mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
[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:
parent
cb9384e474
commit
c3e2d948e8
9 changed files with 236 additions and 91 deletions
|
|
@ -12,6 +12,7 @@ Generic query:
|
||||||
|
|
||||||
Games:
|
Games:
|
||||||
- [Halo: Combat Evolved](https://en.wikipedia.org/wiki/Halo:_Combat_Evolved) support.
|
- [Halo: Combat Evolved](https://en.wikipedia.org/wiki/Halo:_Combat_Evolved) support.
|
||||||
|
- [Just Cause 2: Multiplayer](https://store.steampowered.com/app/259080/Just_Cause_2_Multiplayer_Mod/) support.
|
||||||
|
|
||||||
### Breaking...
|
### Breaking...
|
||||||
Crate:
|
Crate:
|
||||||
|
|
|
||||||
109
GAMES.md
109
GAMES.md
|
|
@ -2,60 +2,61 @@ 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.
|
Beware of the `Notes` column, as it contains information about query port offsets or other query requirements.
|
||||||
|
|
||||||
# Supported games:
|
# Supported games:
|
||||||
| Game | Use name | Protocol | Notes |
|
| Game | Use name | Protocol | Notes |
|
||||||
|------------------------------------|----------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------|----------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| Team Fortress 2 | TF2 | Valve Protocol | |
|
| Team Fortress 2 | TF2 | Valve Protocol | |
|
||||||
| The Ship | TS | Valve Protocol (*Altered) | |
|
| 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: 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 | |
|
| Counter-Strike: Source | CSS | Valve Protocol | |
|
||||||
| Day of Defeat: Source | DODS | Valve Protocol | |
|
| Day of Defeat: Source | DODS | Valve Protocol | |
|
||||||
| Left 4 Dead | L4D | Valve Protocol | |
|
| Left 4 Dead | L4D | Valve Protocol | |
|
||||||
| Left 4 Dead 2 | L4D2 | Valve Protocol | |
|
| Left 4 Dead 2 | L4D2 | Valve Protocol | |
|
||||||
| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | |
|
| Half-Life 2 Deathmatch | HL2DM | Valve Protocol | |
|
||||||
| Alien Swarm | ALIENS | Valve Protocol | |
|
| Alien Swarm | ALIENS | Valve Protocol | |
|
||||||
| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | |
|
| Alien Swarm: Reactive Drop | ASRD | Valve Protocol | |
|
||||||
| Insurgency | INS | Valve Protocol | |
|
| Insurgency | INS | Valve Protocol | |
|
||||||
| Insurgency: Sandstorm | INSS | Valve Protocol | Query port offset: 1. |
|
| Insurgency: Sandstorm | INSS | Valve Protocol | Query port offset: 1. |
|
||||||
| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | |
|
| Insurgency: Modern Infantry Combat | INSMIC | Valve Protocol | |
|
||||||
| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | |
|
| Counter-Strike: Condition Zero | CSCZ | Valve Protocol (GoldSrc) | |
|
||||||
| Day of Defeat | DOD | 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. |
|
| 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 | |
|
| 7 Days To Die | SDTD | Valve Protocol | |
|
||||||
| ARK: Survival Evolved | ASE | Valve Protocol | |
|
| ARK: Survival Evolved | ASE | Valve Protocol | |
|
||||||
| Unturned | UNTURNED | Valve Protocol | |
|
| Unturned | UNTURNED | Valve Protocol | |
|
||||||
| The Forest | TF | Valve Protocol (GoldSrc) | Query port offset: 1. |
|
| The Forest | TF | Valve Protocol (GoldSrc) | Query port offset: 1. |
|
||||||
| Team Fortress Classic | TFC | Valve Protocol | |
|
| Team Fortress Classic | TFC | Valve Protocol | |
|
||||||
| Sven Co-op | SC | Valve Protocol (GoldSrc) | |
|
| Sven Co-op | SC | Valve Protocol (GoldSrc) | |
|
||||||
| Rust | RUST | Valve Protocol | |
|
| Rust | RUST | Valve Protocol | |
|
||||||
| Counter-Strike | CS | Valve Protocol (GoldSrc) | |
|
| Counter-Strike | CS | Valve Protocol (GoldSrc) | |
|
||||||
| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Query port offset: 1. |
|
| Arma 2: Operation Arrowhead | ARMA2OA | Valve Protocol | Query port offset: 1. |
|
||||||
| Day of Infamy | DOI | Valve Protocol | |
|
| Day of Infamy | DOI | Valve Protocol | |
|
||||||
| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | |
|
| Half-Life Deathmatch: Source | HLDMS | Valve Protocol | |
|
||||||
| Risk of Rain 2 | ROR2 | Valve Protocol | Query port offset: 1. |
|
| 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. |
|
| 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 | |
|
| Black Mesa | BM | Valve Protocol | |
|
||||||
| Project Zomboid | PZ | Valve Protocol | |
|
| Project Zomboid | PZ | Valve Protocol | |
|
||||||
| Age of Chivalry | AOC | Valve Protocol | |
|
| Age of Chivalry | AOC | Valve Protocol | |
|
||||||
| Don't Starve Together | DST | Valve Protocol | Query port is 27016. |
|
| Don't Starve Together | DST | Valve Protocol | Query port is 27016. |
|
||||||
| Colony Survival | COLU | Valve Protocol | |
|
| Colony Survival | COLU | Valve Protocol | |
|
||||||
| Onset | ONSET | Valve Protocol | Query port is 7776. |
|
| Onset | ONSET | Valve Protocol | Query port is 7776. |
|
||||||
| Codename CURE | CCURE | Valve Protocol | |
|
| Codename CURE | CCURE | Valve Protocol | |
|
||||||
| Ballistic Overkill | BO | Valve Protocol | Query port is 27016. |
|
| Ballistic Overkill | BO | Valve Protocol | Query port is 27016. |
|
||||||
| BrainBread 2 | BB2 | Valve Protocol | |
|
| BrainBread 2 | BB2 | Valve Protocol | |
|
||||||
| Avorion | AVORION | Valve Protocol | Query port is 27020. |
|
| Avorion | AVORION | Valve Protocol | Query port is 27020. |
|
||||||
| Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. |
|
| Operation: Harsh Doorstop | OHD | Valve Protocol | Query port is 27005. |
|
||||||
| V Rising | VR | Valve Protocol | Query port is 27016. |
|
| V Rising | VR | Valve Protocol | Query port is 27016. |
|
||||||
| Unreal Tournament | UT | GameSpy 1 | Query Port offset: 1. |
|
| Unreal Tournament | UT | GameSpy 1 | Query Port offset: 1. |
|
||||||
| Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. |
|
| Battlefield 1942 | BF1942 | GameSpy 1 | Query port is 23000. |
|
||||||
| Serious Sam | SS | GameSpy 1 | Query Port offset: 1. |
|
| Serious Sam | SS | GameSpy 1 | Query Port offset: 1. |
|
||||||
| Frontlines: Fuel of War | FFOW | Valve Protocol (Proprietary) | Query Port offset: 2. |
|
| Frontlines: Fuel of War | FFOW | Valve Protocol (*Altered) | Query Port offset: 2. |
|
||||||
| Crysis Wars | CW | GameSpy 3 | |
|
| Crysis Wars | CW | GameSpy 3 | |
|
||||||
| Quake 2 | QUAKE2 | Quake 2 | |
|
| Quake 2 | QUAKE2 | Quake 2 | |
|
||||||
| Quake 1 | QUAKE1 | Quake 1 | |
|
| Quake 1 | QUAKE1 | Quake 1 | |
|
||||||
| Quake 3: Arena | QUAKE3A | Quake 3 | |
|
| Quake 3: Arena | QUAKE3A | Quake 3 | |
|
||||||
| Hell Let Loose | HLL | Valve Protocol | Query port is 26420. Note that on this port it might not send players data, as there might be another query port that does send players data. |
|
| Hell Let Loose | HLL | Valve Protocol | Query port is 26420. Note that on this port it might not send players data, as there might be another query port that does send players data. |
|
||||||
| Soldier of Fortune 2 | SOF2 | Quake 3 | |
|
| Soldier of Fortune 2 | SOF2 | Quake 3 | |
|
||||||
| Halo: Combat Evolved | HALOCE | GameSpy 2 | |
|
| Halo: Combat Evolved | HALOCE | GameSpy 2 | |
|
||||||
|
| Just Cause 2: Multiplayer | JC2MP | GameSpy 3 (*Altered) | |
|
||||||
|
|
||||||
## Planned to add support:
|
## Planned to add support:
|
||||||
_
|
_
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ use gamedig::{
|
||||||
ins,
|
ins,
|
||||||
insmic,
|
insmic,
|
||||||
inss,
|
inss,
|
||||||
|
jc2mp,
|
||||||
l4d,
|
l4d,
|
||||||
l4d2,
|
l4d2,
|
||||||
mc,
|
mc,
|
||||||
|
|
@ -189,6 +190,7 @@ fn main() -> GDResult<()> {
|
||||||
"sof2" => println!("{:#?}", sof2::query(ip, port)?),
|
"sof2" => println!("{:#?}", sof2::query(ip, port)?),
|
||||||
"_gamespy2" => println!("{:#?}", gamespy::two::query(address, None)),
|
"_gamespy2" => println!("{:#?}", gamespy::two::query(address, None)),
|
||||||
"haloce" => println!("{:#?}", haloce::query(ip, port)?),
|
"haloce" => println!("{:#?}", haloce::query(ip, port)?),
|
||||||
|
"jc2mp" => println!("{:#?}", jc2mp::query(ip, port)?),
|
||||||
_ => panic!("Undefined game: {}", args[1]),
|
_ => panic!("Undefined game: {}", args[1]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,4 +81,5 @@ pub static GAMES: Map<&'static str, Game> = phf_map! {
|
||||||
"unturned" => game!("Unturned", 27015, Protocol::Valve(SteamApp::UNTURNED)),
|
"unturned" => game!("Unturned", 27015, Protocol::Valve(SteamApp::UNTURNED)),
|
||||||
"ut" => game!("Unreal Tournament", 7778, Protocol::Gamespy(GameSpyVersion::One)),
|
"ut" => game!("Unreal Tournament", 7778, Protocol::Gamespy(GameSpyVersion::One)),
|
||||||
"vr" => game!("V Rising", 27016, Protocol::Valve(SteamApp::VR)),
|
"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
106
src/games/jc2mp.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -65,6 +65,8 @@ pub mod ins;
|
||||||
pub mod insmic;
|
pub mod insmic;
|
||||||
/// Insurgency: Sandstorm
|
/// Insurgency: Sandstorm
|
||||||
pub mod inss;
|
pub mod inss;
|
||||||
|
/// Just Cause 2: Multiplayer
|
||||||
|
pub mod jc2mp;
|
||||||
/// Left 4 Dead
|
/// Left 4 Dead
|
||||||
pub mod l4d;
|
pub mod l4d;
|
||||||
/// Left 4 Dead 2
|
/// 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::TheShip => ts::query(address, port).map(|r| r.into())?,
|
||||||
Protocol::FFOW => ffow::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())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
mod common;
|
pub(crate) mod common;
|
||||||
/// The implementations.
|
/// The implementations.
|
||||||
pub mod protocols;
|
pub mod protocols;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,18 +36,41 @@ impl RequestPacket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GameSpy3 {
|
pub(crate) struct GameSpy3 {
|
||||||
socket: UdpSocket,
|
socket: UdpSocket,
|
||||||
|
payload: [u8; 4],
|
||||||
|
single_packets: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PACKET_SIZE: usize = 2048;
|
const PACKET_SIZE: usize = 2048;
|
||||||
|
const DEFAULT_PAYLOAD: [u8; 4] = [0xFF, 0xFF, 0xFF, 0x01];
|
||||||
|
|
||||||
impl GameSpy3 {
|
impl GameSpy3 {
|
||||||
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
|
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
|
||||||
let socket = UdpSocket::new(address)?;
|
let socket = UdpSocket::new(address)?;
|
||||||
socket.apply_timeout(timeout_settings)?;
|
socket.apply_timeout(timeout_settings)?;
|
||||||
|
|
||||||
Ok(Self { socket })
|
Ok(Self {
|
||||||
|
socket,
|
||||||
|
payload: DEFAULT_PAYLOAD,
|
||||||
|
single_packets: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_custom(
|
||||||
|
address: &SocketAddr,
|
||||||
|
timeout_settings: Option<TimeoutSettings>,
|
||||||
|
payload: [u8; 4],
|
||||||
|
single_packets: bool,
|
||||||
|
) -> GDResult<Self> {
|
||||||
|
let socket = UdpSocket::new(address)?;
|
||||||
|
socket.apply_timeout(timeout_settings)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
socket,
|
||||||
|
payload,
|
||||||
|
single_packets,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive(&mut self, size: Option<usize>, kind: u8) -> GDResult<Bufferer> {
|
fn receive(&mut self, size: Option<usize>, kind: u8) -> GDResult<Bufferer> {
|
||||||
|
|
@ -97,54 +120,57 @@ impl GameSpy3 {
|
||||||
kind: 0,
|
kind: 0,
|
||||||
session_id: THIS_SESSION_ID,
|
session_id: THIS_SESSION_ID,
|
||||||
challenge,
|
challenge,
|
||||||
payload: Some([0xff, 0xff, 0xff, 0x01]),
|
payload: Some(self.payload),
|
||||||
}
|
}
|
||||||
.to_bytes(),
|
.to_bytes(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn get_server_packets(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Vec<Vec<u8>>> {
|
pub(crate) fn get_server_packets(&mut self) -> GDResult<Vec<Vec<u8>>> {
|
||||||
let mut gs3 = GameSpy3::new(address, timeout_settings)?;
|
let challenge = self.make_initial_handshake()?;
|
||||||
|
self.send_data_request(challenge)?;
|
||||||
|
|
||||||
let challenge = gs3.make_initial_handshake()?;
|
let mut values: Vec<Vec<u8>> = Vec::new();
|
||||||
gs3.send_data_request(challenge)?;
|
|
||||||
|
|
||||||
let mut values: Vec<Vec<u8>> = Vec::new();
|
let mut expected_number_of_packets: Option<usize> = None;
|
||||||
|
|
||||||
let mut expected_number_of_packets: Option<usize> = None;
|
while expected_number_of_packets.is_none() || values.len() != expected_number_of_packets.unwrap() {
|
||||||
|
let mut buf = self.receive(None, 0)?;
|
||||||
|
|
||||||
while expected_number_of_packets.is_none() || values.len() != expected_number_of_packets.unwrap() {
|
if self.single_packets {
|
||||||
let mut buf = gs3.receive(None, 0)?;
|
buf.move_position_ahead(11);
|
||||||
|
return Ok(vec![buf.remaining_data_vec()]);
|
||||||
|
}
|
||||||
|
|
||||||
if buf.get_string_utf8()? != "splitnum" {
|
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);
|
return Err(GDError::PacketBad);
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = buf.get_u8()?;
|
Ok(values)
|
||||||
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: &[u8]) -> GDResult<(HashMap<String, String>, Vec<u8>)> {
|
pub(crate) fn data_to_map(packet: &[u8]) -> GDResult<(HashMap<String, String>, Vec<u8>)> {
|
||||||
let mut vars = HashMap::new();
|
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);
|
||||||
|
|
@ -168,7 +194,8 @@ pub fn query_vars(
|
||||||
address: &SocketAddr,
|
address: &SocketAddr,
|
||||||
timeout_settings: Option<TimeoutSettings>,
|
timeout_settings: Option<TimeoutSettings>,
|
||||||
) -> GDResult<HashMap<String, String>> {
|
) -> GDResult<HashMap<String, String>> {
|
||||||
let packets = get_server_packets(address, timeout_settings)?;
|
let mut client = GameSpy3::new(address, timeout_settings)?;
|
||||||
|
let packets = client.get_server_packets()?;
|
||||||
|
|
||||||
let mut vars = HashMap::new();
|
let mut vars = HashMap::new();
|
||||||
|
|
||||||
|
|
@ -308,7 +335,8 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
|
||||||
/// Providing None to the timeout settings results in using the default values.
|
/// Providing None to the timeout settings results in using the default values.
|
||||||
/// (TimeoutSettings::[default](TimeoutSettings::default)).
|
/// (TimeoutSettings::[default](TimeoutSettings::default)).
|
||||||
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
|
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
|
||||||
let packets = get_server_packets(address, timeout_settings)?;
|
let mut client = GameSpy3::new(address, timeout_settings)?;
|
||||||
|
let packets = client.get_server_packets()?;
|
||||||
|
|
||||||
let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDError::PacketBad)?)?;
|
let (mut server_vars, remaining_data) = data_to_map(packets.get(0).ok_or(GDError::PacketBad)?)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ pub enum Protocol {
|
||||||
Valve(valve::SteamApp),
|
Valve(valve::SteamApp),
|
||||||
TheShip,
|
TheShip,
|
||||||
FFOW,
|
FFOW,
|
||||||
|
JC2MP,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A generic version of a response
|
/// A generic version of a response
|
||||||
|
|
@ -56,6 +57,8 @@ pub enum SpecificResponse {
|
||||||
TheShip(crate::games::ts::ExtraResponse),
|
TheShip(crate::games::ts::ExtraResponse),
|
||||||
#[cfg(not(feature = "no_games"))]
|
#[cfg(not(feature = "no_games"))]
|
||||||
FFOW(crate::games::ffow::ExtraResponse),
|
FFOW(crate::games::ffow::ExtraResponse),
|
||||||
|
#[cfg(not(feature = "no_games"))]
|
||||||
|
JC2MP,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Timeout settings for socket operations
|
/// Timeout settings for socket operations
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue