Better packet structure (#3)

* [packet_structure] Initial implementation

* [packet_structure] Fixed on tf2

* [packet_structure] Fixed info request
This commit is contained in:
CosminPerRam 2022-10-22 01:22:09 +03:00 committed by GitHub
parent 4e9458f102
commit e621a9aedd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 148 additions and 74 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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<u8>
}
impl Packet {
fn new(buf: &[u8]) -> GDResult<Self> {
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<u8>) -> 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<u8> {
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<u8>
}
impl SplitPacketInfo {
fn new(_app: &App, buf: &[u8]) -> GDResult<Self> {
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<Vec<u8>> {
let mut buffer: Vec<u8> = 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<Vec<u8>> {
let mut buf: Vec<u8> = 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<Vec<u8>> {
let count = initial_packet[8] - 1;
let mut final_packet: Vec<u8> = initial_packet.to_vec().drain(17..).collect::<Vec<u8>>();
for _ in 0..count {
let mut packet = self.receive(DEFAULT_PACKET_SIZE)?;
final_packet.append(&mut packet.drain(13..).collect::<Vec<u8>>());
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<Packet> {
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<Vec<u8>> {
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::<Vec<u8>>())
}
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<Option<Vec<ServerRule>>> {
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);
}

View file

@ -1,10 +1,6 @@
use std::ops::Add;
use crate::{GDResult, GDError};
pub fn concat_u8_arrays(first: &[u8], second: &[u8]) -> Vec<u8> {
[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";