use std::io::{Cursor, ErrorKind};
use std::net::ToSocketAddrs;
use byteorder::{LittleEndian, ReadBytesExt};
use crate::{A2SClient, ReadCString};
use crate::errors::{Error, Result};
const INFO_REQUEST: [u8; 25] = [0xFF, 0xFF, 0xFF, 0xFF,
0x54,
0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69,
0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79,
0x00];
#[derive(Debug)]
pub struct Info {
pub protocol: u8,
pub name: String,
pub map: String,
pub folder: String,
pub game: String,
pub app_id: u16,
pub players: u8,
pub max_players: u8,
pub bots: u8,
pub server_type: ServerType,
pub server_os: ServerOS,
pub visibility: bool,
pub vac: bool,
pub the_ship: Option<TheShip>,
pub version: String,
pub edf: u8,
pub extended_server_info: ExtendedServerInfo,
pub source_tv: Option<SourceTVInfo>,
}
#[derive(Debug)]
pub struct TheShip {
pub mode: TheShipMode,
pub witnesses: u8,
pub duration: u8 ,
}
#[derive(Debug)]
pub enum TheShipMode {
Hunt,
Elimination,
Duel,
Deathmatch,
VIPTeam,
TeamElimination,
Unknown,
}
impl From<u8> for TheShipMode {
fn from (v: u8) -> Self {
match v {
0 => Self::Hunt,
1 => Self::Elimination,
2 => Self::Duel,
3 => Self::Deathmatch,
4 => Self::VIPTeam,
5 => Self::TeamElimination,
_ => Self::Unknown,
}
}
}
#[derive(Debug)]
pub struct ExtendedServerInfo {
pub port: Option<u16>,
pub steam_id: Option<u64>,
pub keywords: Option<String>,
pub game_id: Option<u64>,
}
#[derive(Debug)]
pub struct SourceTVInfo {
pub port: u16,
pub name: String,
}
#[derive(Debug)]
pub enum ServerType {
Dedicated,
NonDedicated,
SourceTV,
}
#[derive(Debug)]
pub enum ServerOS {
Linux,
Windows,
Mac,
}
impl A2SClient {
pub fn info<A: ToSocketAddrs>(&self, addr: A) -> Result<Info> {
let data = self.send(&INFO_REQUEST, addr)?;
let mut data = Cursor::new(data);
if data.read_u8()? != 0x49u8 {
return Err(Error::InvalidResponse);
}
let app_id: u16;
let mut flag = 0u8;
Ok(Info{
protocol: data.read_u8()?,
name: data.read_cstring()?,
map: data.read_cstring()?,
folder: data.read_cstring()?,
game: data.read_cstring()?,
app_id: {
app_id = data.read_u16::<LittleEndian>()?;
app_id
},
players: data.read_u8()?,
max_players: data.read_u8()?,
bots: data.read_u8()?,
server_type: {
match data.read_u8()? as char {
'd' => ServerType::Dedicated,
'i' => ServerType::NonDedicated,
'p' => ServerType::SourceTV,
_ => return Err(Error::Other("Invalid server type"))
}
},
server_os: {
match data.read_u8()? as char {
'l' => ServerOS::Linux,
'w' => ServerOS::Windows,
'm' | 'o' => ServerOS::Mac,
_ => return Err(Error::Other("Invalid environment"))
}
},
visibility: data.read_u8()? != 0,
vac: data.read_u8()? != 0,
the_ship: {
if app_id == 2400 {
Some(TheShip{
mode: TheShipMode::from(data.read_u8()?),
witnesses: data.read_u8()?,
duration: data.read_u8()?,
})
} else {
None
}
},
version: data.read_cstring()?,
edf: {
match data.read_u8() {
Ok(val) => { flag = val; },
Err(err) => { if err.kind() != ErrorKind::UnexpectedEof { return Err(Error::Io(err)); }}
}
flag
},
extended_server_info: ExtendedServerInfo{
port: {
if flag & 0x80 != 0 {
Some(data.read_u16::<LittleEndian>()?)
} else {
None
}
},
steam_id: {
if flag & 0x10 != 0 {
Some(data.read_u64::<LittleEndian>()?)
} else {
None
}
},
keywords: {
if flag & 0x20 != 0 {
Some(data.read_cstring()?)
} else {
None
}
},
game_id: {
if flag & 0x01 != 0 {
Some(data.read_u64::<LittleEndian>()?)
} else {
None
}
}
},
source_tv: {
if flag & 0x40 != 0 {
Some(SourceTVInfo{
port: data.read_u16::<LittleEndian>()?,
name: data.read_cstring()?,
})
} else {
None
}
}
})
}
}