mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
Better packet structure (#3)
* [packet_structure] Initial implementation * [packet_structure] Fixed on tf2 * [packet_structure] Fixed info request
This commit is contained in:
parent
4e9458f102
commit
e621a9aedd
4 changed files with 148 additions and 74 deletions
6
GAMES.md
6
GAMES.md
|
|
@ -1,11 +1,11 @@
|
||||||
|
|
||||||
# Supported games:
|
# Supported games:
|
||||||
| ID | Name | Protocol | Notes |
|
| ID | Name | Protocol | Notes |
|
||||||
|-------|----------------------------------|----------------|----------------------------------|
|
|-------|----------------------------------|----------------|------------------------------------------------------------------------------|
|
||||||
| TF2 | Team Fortress 2 | Valve Protocol | |
|
| TF2 | Team Fortress 2 | Valve Protocol | |
|
||||||
| TS | The Ship | Valve Protocol | |
|
| TS | The Ship | Valve Protocol | |
|
||||||
| CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. |
|
| 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 | |
|
| CSS | Counter-Strike: Source | Valve Protocol | If protocol is 7, the Rules query crashes. |
|
||||||
| DODS | Day of Defeat: Source | Valve Protocol | |
|
| DODS | Day of Defeat: Source | Valve Protocol | |
|
||||||
| L4D | Left 4 Dead | Valve Protocol | |
|
| L4D | Left 4 Dead | Valve Protocol | |
|
||||||
| L4D2 | Left 4 Dead 2 | Valve Protocol | |
|
| L4D2 | Left 4 Dead 2 | Valve Protocol | |
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use gamedig::games::csgo;
|
use gamedig::games::csgo;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let response = csgo::query("51.38.142.109", None);
|
let response = csgo::query("216.52.148.47", None);
|
||||||
match response {
|
match response {
|
||||||
Err(error) => println!("Couldn't query, error: {error}"),
|
Err(error) => println!("Couldn't query, error: {error}"),
|
||||||
Ok(r) => println!("{:?}", r)
|
Ok(r) => println!("{:?}", r)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use crate::{GDError, GDResult};
|
use crate::{GDError, GDResult};
|
||||||
use crate::utils::{buffer, complete_address, concat_u8_arrays};
|
use crate::utils::{buffer, complete_address};
|
||||||
|
|
||||||
/// The type of the server.
|
/// The type of the server.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -111,14 +111,15 @@ pub struct ExtraData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of the request, see the [protocol](https://developer.valvesoftware.com/wiki/Server_queries).
|
/// 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 {
|
pub enum Request {
|
||||||
/// Known as `A2S_INFO`
|
/// Known as `A2S_INFO`
|
||||||
INFO,
|
INFO = 0x54,
|
||||||
/// Known as `A2S_PLAYERS`
|
/// Known as `A2S_PLAYERS`
|
||||||
PLAYERS,
|
PLAYERS = 0x55,
|
||||||
/// Known as `A2S_RULES`
|
/// Known as `A2S_RULES`
|
||||||
RULES
|
RULES = 0x56
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supported app id's
|
/// Supported app id's
|
||||||
|
|
@ -176,6 +177,103 @@ pub struct ValveProtocol {
|
||||||
|
|
||||||
static DEFAULT_PACKET_SIZE: usize = 2048;
|
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 {
|
impl ValveProtocol {
|
||||||
fn new(address: &str, port: u16) -> Self {
|
fn new(address: &str, port: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -189,62 +287,53 @@ impl ValveProtocol {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive(&self, buffer_size: usize) -> GDResult<Vec<u8>> {
|
fn receive_raw(&self, buffer_size: usize) -> GDResult<Vec<u8>> {
|
||||||
let mut buffer: Vec<u8> = vec![0; buffer_size];
|
let mut buf: 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()))?;
|
let (amt, _) = self.socket.recv_from(&mut buf.as_mut_slice()).map_err(|e| GDError::PacketReceive(e.to_string()))?;
|
||||||
Ok(buffer[..amt].to_vec())
|
|
||||||
|
if amt < 9 {
|
||||||
|
return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 9 bytes long.".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_truncated(&self, initial_packet: &[u8]) -> GDResult<Vec<u8>> {
|
Ok(buf[..amt].to_vec())
|
||||||
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 {
|
fn receive(&self, app: &App, buffer_size: usize) -> GDResult<Packet> {
|
||||||
let mut packet = self.receive(DEFAULT_PACKET_SIZE)?;
|
let mut buf = self.receive_raw(buffer_size)?;
|
||||||
final_packet.append(&mut packet.drain(13..).collect::<Vec<u8>>());
|
|
||||||
|
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)
|
Ok(final_packet)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
Packet::new(&buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ask for a specific request only.
|
/// Ask for a specific request only.
|
||||||
pub fn get_request_data(&self, app: &App, kind: Request) -> GDResult<Vec<u8>> {
|
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 request_initial_packet = Packet::initial(kind.clone()).to_bytes();
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
self.send(&request_initial_packet)?;
|
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 {
|
if packet.kind != 0x41 { //'A'
|
||||||
return Err(GDError::PacketOverflow("Any Valve Protocol response can't be under 9 bytes long.".to_string()));
|
return Ok(packet.payload.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if initial_receive[4] != 0x41 { //'A'
|
let challenge = packet.payload;
|
||||||
return Ok(initial_receive.drain(5..).collect());
|
let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes();
|
||||||
}
|
|
||||||
|
|
||||||
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]]
|
|
||||||
};
|
|
||||||
|
|
||||||
self.send(&challenge_packet)?;
|
self.send(&challenge_packet)?;
|
||||||
|
Ok(self.receive(app, DEFAULT_PACKET_SIZE)?.payload)
|
||||||
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>>())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the server information's.
|
/// Get the server information's.
|
||||||
|
|
@ -347,7 +436,7 @@ impl ValveProtocol {
|
||||||
|
|
||||||
/// Get the server rules's.
|
/// Get the server rules's.
|
||||||
pub fn get_server_rules(&self, app: &App) -> GDResult<Option<Vec<ServerRule>>> {
|
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);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
src/utils.rs
15
src/utils.rs
|
|
@ -1,10 +1,6 @@
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use crate::{GDResult, GDError};
|
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 {
|
pub fn complete_address(address: &str, port: u16) -> String {
|
||||||
String::from(address.to_owned() + ":").add(&*port.to_string())
|
String::from(address.to_owned() + ":").add(&*port.to_string())
|
||||||
}
|
}
|
||||||
|
|
@ -75,17 +71,6 @@ pub mod buffer {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn complete_address_test() {
|
fn complete_address_test() {
|
||||||
let address = "192.168.0.1";
|
let address = "192.168.0.1";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue