diff --git a/src/games/tf2.rs b/src/games/tf2.rs index 06d89cc..3b6ea8c 100644 --- a/src/games/tf2.rs +++ b/src/games/tf2.rs @@ -1,5 +1,5 @@ use crate::errors::GDError; -use crate::valve::{Response, ValveProtocol, App}; +use crate::valve::{Response, ValveProtocol, App, GatheringSettings}; pub struct TF2; @@ -8,6 +8,10 @@ impl TF2 { ValveProtocol::query(App::TF2, address, match port { None => 27015, Some(port) => port - }, true, true) + }, GatheringSettings { + info: false, + players: false, + rules: true + }) } } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index eeaf276..984ea92 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -18,6 +18,13 @@ pub enum Environment { #[derive(Debug)] pub struct Response { + pub info: Option, + pub players: Option, + pub rules: Option +} + +#[derive(Debug)] +pub struct ServerInfo { pub protocol: u8, pub map: String, pub name: String, @@ -36,6 +43,33 @@ pub struct Response { pub extra_data: Option } +#[derive(Debug)] +pub struct ServerPlayers { + pub count: u8, + pub players: Vec +} + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32, + pub deaths: Option, //the_ship + pub money: Option, //the_ship +} + +#[derive(Debug)] +pub struct ServerRules { + pub count: u16, + pub rules: Vec +} + +#[derive(Debug)] +pub struct Rule { + pub name: String, + pub value: String +} + #[derive(Debug)] pub struct TheShip { pub mode: u8, @@ -53,24 +87,32 @@ pub struct ExtraData { pub game_id: Option } +#[derive(PartialEq)] pub enum Request { INFO, - PLAYER, + PLAYERS, RULES } #[derive(PartialEq)] pub enum App { TF2 = 440, + CSGO = 730, TheShip = 2400 } +pub struct GatheringSettings { + pub info: bool, + pub players: bool, + pub rules: bool +} + pub struct ValveProtocol { socket: UdpSocket, complete_address: String } -static DEFAULT_PACKET_SIZE: usize = 256; +static DEFAULT_PACKET_SIZE: usize = 2048; impl ValveProtocol { fn new(address: &str, port: u16) -> Self { @@ -91,45 +133,52 @@ impl ValveProtocol { Ok(buffer[..amt].to_vec()) } - pub fn do_request(&self, kind: Request) -> Result, GDError> { + pub fn get_request_data(&self, kind: Request) -> Result, GDError> { let info_initial_packet = vec![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]; - let player_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55]; - let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56]; - - let no_challenge: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF]; + let players_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF]; + let rules_initial_packet = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF]; let request_initial_packet = match kind { Request::INFO => info_initial_packet, - Request::PLAYER => concat_u8_arrays(&player_initial_packet, &no_challenge), - Request::RULES => concat_u8_arrays(&rules_initial_packet, &no_challenge) + Request::PLAYERS => players_initial_packet, + Request::RULES => rules_initial_packet }; self.send(&request_initial_packet)?; - let buffer = self.receive(DEFAULT_PACKET_SIZE)?; + let mut initial_receive = self.receive(DEFAULT_PACKET_SIZE)?; - if buffer.len() < 9 { + if initial_receive.len() < 9 { return Err(GDError::PacketOverflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); } - if buffer[4] != 41 { //'A' - return Ok(buffer); + if initial_receive[4] != 0x41 { //'A' + return Ok(initial_receive.drain(5..).collect()); } - let challenge: [u8; 4] = [buffer[5], buffer[6], buffer[7], buffer[8]]; - self.send(&concat_u8_arrays(&request_initial_packet, &challenge))?; + let challenge: [u8; 4] = [initial_receive[5], initial_receive[6], initial_receive[7], initial_receive[8]]; + let challenge_packet = match kind { + Request::INFO => concat_u8_arrays(&request_initial_packet, &challenge), + Request::PLAYERS => vec![0xFF, 0xFF, 0xFF, 0xFF, 0x55, challenge[0], challenge[1], challenge[2], challenge[3]], + Request::RULES => vec![0xFF, 0xFF, 0xFF, 0xFF, 0x56, challenge[0], challenge[1], challenge[2], challenge[3]] + }; - Ok(self.receive(DEFAULT_PACKET_SIZE)?) + self.send(&challenge_packet)?; + + let mut after_challenge_receive = self.receive(DEFAULT_PACKET_SIZE)?; + + if kind == Request::RULES && after_challenge_receive[0] == 0xFE { + Ok(after_challenge_receive.drain(17..).collect()) + } + else { + Ok(after_challenge_receive.drain(5..).collect()) + } } -} -impl ValveProtocol { - pub(crate) fn query(app: App, address: &str, port: u16, gather_players: bool, gather_rules: bool) -> Result { - let client = ValveProtocol::new(address, port); + fn get_server_info(&self, app: &App) -> Result { + let buf = self.get_request_data(Request::INFO)?; + let mut pos = 0; - let buf = client.do_request(Request::INFO)?; - let mut pos = 4; - - Ok(Response { + Ok(ServerInfo { protocol: buffer::get_u8(&buf, &mut pos)?, name: buffer::get_string(&buf, &mut pos)?, map: buffer::get_string(&buf, &mut pos)?, @@ -151,7 +200,7 @@ impl ValveProtocol { }, has_password: buffer::get_u8(&buf, &mut pos)? == 1, vac_secured: buffer::get_u8(&buf, &mut pos)? == 1, - the_ship: match app == App::TheShip { + the_ship: match *app == App::TheShip { false => None, true => Some(TheShip { mode: buffer::get_u8(&buf, &mut pos)?, @@ -191,4 +240,80 @@ impl ValveProtocol { } }) } + + fn get_server_players(&self, app: &App) -> Result { + let buf = self.get_request_data(Request::PLAYERS)?; + let mut pos = 0; + + println!("{:x?}", buf); + + let count = buffer::get_u8(&buf, &mut pos)?; + let mut players: Vec = Vec::new(); + + for _ in 0..count { + pos += 1; //skip the index byte + players.push(Player { + name: buffer::get_string(&buf, &mut pos)?, + score: buffer::get_u32_le(&buf, &mut pos)?, + duration: buffer::get_f32_le(&buf, &mut pos)?, + deaths: match *app == App::TheShip { + false => None, + true => Some(buffer::get_u32_le(&buf, &mut pos)?) + }, + money: match *app == App::TheShip { + false => None, + true => Some(buffer::get_u32_le(&buf, &mut pos)?) + } + }); + } + + Ok(ServerPlayers { + count, + players + }) + } + + fn get_server_rules(&self, app: &App) -> Result { + let buf = self.get_request_data(Request::RULES)?; + let mut pos = 0; + + println!("{:x?}", buf); + + let count = buffer::get_u16_le(&buf, &mut pos)?; + let mut rules: Vec = Vec::new(); + + for _ in 0..count { + rules.push(Rule { + name: buffer::get_string(&buf, &mut pos)?, //might be truncated! + value: buffer::get_string(&buf, &mut pos)? + }); + println!("{} = {}", rules[rules.len() - 1].name, rules[rules.len() - 1].value); + } + + println!("{} {}", buf.len(), pos); + + Ok(ServerRules { + count, + rules + }) + } + + pub(crate) fn query(app: App, address: &str, port: u16, gather: GatheringSettings) -> Result { + let client = ValveProtocol::new(address, port); + + Ok(Response { + info: match gather.info { + false => None, + true => Some(client.get_server_info(&app)?) + }, + players: match gather.players { + false => None, + true => Some(client.get_server_players(&app)?) + }, + rules: match gather.rules { + false => None, + true => Some(client.get_server_rules(&app)?) + } + }) + } } diff --git a/src/utils.rs b/src/utils.rs index e0c5e55..6dd678d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,14 +9,6 @@ pub fn complete_address(address: &str, port: u16) -> String { String::from(address.to_owned() + ":").add(&*port.to_string()) } -pub fn combine_two_u8_le(high: u8, low: u8) -> u16 { - u16::from_be_bytes([high, low]) -} - -pub fn combine_eight_u8_le(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> u64 { - u64::from_be_bytes([a, b, c, d, e, f, g, h]) -} - pub mod buffer { use super::*; @@ -35,17 +27,37 @@ pub mod buffer { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } - let value = combine_two_u8_le(buf[*pos + 1], buf[*pos]); + let value = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]); *pos += 2; Ok(value) } + pub fn get_u32_le(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos + 3 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + } + + let value = u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); + *pos += 4; + Ok(value) + } + + pub fn get_f32_le(buf: &[u8], pos: &mut usize) -> Result { + if buf.len() <= *pos + 3 { + return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); + } + + let value = f32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]); + *pos += 4; + Ok(value) + } + pub fn get_u64_le(buf: &[u8], pos: &mut usize) -> Result { if buf.len() <= *pos + 7 { return Err(GDError::PacketUnderflow("Unexpectedly short packet.".to_string())); } - let value = combine_eight_u8_le(buf[*pos + 7], buf[*pos + 6], buf[*pos + 5], buf[*pos + 4], buf[*pos + 3], buf[*pos + 2], buf[*pos + 1], buf[*pos]); + let value = u64::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3], buf[*pos + 4], buf[*pos + 5], buf[*pos + 6], buf[*pos + 7]]); *pos += 8; Ok(value) } @@ -81,16 +93,6 @@ mod tests { assert_eq!(complete_address(address, port), "192.168.0.1:27015"); } - #[test] - fn combine_two_u8_le_test() { - assert_eq!(combine_two_u8_le(49, 123), 12667); - } - - #[test] - fn combine_eight_u8_le_test() { - assert_eq!(combine_eight_u8_le(49, 123, 44, 52, 3, 250, 22, 0), 3565492131910522368); - } - #[test] fn get_u8_test() { let data = [72]; @@ -111,6 +113,26 @@ mod tests { assert_eq!(pos, 2); } + #[test] + fn get_u32_le_test() { + let data = [72, 29, 128, 100]; + let mut pos = 0; + assert_eq!(buffer::get_u32_le(&data, &mut pos).unwrap(), 1686117704); + assert_eq!(pos, 4); + assert!(buffer::get_u32_le(&data, &mut pos).is_err()); + assert_eq!(pos, 4); + } + + #[test] + fn get_f32_le_test() { + let data = [72, 29, 128, 100]; + let mut pos = 0; + assert_eq!(buffer::get_f32_le(&data, &mut pos).unwrap(), 1.8906345e22); + assert_eq!(pos, 4); + assert!(buffer::get_f32_le(&data, &mut pos).is_err()); + assert_eq!(pos, 4); + } + #[test] fn get_u64_le_test() { let data = [72, 29, 128, 99, 69, 4, 2, 0];