From 3b4dd9d9e4cbc9f9711508f61deb2ac3bbbeaf9c Mon Sep 17 00:00:00 2001 From: cosminperram Date: Sat, 22 Oct 2022 02:27:11 +0300 Subject: [PATCH] Decompression support --- Cargo.toml | 2 ++ PROTOCOLS.md | 6 ++-- src/errors.rs | 5 ++- src/protocols/valve.rs | 72 ++++++++++++++++++++++++++++++------------ 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb8d613..8e233de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,5 @@ keywords = ["server", "verify", "game", "check", "status"] msrv = "1.58.1" [dependencies] +crc32fast = "1.3.2" +bzip2-rs = "0.1.2" diff --git a/PROTOCOLS.md b/PROTOCOLS.md index 48aad3e..ddb2a63 100644 --- a/PROTOCOLS.md +++ b/PROTOCOLS.md @@ -1,8 +1,8 @@ # Supported protocols: -| Name | Documentation reference | Used by | -|----------------|---------------------------------------------------------------------------|---------------------| -| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, The_ship | +| Name | Documentation reference | Used by | Notes | +|----------------|---------------------------------------------------------------------------|------------------------------------------------|----------------------------------------| +| Valve Protocol | [Server Queries](https://developer.valvesoftware.com/wiki/Server_queries) | TF2, CSGO, TS, CSS, DODS, GM, HL2DM, L4D, L4D2 | Multi-packet decompression not tested. | ## Planned to add support: Minecraft protocol diff --git a/src/errors.rs b/src/errors.rs index f4713d4..a1d199a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,7 +17,9 @@ pub enum GDError { PacketSend(String), /// Couldn't send the receive. PacketReceive(String), - /// Unknown cast while translating a value to an enum + /// Couldn't decompress data. + Decompress(String), + /// Unknown cast while translating a value to an enum. UnknownEnumCast, /// The server queried is not from the queried game. BadGame(String) @@ -31,6 +33,7 @@ impl fmt::Display for GDError { GDError::PacketBad(details) => write!(f, "Packet bad: {details}"), GDError::PacketSend(details) => write!(f, "Couldn't send a packet: {details}"), GDError::PacketReceive(details) => write!(f, "Couldn't receive a packet: {details}"), + GDError::Decompress(details) => write!(f, "Couldn't decompress data: {details}"), GDError::UnknownEnumCast => write!(f, "Unknown enum cast encountered."), GDError::BadGame(details) => write!(f, "Queried another game that the supposed one: {details}"), } diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index d320ce4..11c42da 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -1,4 +1,5 @@ use std::net::UdpSocket; +use bzip2_rs::decoder::Decoder; use crate::{GDError, GDResult}; use crate::utils::{buffer, complete_address}; @@ -233,16 +234,19 @@ impl Packet { #[derive(Debug)] #[allow(dead_code)] //remove this later on -struct SplitPacketInfo { +struct SplitPacket { pub header: u32, pub id: u32, pub total: u8, pub number: u8, pub size: u16, - pub payload: Vec + pub compressed: bool, + pub decompressed_size: Option, + pub uncompressed_crc32: Option, + payload: Vec } -impl SplitPacketInfo { +impl SplitPacket { fn new(_app: &App, buf: &[u8]) -> GDResult { let mut pos = 0; @@ -251,16 +255,10 @@ impl SplitPacketInfo { 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![] - } + let compressed = ((id >> 31) & 1) == 1; + let (decompressed_size, uncompressed_crc32) = match compressed { + false => (None, None), + true => (Some(buffer::get_u32_le(&buf, &mut pos)?), Some(buffer::get_u32_le(&buf, &mut pos)?)) }; Ok(Self { @@ -269,9 +267,44 @@ impl SplitPacketInfo { total, number, size, - payload + compressed, + decompressed_size, + uncompressed_crc32, + payload: buf[pos..].to_vec() }) } + + fn decompress(&self) -> GDResult> { + if !self.compressed { + let mut decoder = Decoder::new(); + decoder.write(&self.payload).map_err(|e| GDError::Decompress(e.to_string()))?; + + let decompressed_size = self.decompressed_size.unwrap() as usize; + + let mut decompressed_payload = Vec::with_capacity(decompressed_size); + decoder.read(&mut decompressed_payload).map_err(|e| GDError::Decompress(e.to_string()))?; + + if decompressed_payload.len() != decompressed_size { + Err(GDError::Decompress("Valve Protocol: The decompressed payload size doesn't match the expected one.".to_string())) + } + else if crc32fast::hash(&decompressed_payload) != self.uncompressed_crc32.unwrap() { + Err(GDError::Decompress("Valve Protocol: The decompressed crc32 hash does not match the expected one.".to_string())) + } + else { + Ok(decompressed_payload) + } + } else { //already decompressed + Ok(self.payload.clone()) + } + } + + fn get_payload(&self) -> GDResult> { + if self.compressed { + Ok(self.decompress()?) + } else { + Ok(self.payload.clone()) + } + } } impl ValveProtocol { @@ -302,16 +335,15 @@ impl ValveProtocol { 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)?; + let mut main_packet = SplitPacket::new(app, &buf)?; - for _ in 1..initial_split_packet_info.total { + for _ in 1..main_packet.total { buf = self.receive_raw(buffer_size)?; - let split_packet_info = SplitPacketInfo::new(app, &buf)?; - final_packet.payload.extend(split_packet_info.payload); + let chunk_packet = SplitPacket::new(app, &buf)?; + main_packet.payload.extend(chunk_packet.payload); } - Ok(final_packet) + Ok(Packet::new(&main_packet.get_payload()?)?) } else { Packet::new(&buf)