From e621a9aedd3ae34baba9966b38d94c6b7fb83083 Mon Sep 17 00:00:00 2001 From: CosminPerRam Date: Sat, 22 Oct 2022 01:22:09 +0300 Subject: [PATCH] Better packet structure (#3) * [packet_structure] Initial implementation * [packet_structure] Fixed on tf2 * [packet_structure] Fixed info request --- GAMES.md | 20 ++--- examples/csgo.rs | 2 +- src/protocols/valve.rs | 185 ++++++++++++++++++++++++++++++----------- src/utils.rs | 15 ---- 4 files changed, 148 insertions(+), 74 deletions(-) diff --git a/GAMES.md b/GAMES.md index 8d8350a..bf3adf4 100644 --- a/GAMES.md +++ b/GAMES.md @@ -1,15 +1,15 @@ # Supported games: -| ID | Name | Protocol | Notes | -|-------|----------------------------------|----------------|----------------------------------| -| TF2 | Team Fortress 2 | Valve Protocol | | -| TS | The Ship | Valve Protocol | | -| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | -| CSS | Counter-Strike: Source | Valve Protocol | | -| DODS | Day of Defeat: Source | Valve Protocol | | -| L4D | Left 4 Dead | Valve Protocol | | -| L4D2 | Left 4 Dead 2 | Valve Protocol | | -| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | +| ID | Name | Protocol | Notes | +|-------|----------------------------------|----------------|------------------------------------------------------------------------------| +| TF2 | Team Fortress 2 | Valve Protocol | | +| TS | The Ship | Valve Protocol | | +| CSGO | Counter-Strike: Global Offensive | Valve Protocol | The server wouldn't respond the to Rules query since the 21 Feb 2014 update. | +| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, the Rules query crashes. | +| DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | +| HL2DM | Half-Life 2 Deathmatch | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/csgo.rs b/examples/csgo.rs index 48f0943..ca3512e 100644 --- a/examples/csgo.rs +++ b/examples/csgo.rs @@ -2,7 +2,7 @@ use gamedig::games::csgo; fn main() { - let response = csgo::query("51.38.142.109", None); + let response = csgo::query("216.52.148.47", None); match response { Err(error) => println!("Couldn't query, error: {error}"), Ok(r) => println!("{:?}", r) diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 23e9d10..d320ce4 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,6 +1,6 @@ use std::net::UdpSocket; use crate::{GDError, GDResult}; -use crate::utils::{buffer, complete_address, concat_u8_arrays}; +use crate::utils::{buffer, complete_address}; /// The type of the server. #[derive(Debug)] @@ -111,14 +111,15 @@ pub struct ExtraData { } /// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries). -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] +#[repr(u8)] pub enum Request { /// Known as `A2S_INFO` - INFO, + INFO = 0x54, /// Known as `A2S_PLAYERS` - PLAYERS, + PLAYERS = 0x55, /// Known as `A2S_RULES` - RULES + RULES = 0x56 } /// Supported app id's @@ -176,6 +177,103 @@ pub struct ValveProtocol { static DEFAULT_PACKET_SIZE: usize = 2048; +#[derive(Debug, Clone)] +struct Packet { + pub header: u32, + pub kind: u8, + pub payload: Vec +} + +impl Packet { + fn new(buf: &[u8]) -> GDResult { + let mut pos = 0; + Ok(Self { + header: buffer::get_u32_le(&buf, &mut pos)?, + kind: buffer::get_u8(&buf, &mut pos)?, + payload: buf[pos..].to_vec() + }) + } + + fn challenge(kind: Request, challenge: Vec) -> Self { + let mut initial = Packet::initial(kind); + + Self { + header: initial.header, + kind: initial.kind, + payload: match initial.kind { + 0x54 => { + initial.payload.extend(challenge); + initial.payload + }, + _ => challenge + } + } + } + + fn initial(kind: Request) -> Self { + Self { + header: 4294967295, //FF FF FF FF + kind: kind as u8, + payload: match kind { + Request::INFO => String::from("Source Engine Query\0").into_bytes(), + _ => vec![0xFF, 0xFF, 0xFF, 0xFF] + } + } + } + + fn to_bytes(&self) -> Vec { + let mut buf = Vec::from(self.header.to_be_bytes()); + + buf.push(self.kind); + buf.extend(&self.payload); + + buf + } +} + +#[derive(Debug)] +#[allow(dead_code)] //remove this later on +struct SplitPacketInfo { + pub header: u32, + pub id: u32, + pub total: u8, + pub number: u8, + pub size: u16, + pub payload: Vec +} + +impl SplitPacketInfo { + fn new(_app: &App, buf: &[u8]) -> GDResult { + let mut pos = 0; + + let header = buffer::get_u32_le(&buf, &mut pos)?; + let id = buffer::get_u32_le(&buf, &mut pos)?; + let total = buffer::get_u8(&buf, &mut pos)?; + let number = buffer::get_u8(&buf, &mut pos)?; + let size = buffer::get_u16_le(&buf, &mut pos)?; + + let payload = match ((id >> 31) & 1) == 1 { + false => buf[pos..].to_vec(), + true => { + let _decompressed_size = buffer::get_u32_le(&buf, &mut pos)?; + let _uncompressed_crc32 = buffer::get_u32_le(&buf, &mut pos)?; + + //decompress... + vec![] + } + }; + + Ok(Self { + header, + id, + total, + number, + size, + payload + }) + } +} + impl ValveProtocol { fn new(address: &str, port: u16) -> Self { Self { @@ -189,62 +287,53 @@ impl ValveProtocol { Ok(()) } - fn receive(&self, buffer_size: usize) -> GDResult> { - let mut buffer: Vec = vec![0; buffer_size]; - let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; - Ok(buffer[..amt].to_vec()) - } + fn receive_raw(&self, buffer_size: usize) -> GDResult> { + let mut buf: Vec = vec![0; buffer_size]; + let (amt, _) = self.socket.recv_from(&mut buf.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?; - fn receive_truncated(&self, initial_packet: &[u8]) -> GDResult> { - let count = initial_packet[8] - 1; - let mut final_packet: Vec = initial_packet.to_vec().drain(17..).collect::>(); - - for _ in 0..count { - let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; - final_packet.append(&mut packet.drain(13..).collect::>()); + if amt < 9 { + return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); } - Ok(final_packet) + Ok(buf[..amt].to_vec()) + } + + fn receive(&self, app: &App, buffer_size: usize) -> GDResult { + let mut buf = self.receive_raw(buffer_size)?; + + if buf[0] == 0xFE { //the packet is split + let initial_split_packet_info = SplitPacketInfo::new(app, &buf)?; + let mut final_packet = Packet::new(&initial_split_packet_info.payload)?; + + for _ in 1..initial_split_packet_info.total { + buf = self.receive_raw(buffer_size)?; + let split_packet_info = SplitPacketInfo::new(app, &buf)?; + final_packet.payload.extend(split_packet_info.payload); + } + + Ok(final_packet) + } + else { + Packet::new(&buf) + } } /// Ask for a specific request only. pub fn get_request_data(&self, app: &App, kind: Request) -> GDResult> { - 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 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::PLAYERS => players_initial_packet, - Request::RULES => rules_initial_packet - }; + let request_initial_packet = Packet::initial(kind.clone()).to_bytes(); self.send(&request_initial_packet)?; - let mut initial_receive = self.receive(DEFAULT_PACKET_SIZE)?; + let packet = self.receive(app, DEFAULT_PACKET_SIZE)?; - if initial_receive.len() < 9 { - return Err(GDError::PacketOverflow("Any Valve Protocol response can't be under 9 bytes long.".to_string())); + if packet.kind != 0x41 { //'A' + return Ok(packet.payload.clone()); } - if initial_receive[4] != 0x41 { //'A' - return Ok(initial_receive.drain(5..).collect()); - } - - 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]] - }; + let challenge = packet.payload; + let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes(); self.send(&challenge_packet)?; - - let mut packet = self.receive(DEFAULT_PACKET_SIZE)?; - if (packet[0] == 0xFE || (packet[0] == 0xFF && packet[4] == 0x45)) && (*app != App::TS) { //'E' - self.receive_truncated(&packet) - } else { - Ok(packet.drain(5..).collect::>()) - } + Ok(self.receive(app, DEFAULT_PACKET_SIZE)?.payload) } /// Get the server information's. @@ -347,7 +436,7 @@ impl ValveProtocol { /// Get the server rules's. pub fn get_server_rules(&self, app: &App) -> GDResult>> { - if *app == App::CSGO { //cause csgo response here is broken after feb 21 2014 + if *app == App::CSGO { //cause csgo wont respond to this since feb 21 2014 update return Ok(None); } diff --git a/src/utils.rs b/src/utils.rs index cdce785..bb91858 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,10 +1,6 @@ use std::ops::Add; use crate::{GDResult, GDError}; -pub fn concat_u8_arrays(first: &[u8], second: &[u8]) -> Vec { - [first, second].concat() -} - pub fn complete_address(address: &str, port: u16) -> String { String::from(address.to_owned() + ":").add(&*port.to_string()) } @@ -75,17 +71,6 @@ pub mod buffer { mod tests { use super::*; - #[test] - fn concat_u8_arrays_test() { - let a: [u8; 2] = [1, 2]; - let b: [u8; 2] = [3, 4]; - let combined = concat_u8_arrays(&a, &b); - assert_eq!(combined[0], a[0]); - assert_eq!(combined[1], a[1]); - assert_eq!(combined[2], b[0]); - assert_eq!(combined[3], b[1]); - } - #[test] fn complete_address_test() { let address = "192.168.0.1";