mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-05-18 09:35:50 +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
20
GAMES.md
20
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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
15
src/utils.rs
15
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<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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue