mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-05-18 09:35:50 +00:00
[Crate] Refactor: Buffer (#62)
* merge: local -> fresh pr * fix: utf16 bugs + clean up * docs: buffer * [feat/buffer] Replaced errors, partial valve protocol ported * fix: change buffer name + add endian switch * chore: update module * refactor: valve_master * refactor: mc bedrock * refactor: mc java * refactor: mc types * refactor: mc legacy 1.8 * refactor: mc legacy 1.4 * refactor: valve * refactor: valve types * refactor: quake * refactor: mc legacy 1.6 * refactor: gamespy 1 * fix: make switch endian move cursor * fix: reset cursor on switch * chore: add switch endian tests * chore: remove todo comment * chore: clean up buffer generic types * refactor: prop len when switching in mc bedrock * fix: tests and current pos fn * refactor: ffow * refactor: jc2mp * refactor: gs 3 * refactor: gs 2 * fix: mc bedrock prop on move + move data * fix: mc java lifetime error * fix: mc legacy 1.6 using pub not pub crate * fix: quake client lifetime * fix: quake 2 clippy warning * fix: valve lifetime issue * fix: buffer test * chore: format to keep ci happy * fix: buffer move_cursor * fix: quake client * feat: GameSpy 1 small optimization * fix: incomplete gamespy 3 fix * fix: gamespy 3 fix * fix: minecraft java * fix: minecraft bedrock * feat: update the CHANGELOG to mention the buffer rewrite and thank @cainthebest for it * fix: minecraft legacy 1.6 --------- Co-authored-by: CosminPerRam <cosmin.p@live.com>
This commit is contained in:
parent
a8342296d6
commit
66cc39eb26
20 changed files with 859 additions and 568 deletions
|
|
@ -16,6 +16,9 @@ Games:
|
|||
- [Just Cause 2: Multiplayer](https://store.steampowered.com/app/259080/Just_Cause_2_Multiplayer_Mod/) support.
|
||||
- [Warsow](https://warsow.net/) support.
|
||||
|
||||
Internal:
|
||||
- Buffer reader rewrite, resulting in more data checks and better code quality (thanks [@cainthebest](https://github.com/cainthebest)).
|
||||
|
||||
### Breaking...
|
||||
Protocols:
|
||||
- Quake 2: Renamed the players frags field to score to be more inline with the other protocols.
|
||||
|
|
|
|||
548
src/buffer.rs
Normal file
548
src/buffer.rs
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
use crate::GDError::{PacketBad, PacketUnderflow};
|
||||
use crate::GDResult;
|
||||
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||
use std::{convert::TryInto, marker::PhantomData};
|
||||
|
||||
/// A struct representing a buffer with a specific byte order.
|
||||
///
|
||||
/// It's comprised of a byte slice that it reads from, a cursor to keep track of
|
||||
/// the current position within the byte slice, and a `PhantomData` marker to
|
||||
/// bind it to a specific byte order (BigEndian or LittleEndian).
|
||||
///
|
||||
/// The byte order is defined by the `B: ByteOrder` generic parameter.
|
||||
pub(crate) struct Buffer<'a, B: ByteOrder> {
|
||||
/// The byte slice that the buffer reads from.
|
||||
data: &'a [u8],
|
||||
/// The cursor marking our current position in the buffer.
|
||||
cursor: usize,
|
||||
/// A phantom field used to bind the `Buffer` to a specific `ByteOrder`.
|
||||
_marker: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<'a, B: ByteOrder> Buffer<'a, B> {
|
||||
/// Creates and returns a new `Buffer` with the given data.
|
||||
///
|
||||
/// The cursor is set to the start of the buffer (position 0) upon
|
||||
/// initialization.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - A byte slice that the buffer will read from.
|
||||
pub(crate) fn new(data: &'a [u8]) -> Self {
|
||||
Self {
|
||||
data,
|
||||
cursor: 0,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn current_position(&self) -> usize { self.cursor }
|
||||
|
||||
/// Returns the length of the remaining bytes from the current cursor
|
||||
/// position.
|
||||
pub(crate) fn remaining_length(&self) -> usize { self.data.len() - self.cursor }
|
||||
|
||||
/// Returns the length of the buffer data.
|
||||
pub(crate) fn data_length(&self) -> usize { self.data.len() }
|
||||
|
||||
// Added for legacy support just for the refactoring
|
||||
// Not Tested
|
||||
pub(crate) fn remaining_bytes(&self) -> &[u8] { &self.data[self.cursor ..] }
|
||||
|
||||
/// Moves the cursor forward or backward by a specified offset.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `offset` - The amount to move the cursor. Use a negative value to move
|
||||
/// backwards.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `BufferError` if the attempted move would position the cursor
|
||||
/// out of bounds.
|
||||
pub(crate) fn move_cursor(&mut self, offset: isize) -> GDResult<()> {
|
||||
// Compute the new cursor position by adding the offset to the current cursor
|
||||
// position. The checked_add method is used for safe addition,
|
||||
// preventing overflow and underflow.
|
||||
let new_cursor = (self.cursor as isize).checked_add(offset);
|
||||
|
||||
match new_cursor {
|
||||
// If the addition was not successful (i.e., it resulted in an overflow or underflow),
|
||||
// return an error indicating that the cursor is out of bounds.
|
||||
None => Err(PacketBad),
|
||||
|
||||
// If the new cursor position is either less than zero (i.e., before the start of the buffer)
|
||||
// or greater than the remaining length of the buffer (i.e., past the end of the buffer),
|
||||
// return an error indicating that the cursor is out of bounds.
|
||||
Some(x) if x < 0 || x as usize > self.data_length() => Err(PacketBad),
|
||||
|
||||
// If the new cursor position is within the bounds of the buffer, update the cursor
|
||||
// position and return Ok.
|
||||
Some(x) => {
|
||||
self.cursor = x as usize;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a value of type `T` from the buffer, and advances the cursor by
|
||||
/// the size of `T`.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `T` - The type of value to be read from the buffer. This type must
|
||||
/// implement the `BufferRead` trait with the same byte order as the
|
||||
/// buffer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `BufferError` if there is not enough data remaining in the
|
||||
/// buffer to read a value of type `T`.
|
||||
pub(crate) fn read<T: Sized + BufferRead<B>>(&mut self) -> GDResult<T> {
|
||||
// Get the size of `T` in bytes.
|
||||
let size = std::mem::size_of::<T>();
|
||||
// Calculate remaining length of the buffer.
|
||||
let remaining = self.remaining_length();
|
||||
|
||||
// If the size of `T` is larger than the remaining length, return an error
|
||||
// because we don't have enough data left to read.
|
||||
if size > remaining {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
// Slice the data array from the current cursor position for `size` amount of
|
||||
// bytes.
|
||||
let bytes = &self.data[self.cursor .. self.cursor + size];
|
||||
|
||||
// Move the cursor forward by `size`.
|
||||
self.cursor += size;
|
||||
|
||||
// Use the `read_from_buffer` function of the `BufferRead` implementation for
|
||||
// `T` to convert the bytes into an instance of `T`.
|
||||
T::read_from_buffer(bytes)
|
||||
}
|
||||
|
||||
/// Reads a string from the buffer using a specified `StringDecoder`, until
|
||||
/// an optional delimiter.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `D` - The type of string decoder to use. This type must implement the
|
||||
/// `StringDecoder` trait with the same byte order as the buffer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `until` - An optional delimiter. If provided, the method will read
|
||||
/// until this
|
||||
/// delimiter is encountered. If not provided, the method will read until
|
||||
/// the default delimiter of the decoder.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `BufferError` if there is an error decoding the string.
|
||||
pub(crate) fn read_string<D: StringDecoder>(&mut self, until: Option<D::Delimiter>) -> GDResult<String> {
|
||||
// Slice the data array from the current cursor position to the end.
|
||||
let data_slice = &self.data[self.cursor ..];
|
||||
|
||||
// Use the provided delimiter if one was given, or default to the
|
||||
// delimiter specified by the StringDecoder.
|
||||
let delimiter = until.unwrap_or(D::DELIMITER);
|
||||
|
||||
// Invoke the decode_string function of the provided StringDecoder,
|
||||
// passing in the remaining data slice, the mutable reference to the
|
||||
// cursor, and the delimiter.
|
||||
let result = D::decode_string(data_slice, &mut self.cursor, delimiter)?;
|
||||
|
||||
// If decoding was successful, return the decoded string. The cursor
|
||||
// position has been updated within the decode_string call to reflect
|
||||
// the new position after reading.
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that provides an interface to switch endianness.
|
||||
///
|
||||
/// The trait `SwitchEndian` is used for types that have a specific
|
||||
/// byte order (endianness) and can switch to another byte order.
|
||||
/// The type of the switched endianness is determined by the associated
|
||||
/// type `Output`.
|
||||
///
|
||||
/// The associated type `Output` must implement the `ByteOrder` trait.
|
||||
pub(crate) trait SwitchEndian {
|
||||
type Output: ByteOrder;
|
||||
}
|
||||
|
||||
/// An implementation of `SwitchEndian` for `LittleEndian`.
|
||||
///
|
||||
/// The switched endianness type is `BigEndian`.
|
||||
impl SwitchEndian for LittleEndian {
|
||||
type Output = BigEndian;
|
||||
}
|
||||
|
||||
/// An implementation of `SwitchEndian` for `BigEndian`.
|
||||
///
|
||||
/// The switched endianness type is `LittleEndian`.
|
||||
impl SwitchEndian for BigEndian {
|
||||
type Output = LittleEndian;
|
||||
}
|
||||
|
||||
impl<'a, B: SwitchEndian + ByteOrder> Buffer<'a, B> {
|
||||
/// Switches the byte order of a chunk in the buffer.
|
||||
///
|
||||
/// This method consumes the buffer and returns a new buffer
|
||||
/// with a chunk of the original buffer's data, starting from the
|
||||
/// original cursor position and of the given size, where the byte
|
||||
/// order is switched according to the implementation
|
||||
/// of `SwitchEndian` for `B`.
|
||||
///
|
||||
/// Note: The method also advances the cursor of the original buffer
|
||||
/// by `size`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `size`: The size of the chunk to be taken from the original buffer.
|
||||
pub(crate) fn switch_endian_chunk(&mut self, size: usize) -> GDResult<Buffer<'a, B::Output>> {
|
||||
let old_cursor = self.cursor;
|
||||
self.move_cursor(size as isize)?;
|
||||
|
||||
Ok(Buffer {
|
||||
data: &self.data[old_cursor .. old_cursor + size],
|
||||
cursor: 0,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait defining a protocol for reading values of a certain type from a
|
||||
/// buffer.
|
||||
///
|
||||
/// Implementors of this trait provide a method for reading their type from a
|
||||
/// byte buffer with a specific byte order.
|
||||
pub(crate) trait BufferRead<B: ByteOrder>: Sized {
|
||||
fn read_from_buffer(data: &[u8]) -> GDResult<Self>;
|
||||
}
|
||||
|
||||
/// Macro to implement the `BufferRead` trait for byte types.
|
||||
///
|
||||
/// This macro generates an implementation of the `BufferRead` trait for a
|
||||
/// specified byte type. The implementation will read a single byte from the
|
||||
/// buffer and convert it to the target type using the provided map function.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `$type` - The target type to implement `BufferRead` for.
|
||||
/// * `$map_func` - The function to map a byte to the target type.
|
||||
macro_rules! impl_buffer_read_byte {
|
||||
($type:ty, $map_func:expr) => {
|
||||
impl<B: ByteOrder> BufferRead<B> for $type {
|
||||
fn read_from_buffer(data: &[u8]) -> GDResult<Self> {
|
||||
// Use the `first` method to get the first byte from the data array.
|
||||
data.first()
|
||||
// Apply the $map_func function to convert the raw byte to the $type.
|
||||
.map($map_func)
|
||||
// If the data array is empty (and thus `first` returns None),
|
||||
// `ok_or_else` will return a BufferError.
|
||||
.ok_or_else(|| PacketBad)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to implement the `BufferRead` trait for multi-byte types.
|
||||
///
|
||||
/// This macro generates an implementation of the `BufferRead` trait for a
|
||||
/// specified multi-byte type. The implementation will read the appropriate
|
||||
/// number of bytes from the buffer and convert them to the target type using
|
||||
/// the provided read function.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `$type` - The target type to implement `BufferRead` for.
|
||||
/// * `$read_func` - The function to read the bytes into the target type.
|
||||
macro_rules! impl_buffer_read {
|
||||
($type:ty, $read_func:ident) => {
|
||||
impl<B: ByteOrder> BufferRead<B> for $type {
|
||||
fn read_from_buffer(data: &[u8]) -> GDResult<Self> {
|
||||
// Convert the byte slice into an array of the appropriate type.
|
||||
let array = data.try_into().map_err(|_| {
|
||||
// If conversion fails, return an error indicating the required and provided
|
||||
// lengths.
|
||||
PacketBad
|
||||
})?;
|
||||
|
||||
// Use the provided function to read the data from the array into the given
|
||||
// type.
|
||||
Ok(B::$read_func(array))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_buffer_read_byte!(u8, |&b| b);
|
||||
impl_buffer_read_byte!(i8, |&b| b as i8);
|
||||
|
||||
impl_buffer_read!(u16, read_u16);
|
||||
impl_buffer_read!(i16, read_i16);
|
||||
impl_buffer_read!(u32, read_u32);
|
||||
impl_buffer_read!(i32, read_i32);
|
||||
impl_buffer_read!(u64, read_u64);
|
||||
impl_buffer_read!(i64, read_i64);
|
||||
impl_buffer_read!(f32, read_f32);
|
||||
impl_buffer_read!(f64, read_f64);
|
||||
|
||||
/// A trait defining a protocol for decoding strings from a buffer.
|
||||
///
|
||||
/// This trait should be implemented by types that can decode strings from a
|
||||
/// byte buffer with a specific byte order and delimiter.
|
||||
pub(crate) trait StringDecoder {
|
||||
/// The type of the delimiter used by the decoder.
|
||||
type Delimiter: AsRef<[u8]>;
|
||||
|
||||
/// The default delimiter used by the decoder.
|
||||
const DELIMITER: Self::Delimiter;
|
||||
|
||||
/// Decodes a string from the provided byte slice, and updates the cursor
|
||||
/// position accordingly.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The byte slice to decode the string from.
|
||||
/// * `cursor` - The current position in the byte slice.
|
||||
/// * `delimiter` - The delimiter to use for decoding the string.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `BufferError` if there is an error decoding the string.
|
||||
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String>;
|
||||
}
|
||||
|
||||
/// A decoder for UTF-8 encoded strings.
|
||||
///
|
||||
/// This decoder uses a single null byte (`0x00`) as the default delimiter.
|
||||
pub(crate) struct Utf8Decoder;
|
||||
|
||||
impl StringDecoder for Utf8Decoder {
|
||||
type Delimiter = [u8; 1];
|
||||
|
||||
const DELIMITER: Self::Delimiter = [0x00];
|
||||
|
||||
/// Decodes a UTF-8 string from the given data, updating the cursor position
|
||||
/// accordingly.
|
||||
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String> {
|
||||
// Find the position of the delimiter in the data. If the delimiter is not
|
||||
// found, the length of the data is returned.
|
||||
let position = data
|
||||
// Create an iterator over the data.
|
||||
.iter()
|
||||
// Find the position of the delimiter
|
||||
.position(|&b| b == delimiter.as_ref()[0])
|
||||
// If the delimiter is not found, use the whole data slice.
|
||||
.unwrap_or(data.len());
|
||||
|
||||
// Convert the data until the found position into a UTF-8 string.
|
||||
let result = std::str::from_utf8(
|
||||
// Take a slice of data until the position.
|
||||
&data[.. position]
|
||||
)
|
||||
// If the data cannot be converted into a UTF-8 string, return an error
|
||||
.map_err(|_| PacketBad)?
|
||||
// Convert the resulting &str into a String
|
||||
.to_owned();
|
||||
|
||||
// Update the cursor position
|
||||
// The +1 is to skip the delimiter
|
||||
*cursor += position + 1;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// A decoder for UTF-16 encoded strings.
|
||||
///
|
||||
/// This decoder uses a pair of null bytes (`0x00, 0x00`) as the default
|
||||
/// delimiter.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `B` - The byte order to use when decoding the string.
|
||||
pub(crate) struct Utf16Decoder<B: ByteOrder> {
|
||||
_marker: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<B: ByteOrder> StringDecoder for Utf16Decoder<B> {
|
||||
type Delimiter = [u8; 2];
|
||||
|
||||
const DELIMITER: Self::Delimiter = [0x00, 0x00];
|
||||
|
||||
/// Decodes a UTF-16 string from the given data, updating the cursor
|
||||
/// position accordingly.
|
||||
fn decode_string(data: &[u8], cursor: &mut usize, delimiter: Self::Delimiter) -> GDResult<String> {
|
||||
// Try to find the position of the delimiter in the data
|
||||
let position = data
|
||||
// Split the data into 2-byte chunks (as UTF-16 uses 2 bytes per character)
|
||||
.chunks_exact(2)
|
||||
// Find the position of the delimiter
|
||||
.position(|chunk| chunk == delimiter.as_ref())
|
||||
// If the delimiter is not found, use the whole data, otherwise use the position of the delimiter
|
||||
.map_or(data.len(), |pos| pos * 2);
|
||||
|
||||
// Create a buffer of u16 values to hold the decoded characters
|
||||
let mut paired_buf: Vec<u16> = vec![0; position / 2];
|
||||
|
||||
// Decode the data into the buffer
|
||||
B::read_u16_into(&data[.. position], &mut paired_buf);
|
||||
|
||||
// Convert the buffer of u16 values into a String
|
||||
let result = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?;
|
||||
|
||||
// Update the cursor position
|
||||
// The +2 accounts for the delimiter
|
||||
*cursor += position + 2;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use byteorder::BigEndian;
|
||||
|
||||
#[test]
|
||||
fn test_new_buffer() {
|
||||
let data: &[u8] = &[1, 2, 3, 4];
|
||||
let buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
assert_eq!(buffer.data, data);
|
||||
assert_eq!(buffer.cursor, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remaining_length() {
|
||||
let data: &[u8] = &[1, 2, 3, 4];
|
||||
let mut buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
assert_eq!(buffer.remaining_length(), 4);
|
||||
|
||||
buffer.cursor = 2;
|
||||
assert_eq!(buffer.remaining_length(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_cursor() {
|
||||
let data: &[u8] = &[1, 2, 3, 4];
|
||||
let mut buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
// Test moving forward
|
||||
assert!(buffer.move_cursor(2).is_ok());
|
||||
assert_eq!(buffer.cursor, 2);
|
||||
|
||||
// Test moving backward
|
||||
assert!(buffer.move_cursor(-1).is_ok());
|
||||
assert_eq!(buffer.cursor, 1);
|
||||
|
||||
// Test moving beyond data limits
|
||||
assert!(buffer.move_cursor(5).is_err());
|
||||
assert!(buffer.move_cursor(-2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_endian_chunk_le_be() {
|
||||
let data = [0x01, 0x02, 0x03, 0x04];
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data[..]);
|
||||
|
||||
let switched_buffer = buffer.switch_endian_chunk(2).unwrap();
|
||||
|
||||
assert_eq!(switched_buffer.data, [0x01, 0x02]);
|
||||
assert_eq!(switched_buffer.cursor, 0);
|
||||
|
||||
assert_eq!(buffer.remaining_bytes(), [0x03, 0x04]);
|
||||
assert_eq!(buffer.cursor, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_endian_chunk_be_le() {
|
||||
let data = [0x01, 0x02, 0x03, 0x04];
|
||||
let mut buffer = Buffer::<BigEndian>::new(&data[..]);
|
||||
|
||||
let switched_buffer = buffer.switch_endian_chunk(2).unwrap();
|
||||
|
||||
assert_eq!(switched_buffer.data, [0x01, 0x02]);
|
||||
assert_eq!(switched_buffer.cursor, 0);
|
||||
|
||||
assert_eq!(buffer.remaining_bytes(), [0x03, 0x04]);
|
||||
assert_eq!(buffer.cursor, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_read_u8() {
|
||||
let data: &[u8] = &[1, 2, 3, 4];
|
||||
let mut buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
let result: Result<u8, _> = buffer.read();
|
||||
assert_eq!(result.unwrap(), 1);
|
||||
assert_eq!(buffer.cursor, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_read_u16() {
|
||||
let data: &[u8] = &[1, 2, 3, 4];
|
||||
let mut buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
let result: Result<u16, _> = buffer.read();
|
||||
assert_eq!(result.unwrap(), 0x0201);
|
||||
assert_eq!(buffer.cursor, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_read_u16_big_endian() {
|
||||
let data: &[u8] = &[1, 2, 3, 4];
|
||||
let mut buffer = Buffer::<BigEndian>::new(data);
|
||||
|
||||
let result: Result<u16, _> = buffer.read();
|
||||
assert_eq!(result.unwrap(), 0x0102);
|
||||
assert_eq!(buffer.cursor, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_string_utf8() {
|
||||
let data: &[u8] = b"Hello\0World\0";
|
||||
let mut cursor = 0;
|
||||
let delimiter = [0x00];
|
||||
|
||||
let result = Utf8Decoder::decode_string(data, &mut cursor, delimiter);
|
||||
assert_eq!(result.unwrap(), "Hello");
|
||||
assert_eq!(cursor, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_string_utf16_le() {
|
||||
let data: &[u8] = &[0x48, 0x00, 0x65, 0x00, 0x00, 0x00];
|
||||
let mut cursor = 0;
|
||||
let delimiter = [0x00, 0x00];
|
||||
|
||||
let result = Utf16Decoder::<LittleEndian>::decode_string(data, &mut cursor, delimiter);
|
||||
assert_eq!(result.unwrap(), "He");
|
||||
assert_eq!(cursor, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_string_utf16_be() {
|
||||
let data: &[u8] = &[0x00, 0x48, 0x00, 0x65, 0x00, 0x00];
|
||||
let mut cursor = 0;
|
||||
let delimiter = [0x00, 0x00];
|
||||
|
||||
let result = Utf16Decoder::<BigEndian>::decode_string(data, &mut cursor, delimiter);
|
||||
assert_eq!(result.unwrap(), "He");
|
||||
assert_eq!(cursor, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_underflow_error() {
|
||||
let data: &[u8] = &[1, 2];
|
||||
let mut buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
let result: Result<u32, _> = buffer.read();
|
||||
assert_eq!(result.unwrap_err(), PacketUnderflow);
|
||||
}
|
||||
}
|
||||
308
src/bufferer.rs
308
src/bufferer.rs
|
|
@ -1,308 +0,0 @@
|
|||
use crate::{
|
||||
GDError::{PacketBad, PacketUnderflow},
|
||||
GDResult,
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||
|
||||
pub enum Endianess {
|
||||
Little,
|
||||
Big,
|
||||
}
|
||||
|
||||
pub struct Bufferer {
|
||||
data: Vec<u8>,
|
||||
endianess: Endianess,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl Bufferer {
|
||||
pub fn new_with_data(endianess: Endianess, data: &[u8]) -> Self {
|
||||
Bufferer {
|
||||
data: data.to_vec(),
|
||||
endianess,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_size(&self, by: usize) -> bool { by > self.remaining_length() }
|
||||
|
||||
pub fn get_u8(&mut self) -> GDResult<u8> {
|
||||
if self.check_size(1) {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let value = self.data[self.position];
|
||||
self.move_position_ahead(1);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn get_u16(&mut self) -> GDResult<u16> {
|
||||
if self.check_size(2) {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let value = match self.endianess {
|
||||
Endianess::Little => LittleEndian::read_u16(self.remaining_data()),
|
||||
Endianess::Big => BigEndian::read_u16(self.remaining_data()),
|
||||
};
|
||||
|
||||
self.move_position_ahead(2);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn get_u32(&mut self) -> GDResult<u32> {
|
||||
if self.check_size(4) {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let value = match self.endianess {
|
||||
Endianess::Little => LittleEndian::read_u32(self.remaining_data()),
|
||||
Endianess::Big => BigEndian::read_u32(self.remaining_data()),
|
||||
};
|
||||
|
||||
self.move_position_ahead(4);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn get_f32(&mut self) -> GDResult<f32> {
|
||||
if self.check_size(4) {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let value = match self.endianess {
|
||||
Endianess::Little => LittleEndian::read_f32(self.remaining_data()),
|
||||
Endianess::Big => BigEndian::read_f32(self.remaining_data()),
|
||||
};
|
||||
|
||||
self.move_position_ahead(4);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn get_u64(&mut self) -> GDResult<u64> {
|
||||
if self.check_size(8) {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let value = match self.endianess {
|
||||
Endianess::Little => LittleEndian::read_u64(self.remaining_data()),
|
||||
Endianess::Big => BigEndian::read_u64(self.remaining_data()),
|
||||
};
|
||||
|
||||
self.move_position_ahead(8);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn get_string_utf8_until(&mut self, until: u8) -> GDResult<String> {
|
||||
let sub_buf = self.remaining_data();
|
||||
if sub_buf.is_empty() {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let first_null_position = sub_buf.iter().position(|&x| x == until).ok_or(PacketBad)?;
|
||||
let value = std::str::from_utf8(&sub_buf[.. first_null_position])
|
||||
.map_err(|_| PacketBad)?
|
||||
.to_string();
|
||||
|
||||
self.move_position_ahead(value.len() + 1);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn get_string_utf8(&mut self) -> GDResult<String> { self.get_string_utf8_until(0) }
|
||||
|
||||
pub fn get_string_utf8_newline(&mut self) -> GDResult<String> { self.get_string_utf8_until(10) }
|
||||
|
||||
pub fn get_string_utf8_optional(&mut self) -> GDResult<String> {
|
||||
match self.get_string_utf8() {
|
||||
Ok(data) => Ok(data),
|
||||
Err(e) => {
|
||||
match e {
|
||||
PacketUnderflow => Ok(String::new()),
|
||||
x => Err(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_string_utf8_unended(&mut self) -> GDResult<String> {
|
||||
let sub_buf = self.remaining_data();
|
||||
if sub_buf.is_empty() {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let value = std::str::from_utf8(sub_buf)
|
||||
.map_err(|_| PacketBad)?
|
||||
.to_string();
|
||||
|
||||
self.move_position_ahead(value.len());
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn get_string_utf16(&mut self) -> GDResult<String> {
|
||||
let sub_buf = self.remaining_data();
|
||||
if sub_buf.is_empty() {
|
||||
return Err(PacketUnderflow);
|
||||
}
|
||||
|
||||
let paired_buf: Vec<u16> = sub_buf
|
||||
.chunks_exact(2)
|
||||
.map(|pair| {
|
||||
match self.endianess {
|
||||
Endianess::Little => LittleEndian::read_u16(pair),
|
||||
Endianess::Big => BigEndian::read_u16(pair),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let value = String::from_utf16(&paired_buf).map_err(|_| PacketBad)?;
|
||||
|
||||
self.move_position_ahead(value.len() * 2);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn move_position_ahead(&mut self, by: usize) { self.position += by; }
|
||||
|
||||
pub fn move_position_backward(&mut self, by: usize) { self.position -= by; }
|
||||
|
||||
pub fn remaining_data(&self) -> &[u8] { &self.data[self.position ..] }
|
||||
|
||||
pub fn remaining_length(&self) -> usize { self.data.len() - self.position }
|
||||
|
||||
pub fn is_remaining_empty(&self) -> bool { self.remaining_length() == 0 }
|
||||
|
||||
pub fn as_endianess(&self, endianess: Endianess) -> Self {
|
||||
Bufferer {
|
||||
data: self.data.clone(),
|
||||
endianess,
|
||||
position: self.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_u8() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72]);
|
||||
|
||||
assert_eq!(buffer.get_u8().unwrap(), 72);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u8().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_u16_le() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 79]);
|
||||
|
||||
assert_eq!(buffer.get_u16().unwrap(), 20296);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u16().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_u16_be() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[29, 72]);
|
||||
|
||||
assert_eq!(buffer.get_u16().unwrap(), 7496);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u16().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_u32_le() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 100]);
|
||||
|
||||
assert_eq!(buffer.get_u32().unwrap(), 1686117704);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u32().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_u32_be() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 100]);
|
||||
|
||||
assert_eq!(buffer.get_u32().unwrap(), 1209892964);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u32().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_f32_le() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 100]);
|
||||
|
||||
assert_eq!(buffer.get_f32().unwrap(), 1.8906345e22);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_f32().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_f32_be() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 100]);
|
||||
|
||||
assert_eq!(buffer.get_f32().unwrap(), 161281.56);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_f32().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_u64_le() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 29, 128, 99, 69, 4, 2, 0]);
|
||||
|
||||
assert_eq!(buffer.get_u64().unwrap(), 567646022016328);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u64().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_u64_be() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &[72, 29, 128, 99, 69, 4, 2, 0]);
|
||||
|
||||
assert_eq!(buffer.get_u64().unwrap(), 5196450708903428608);
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_u64().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_string_utf8() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 101, 108, 108, 111, 0, 72]);
|
||||
|
||||
assert_eq!(buffer.get_string_utf8().unwrap(), "Hello");
|
||||
assert_eq!(buffer.remaining_length(), 1);
|
||||
assert!(buffer.get_string_utf8().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_string_utf8_unended() {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &[72, 101, 108, 108, 111]);
|
||||
|
||||
assert_eq!(buffer.get_string_utf8_unended().unwrap(), "Hello");
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_string_utf8_unended().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_string_utf16_le() {
|
||||
let mut buffer = Bufferer::new_with_data(
|
||||
Endianess::Little,
|
||||
&[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00],
|
||||
);
|
||||
|
||||
assert_eq!(buffer.get_string_utf16().unwrap(), "Hello");
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_string_utf16().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_string_utf16_be() {
|
||||
let mut buffer = Bufferer::new_with_data(
|
||||
Endianess::Big,
|
||||
&[0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f],
|
||||
);
|
||||
|
||||
assert_eq!(buffer.get_string_utf16().unwrap(), "Hello");
|
||||
assert_eq!(buffer.remaining_length(), 0);
|
||||
assert!(buffer.get_string_utf16().is_err());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::types::{CommonResponse, TimeoutSettings};
|
||||
use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::GDResult;
|
||||
use byteorder::LittleEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
|
@ -68,31 +70,33 @@ pub fn query_with_timeout(
|
|||
&SocketAddr::new(*address, port.unwrap_or(5478)),
|
||||
timeout_settings,
|
||||
)?;
|
||||
let mut buffer = client.get_request_data(
|
||||
let data = client.get_request_data(
|
||||
&Engine::GoldSrc(true),
|
||||
0,
|
||||
0x46,
|
||||
String::from("LSQ").into_bytes(),
|
||||
)?;
|
||||
|
||||
let protocol = buffer.get_u8()?;
|
||||
let name = buffer.get_string_utf8()?;
|
||||
let map = buffer.get_string_utf8()?;
|
||||
let active_mod = buffer.get_string_utf8()?;
|
||||
let game_mode = buffer.get_string_utf8()?;
|
||||
let description = buffer.get_string_utf8()?;
|
||||
let version = buffer.get_string_utf8()?;
|
||||
buffer.move_position_ahead(2);
|
||||
let players_online = buffer.get_u8()?;
|
||||
let players_maximum = buffer.get_u8()?;
|
||||
let server_type = Server::from_gldsrc(buffer.get_u8()?)?;
|
||||
let environment_type = Environment::from_gldsrc(buffer.get_u8()?)?;
|
||||
let has_password = buffer.get_u8()? == 1;
|
||||
let vac_secured = buffer.get_u8()? == 1;
|
||||
buffer.move_position_ahead(1); //average fps
|
||||
let round = buffer.get_u8()?;
|
||||
let rounds_maximum = buffer.get_u8()?;
|
||||
let time_left = buffer.get_u16()?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let protocol = buffer.read::<u8>()?;
|
||||
let name = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let map = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let active_mod = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let game_mode = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let description = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let version = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
buffer.move_cursor(2)?;
|
||||
let players_online = buffer.read::<u8>()?;
|
||||
let players_maximum = buffer.read::<u8>()?;
|
||||
let server_type = Server::from_gldsrc(buffer.read::<u8>()?)?;
|
||||
let environment_type = Environment::from_gldsrc(buffer.read::<u8>()?)?;
|
||||
let has_password = buffer.read::<u8>()? == 1;
|
||||
let vac_secured = buffer.read::<u8>()? == 1;
|
||||
buffer.move_cursor(1)?; //average fps
|
||||
let round = buffer.read::<u8>()?;
|
||||
let rounds_maximum = buffer.read::<u8>()?;
|
||||
let time_left = buffer.read::<u16>()?;
|
||||
|
||||
Ok(Response {
|
||||
protocol,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer, TimeoutSettings};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::{GDError, GDResult};
|
||||
use byteorder::BigEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
|
@ -59,16 +60,16 @@ impl CommonResponse for Response {
|
|||
}
|
||||
|
||||
fn parse_players_and_teams(packet: Vec<u8>) -> GDResult<Vec<Player>> {
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Big, &packet);
|
||||
let mut buf = Buffer::<BigEndian>::new(&packet);
|
||||
|
||||
let count = buf.get_u16()?;
|
||||
let count = buf.read::<u16>()?;
|
||||
let mut players = Vec::with_capacity(count as usize);
|
||||
|
||||
while !buf.is_remaining_empty() {
|
||||
while buf.remaining_length() != 0 {
|
||||
players.push(Player {
|
||||
name: buf.get_string_utf8()?,
|
||||
steam_id: buf.get_string_utf8()?,
|
||||
ping: buf.get_u16()?,
|
||||
name: buf.read_string::<Utf8Decoder>(None)?,
|
||||
steam_id: buf.read_string::<Utf8Decoder>(None)?,
|
||||
ping: buf.read::<u16>()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ pub mod protocols;
|
|||
#[cfg(feature = "services")]
|
||||
pub mod services;
|
||||
|
||||
mod bufferer;
|
||||
mod buffer;
|
||||
mod socket;
|
||||
mod utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use byteorder::LittleEndian;
|
||||
|
||||
use crate::buffer::Utf8Decoder;
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::Buffer,
|
||||
protocols::{
|
||||
gamespy::one::{Player, Response},
|
||||
types::TimeoutSettings,
|
||||
|
|
@ -29,9 +32,9 @@ fn get_server_values(
|
|||
|
||||
while !is_finished {
|
||||
let data = socket.receive(None)?;
|
||||
let mut bufferer = Bufferer::new_with_data(Endianess::Little, &data);
|
||||
let mut bufferer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let mut as_string = bufferer.get_string_utf8_unended()?;
|
||||
let mut as_string = bufferer.read_string::<Utf8Decoder>(None)?;
|
||||
as_string.remove(0);
|
||||
|
||||
let splited: Vec<String> = as_string.split('\\').map(str::to_string).collect();
|
||||
|
|
@ -47,8 +50,7 @@ fn get_server_values(
|
|||
server_values.insert(key, value);
|
||||
}
|
||||
|
||||
is_finished = server_values.contains_key("final");
|
||||
server_values.remove("final");
|
||||
is_finished = server_values.remove("final").is_some();
|
||||
|
||||
let query_data = server_values.get("queryid");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use byteorder::{BigEndian, LittleEndian};
|
||||
|
||||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::protocols::gamespy::three::{Player, Response, Team};
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
|
|
@ -73,19 +75,19 @@ impl GameSpy3 {
|
|||
})
|
||||
}
|
||||
|
||||
fn receive(&mut self, size: Option<usize>, kind: u8) -> GDResult<Bufferer> {
|
||||
fn receive(&mut self, size: Option<usize>, kind: u8) -> GDResult<Vec<u8>> {
|
||||
let received = self.socket.receive(size.or(Some(PACKET_SIZE)))?;
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Big, &received);
|
||||
let mut buf = Buffer::<BigEndian>::new(&received);
|
||||
|
||||
if buf.get_u8()? != kind {
|
||||
if buf.read::<u8>()? != kind {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
if buf.get_u32()? != THIS_SESSION_ID {
|
||||
if buf.read::<u32>()? != THIS_SESSION_ID {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
Ok(buf.remaining_bytes().to_vec())
|
||||
}
|
||||
|
||||
fn make_initial_handshake(&mut self) -> GDResult<Option<i32>> {
|
||||
|
|
@ -100,9 +102,10 @@ impl GameSpy3 {
|
|||
.to_bytes(),
|
||||
)?;
|
||||
|
||||
let mut buf = self.receive(Some(16), 9)?;
|
||||
let data = self.receive(Some(16), 9)?;
|
||||
let mut buf = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let challenge_as_string = buf.get_string_utf8()?;
|
||||
let challenge_as_string = buf.read_string::<Utf8Decoder>(None)?;
|
||||
let challenge = challenge_as_string
|
||||
.parse()
|
||||
.map_err(|_| GDError::TypeParse)?;
|
||||
|
|
@ -135,21 +138,22 @@ impl GameSpy3 {
|
|||
let mut expected_number_of_packets: Option<usize> = None;
|
||||
|
||||
while expected_number_of_packets.is_none() || values.len() != expected_number_of_packets.unwrap() {
|
||||
let mut buf = self.receive(None, 0)?;
|
||||
let received_data = self.receive(None, 0)?;
|
||||
let mut buf = Buffer::<BigEndian>::new(&received_data);
|
||||
|
||||
if self.single_packets {
|
||||
buf.move_position_ahead(11);
|
||||
return Ok(vec![buf.remaining_data().to_vec()]);
|
||||
buf.move_cursor(11)?;
|
||||
return Ok(vec![buf.remaining_bytes().to_vec()]);
|
||||
}
|
||||
|
||||
if buf.get_string_utf8()? != "splitnum" {
|
||||
if buf.read_string::<Utf8Decoder>(None)? != "splitnum" {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
let id = buf.get_u8()?;
|
||||
let id = buf.read::<u8>()?;
|
||||
let is_last = (id & 0x80) > 0;
|
||||
let packet_id = (id & 0x7f) as usize;
|
||||
buf.move_position_ahead(1); //unknown byte regarding packet no.
|
||||
buf.move_cursor(1)?; //unknown byte regarding packet no.
|
||||
|
||||
if is_last {
|
||||
expected_number_of_packets = Some(packet_id + 1);
|
||||
|
|
@ -159,7 +163,7 @@ impl GameSpy3 {
|
|||
values.push(Vec::new());
|
||||
}
|
||||
|
||||
values[packet_id] = buf.remaining_data().to_vec();
|
||||
values[packet_id] = buf.remaining_bytes().to_vec();
|
||||
}
|
||||
|
||||
if values.iter().any(|v| v.is_empty()) {
|
||||
|
|
@ -173,19 +177,19 @@ impl GameSpy3 {
|
|||
pub(crate) fn data_to_map(packet: &[u8]) -> GDResult<(HashMap<String, String>, Vec<u8>)> {
|
||||
let mut vars = HashMap::new();
|
||||
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Big, packet);
|
||||
while !buf.is_remaining_empty() {
|
||||
let key = buf.get_string_utf8()?;
|
||||
let mut buf = Buffer::<BigEndian>::new(packet);
|
||||
while buf.remaining_length() != 0 {
|
||||
let key = buf.read_string::<Utf8Decoder>(None)?;
|
||||
if key.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let value = buf.get_string_utf8_optional()?;
|
||||
let value = buf.read_string::<Utf8Decoder>(None)?;
|
||||
|
||||
vars.insert(key, value);
|
||||
}
|
||||
|
||||
Ok((vars, buf.remaining_data().to_vec()))
|
||||
Ok((vars, buf.remaining_bytes().to_vec()))
|
||||
}
|
||||
|
||||
/// If there are parsing problems using the `query` function, you can directly
|
||||
|
|
@ -212,16 +216,16 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
|
|||
let mut teams_data: Vec<HashMap<String, String>> = vec![HashMap::new()];
|
||||
|
||||
for packet in packets {
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Little, &packet);
|
||||
let mut buf = Buffer::<LittleEndian>::new(&packet);
|
||||
|
||||
while !buf.is_remaining_empty() {
|
||||
if buf.get_u8()? < 3 {
|
||||
while buf.remaining_length() != 0 {
|
||||
if buf.read::<u8>()? < 3 {
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.move_position_backward(1);
|
||||
buf.move_cursor(1)?;
|
||||
|
||||
let field = buf.get_string_utf8()?;
|
||||
let field = buf.read_string::<Utf8Decoder>(None)?;
|
||||
if field.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -248,15 +252,15 @@ fn parse_players_and_teams(packets: Vec<Vec<u8>>) -> GDResult<(Vec<Player>, Vec<
|
|||
}
|
||||
};
|
||||
|
||||
let mut offset = buf.get_u8()? as usize;
|
||||
let mut offset = buf.read::<u8>()? as usize;
|
||||
|
||||
let data = match field_type.is_none() {
|
||||
true => &mut players_data,
|
||||
false => &mut teams_data,
|
||||
};
|
||||
|
||||
while !buf.is_remaining_empty() {
|
||||
let item = buf.get_string_utf8()?;
|
||||
while buf.remaining_length() != 0 {
|
||||
let item = buf.read_string::<Utf8Decoder>(None)?;
|
||||
if item.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::gamespy::two::{Player, Response, Team};
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::socket::{Socket, UdpSocket};
|
||||
use crate::{GDError, GDResult};
|
||||
use byteorder::BigEndian;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
|
|
@ -28,12 +29,12 @@ macro_rules! table_extract_parse {
|
|||
};
|
||||
}
|
||||
|
||||
fn data_as_table(data: &mut Bufferer) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
|
||||
if data.get_u8()? != 0 {
|
||||
fn data_as_table(data: &mut Buffer<BigEndian>) -> GDResult<(HashMap<String, Vec<String>>, usize)> {
|
||||
if data.read::<u8>()? != 0 {
|
||||
Err(GDError::PacketBad)?
|
||||
}
|
||||
|
||||
let rows = data.get_u8()? as usize;
|
||||
let rows = data.read::<u8>()? as usize;
|
||||
|
||||
if rows == 0 {
|
||||
return Ok((HashMap::new(), 0));
|
||||
|
|
@ -41,21 +42,28 @@ fn data_as_table(data: &mut Bufferer) -> GDResult<(HashMap<String, Vec<String>>,
|
|||
|
||||
let mut column_heads = Vec::new();
|
||||
|
||||
let mut current_column = data.get_string_utf8()?;
|
||||
let mut current_column = data.read_string::<Utf8Decoder>(None)?;
|
||||
while !current_column.is_empty() {
|
||||
column_heads.push(current_column);
|
||||
current_column = data.get_string_utf8()?;
|
||||
current_column = data.read_string::<Utf8Decoder>(None)?
|
||||
}
|
||||
|
||||
let columns = column_heads.len();
|
||||
let mut table = HashMap::with_capacity(columns);
|
||||
for head in &column_heads {
|
||||
table.insert(head.clone(), Vec::new()); // TODO: This doesn't look good nor it is performant, fix later
|
||||
// TODO: This doesn't look good nor it is performant, fix later
|
||||
// By using &column_heads in the for loop instead of cloning column_heads, you
|
||||
// avoid creating an unnecessary copy. However, column_heads is a
|
||||
// Vec<String> and head is a &String (a reference to a string). Hence, to use
|
||||
// head as a key to the HashMap, we still need to call clone(). This is because
|
||||
// HashMap takes ownership of its keys and we cannot give it a reference to a
|
||||
// local variable (head) that will be dropped at the end of the function.
|
||||
table.insert(head.clone(), Vec::new());
|
||||
}
|
||||
|
||||
for _ in 0 .. rows {
|
||||
for column in column_heads.iter() {
|
||||
let value = data.get_string_utf8()?;
|
||||
let value = data.read_string::<Utf8Decoder>(None)?;
|
||||
table.get_mut(column).ok_or(GDError::PacketBad)?.push(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -71,36 +79,33 @@ impl GameSpy2 {
|
|||
Ok(Self { socket })
|
||||
}
|
||||
|
||||
fn request_data(&mut self) -> GDResult<Bufferer> {
|
||||
fn request_data(&mut self) -> GDResult<(Vec<u8>, usize)> {
|
||||
self.socket
|
||||
.send(&[0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF])?;
|
||||
|
||||
let received = self.socket.receive(None)?;
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Big, &received);
|
||||
|
||||
if buf.get_u8()? != 0 {
|
||||
let mut buf = Buffer::<BigEndian>::new(&received);
|
||||
if buf.read::<u8>()? != 0 || buf.read::<u32>()? != 1 {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
if buf.get_u32()? != 1 {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
let buf_index = buf.current_position();
|
||||
Ok((received, buf_index))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_server_vars(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>> {
|
||||
fn get_server_vars(bufferer: &mut Buffer<BigEndian>) -> GDResult<HashMap<String, String>> {
|
||||
let mut values = HashMap::new();
|
||||
|
||||
let mut done_processing_vars = false;
|
||||
while !done_processing_vars && !bufferer.is_remaining_empty() {
|
||||
let key = bufferer.get_string_utf8()?;
|
||||
let value = bufferer.get_string_utf8_optional()?;
|
||||
while !done_processing_vars && bufferer.remaining_length() != 0 {
|
||||
let key = bufferer.read_string::<Utf8Decoder>(None)?;
|
||||
let value = bufferer.read_string::<Utf8Decoder>(None)?;
|
||||
|
||||
if key.is_empty() {
|
||||
if value.is_empty() {
|
||||
bufferer.move_position_backward(1);
|
||||
bufferer.move_cursor(-1)?;
|
||||
done_processing_vars = true;
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +118,7 @@ fn get_server_vars(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>>
|
|||
Ok(values)
|
||||
}
|
||||
|
||||
fn get_teams(bufferer: &mut Bufferer) -> GDResult<Vec<Team>> {
|
||||
fn get_teams(bufferer: &mut Buffer<BigEndian>) -> GDResult<Vec<Team>> {
|
||||
let mut teams = Vec::new();
|
||||
|
||||
let (table, entries) = data_as_table(bufferer)?;
|
||||
|
|
@ -128,7 +133,7 @@ fn get_teams(bufferer: &mut Bufferer) -> GDResult<Vec<Team>> {
|
|||
Ok(teams)
|
||||
}
|
||||
|
||||
fn get_players(bufferer: &mut Bufferer) -> GDResult<Vec<Player>> {
|
||||
fn get_players(bufferer: &mut Buffer<BigEndian>) -> GDResult<Vec<Player>> {
|
||||
let mut players = Vec::new();
|
||||
|
||||
let (table, entries) = data_as_table(bufferer)?;
|
||||
|
|
@ -147,10 +152,13 @@ fn get_players(bufferer: &mut Bufferer) -> GDResult<Vec<Player>> {
|
|||
|
||||
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
|
||||
let mut client = GameSpy2::new(address, timeout_settings)?;
|
||||
let mut data = client.request_data()?;
|
||||
let (data, buf_index) = client.request_data()?;
|
||||
|
||||
let mut server_vars = get_server_vars(&mut data)?;
|
||||
let players = get_players(&mut data)?;
|
||||
let mut buffer = Buffer::<BigEndian>::new(&data);
|
||||
buffer.move_cursor(buf_index as isize)?;
|
||||
|
||||
let mut server_vars = get_server_vars(&mut buffer)?;
|
||||
let players = get_players(&mut buffer)?;
|
||||
|
||||
let players_online = match server_vars.remove("numplayers") {
|
||||
None => players.len(),
|
||||
|
|
@ -171,7 +179,7 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
|
|||
name: server_vars.remove("hostname").ok_or(GDError::PacketBad)?,
|
||||
map: server_vars.remove("mapname").ok_or(GDError::PacketBad)?,
|
||||
has_password: server_vars.remove("password").ok_or(GDError::PacketBad)? == "1",
|
||||
teams: get_teams(&mut data)?,
|
||||
teams: get_teams(&mut buffer)?,
|
||||
players_maximum: server_vars
|
||||
.remove("maxplayers")
|
||||
.ok_or(GDError::PacketBad)?
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// This file has code that has been documented by the NodeJS GameDig library
|
||||
// (MIT) from https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js
|
||||
|
||||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::{Buffer, Utf8Decoder},
|
||||
protocols::{
|
||||
minecraft::{BedrockResponse, GameMode, Server},
|
||||
types::TimeoutSettings,
|
||||
|
|
@ -12,8 +11,11 @@ use crate::{
|
|||
GDError::{PacketBad, TypeParse},
|
||||
GDResult,
|
||||
};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use byteorder::LittleEndian;
|
||||
|
||||
pub struct Bedrock {
|
||||
socket: UdpSocket,
|
||||
}
|
||||
|
|
@ -40,35 +42,36 @@ impl Bedrock {
|
|||
fn get_info(&mut self) -> GDResult<BedrockResponse> {
|
||||
self.send_status_request()?;
|
||||
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
|
||||
let received = self.socket.receive(None)?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&received);
|
||||
|
||||
if buffer.get_u8()? != 0x1c {
|
||||
if buffer.read::<u8>()? != 0x1c {
|
||||
return Err(PacketBad);
|
||||
}
|
||||
|
||||
// Checking for our nonce directly from a u64 (as the nonce is 8 bytes).
|
||||
if buffer.get_u64()? != 9833440827789222417 {
|
||||
if buffer.read::<u64>()? != 9833440827789222417 {
|
||||
return Err(PacketBad);
|
||||
}
|
||||
|
||||
// These 8 bytes are identical to the serverId string we receive in decimal
|
||||
// below
|
||||
buffer.move_position_ahead(8);
|
||||
buffer.move_cursor(8)?;
|
||||
|
||||
// Verifying the magic value (as we need 16 bytes, cast to two u64 values)
|
||||
if buffer.get_u64()? != 18374403896610127616 {
|
||||
if buffer.read::<u64>()? != 18374403896610127616 {
|
||||
return Err(PacketBad);
|
||||
}
|
||||
|
||||
if buffer.get_u64()? != 8671175388723805693 {
|
||||
if buffer.read::<u64>()? != 8671175388723805693 {
|
||||
return Err(PacketBad);
|
||||
}
|
||||
|
||||
let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize;
|
||||
buffer.move_position_ahead(2);
|
||||
let remaining_length = buffer.switch_endian_chunk(2)?.read::<u16>()? as usize;
|
||||
|
||||
error_by_expected_size(remaining_length, buffer.remaining_length())?;
|
||||
|
||||
let binding = buffer.get_string_utf8_unended()?;
|
||||
let binding = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let status: Vec<&str> = binding.split(';').collect();
|
||||
|
||||
// We must have at least 6 values
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::Buffer,
|
||||
protocols::{
|
||||
minecraft::{as_varint, get_string, get_varint, JavaResponse, Player, Server},
|
||||
types::TimeoutSettings,
|
||||
|
|
@ -8,8 +8,10 @@ use crate::{
|
|||
GDError::{JsonParse, PacketBad},
|
||||
GDResult,
|
||||
};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use byteorder::LittleEndian;
|
||||
use serde_json::Value;
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
|
@ -43,14 +45,15 @@ impl Java {
|
|||
.send(&[as_varint(data.len() as i32), data].concat())
|
||||
}
|
||||
|
||||
fn receive(&mut self) -> GDResult<Bufferer> {
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
|
||||
fn receive(&mut self) -> GDResult<Vec<u8>> {
|
||||
let data = &self.socket.receive(None)?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(data);
|
||||
|
||||
let _packet_length = get_varint(&mut buffer)? as usize;
|
||||
// this declared 'packet length' from within the packet might be wrong (?), not
|
||||
// checking with it...
|
||||
|
||||
Ok(buffer)
|
||||
Ok(buffer.remaining_bytes().to_vec())
|
||||
}
|
||||
|
||||
fn send_handshake(&mut self) -> GDResult<()> {
|
||||
|
|
@ -82,7 +85,8 @@ impl Java {
|
|||
self.send_status_request()?;
|
||||
self.send_ping_request()?;
|
||||
|
||||
let mut buffer = self.receive()?;
|
||||
let socket_data = self.receive()?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&socket_data);
|
||||
|
||||
if get_varint(&mut buffer)? != 0 {
|
||||
// first var int is the packet id
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::{Buffer, Utf16Decoder},
|
||||
protocols::{
|
||||
minecraft::{JavaResponse, LegacyGroup, Server},
|
||||
types::TimeoutSettings,
|
||||
|
|
@ -9,8 +9,11 @@ use crate::{
|
|||
GDError::{PacketBad, ProtocolFormat},
|
||||
GDResult,
|
||||
};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use byteorder::BigEndian;
|
||||
|
||||
pub struct LegacyBV1_8 {
|
||||
socket: TcpSocket,
|
||||
}
|
||||
|
|
@ -29,16 +32,16 @@ impl LegacyBV1_8 {
|
|||
self.send_initial_request()?;
|
||||
|
||||
let data = self.socket.receive(None)?;
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &data);
|
||||
let mut buffer = Buffer::<BigEndian>::new(&data);
|
||||
|
||||
if buffer.get_u8()? != 0xFF {
|
||||
if buffer.read::<u8>()? != 0xFF {
|
||||
return Err(ProtocolFormat);
|
||||
}
|
||||
|
||||
let length = buffer.get_u16()? * 2;
|
||||
let length = buffer.read::<u16>()? * 2;
|
||||
error_by_expected_size((length + 3) as usize, data.len())?;
|
||||
|
||||
let packet_string = buffer.get_string_utf16()?;
|
||||
let packet_string = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
|
||||
|
||||
let split: Vec<&str> = packet_string.split('§').collect();
|
||||
error_by_expected_size(3, split.len())?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use byteorder::BigEndian;
|
||||
|
||||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::{Buffer, Utf16Decoder},
|
||||
protocols::{
|
||||
minecraft::{protocol::legacy_v1_6::LegacyV1_6, JavaResponse, LegacyGroup, Server},
|
||||
types::TimeoutSettings,
|
||||
|
|
@ -29,20 +31,20 @@ impl LegacyV1_4 {
|
|||
self.send_initial_request()?;
|
||||
|
||||
let data = self.socket.receive(None)?;
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &data);
|
||||
let mut buffer = Buffer::<BigEndian>::new(&data);
|
||||
|
||||
if buffer.get_u8()? != 0xFF {
|
||||
if buffer.read::<u8>()? != 0xFF {
|
||||
return Err(ProtocolFormat);
|
||||
}
|
||||
|
||||
let length = buffer.get_u16()? * 2;
|
||||
let length = buffer.read::<u16>()? * 2;
|
||||
error_by_expected_size((length + 3) as usize, data.len())?;
|
||||
|
||||
if LegacyV1_6::is_protocol(&mut buffer)? {
|
||||
return LegacyV1_6::get_response(&mut buffer);
|
||||
}
|
||||
|
||||
let packet_string = buffer.get_string_utf16()?;
|
||||
let packet_string = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
|
||||
|
||||
let split: Vec<&str> = packet_string.split('§').collect();
|
||||
error_by_expected_size(3, split.len())?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use byteorder::BigEndian;
|
||||
|
||||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::{Buffer, Utf16Decoder},
|
||||
protocols::{
|
||||
minecraft::{JavaResponse, LegacyGroup, Server},
|
||||
types::TimeoutSettings,
|
||||
|
|
@ -36,29 +38,34 @@ impl LegacyV1_6 {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_protocol(buffer: &mut Bufferer) -> GDResult<bool> {
|
||||
pub(crate) fn is_protocol(buffer: &mut Buffer<BigEndian>) -> GDResult<bool> {
|
||||
let state = buffer
|
||||
.remaining_data()
|
||||
.remaining_bytes()
|
||||
.starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]);
|
||||
|
||||
if state {
|
||||
buffer.move_position_ahead(6);
|
||||
buffer.move_cursor(6)?;
|
||||
}
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub fn get_response(buffer: &mut Bufferer) -> GDResult<JavaResponse> {
|
||||
let packet_string = buffer.get_string_utf16()?;
|
||||
|
||||
let split: Vec<&str> = packet_string.split('\x00').collect();
|
||||
error_by_expected_size(5, split.len())?;
|
||||
|
||||
let version_protocol = split[0].parse().map_err(|_| PacketBad)?;
|
||||
let version_name = split[1].to_string();
|
||||
let description = split[2].to_string();
|
||||
let online_players = split[3].parse().map_err(|_| PacketBad)?;
|
||||
let max_players = split[4].parse().map_err(|_| PacketBad)?;
|
||||
pub(crate) fn get_response(buffer: &mut Buffer<BigEndian>) -> GDResult<JavaResponse> {
|
||||
// This is a specific order!
|
||||
let version_protocol = buffer
|
||||
.read_string::<Utf16Decoder<BigEndian>>(None)?
|
||||
.parse()
|
||||
.map_err(|_| PacketBad)?;
|
||||
let version_name = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
|
||||
let description = buffer.read_string::<Utf16Decoder<BigEndian>>(None)?;
|
||||
let online_players = buffer
|
||||
.read_string::<Utf16Decoder<BigEndian>>(None)?
|
||||
.parse()
|
||||
.map_err(|_| PacketBad)?;
|
||||
let max_players = buffer
|
||||
.read_string::<Utf16Decoder<BigEndian>>(None)?
|
||||
.parse()
|
||||
.map_err(|_| PacketBad)?;
|
||||
|
||||
Ok(JavaResponse {
|
||||
version_name,
|
||||
|
|
@ -78,13 +85,13 @@ impl LegacyV1_6 {
|
|||
self.send_initial_request()?;
|
||||
|
||||
let data = self.socket.receive(None)?;
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Big, &data);
|
||||
let mut buffer = Buffer::<BigEndian>::new(&data);
|
||||
|
||||
if buffer.get_u8()? != 0xFF {
|
||||
if buffer.read::<u8>()? != 0xFF {
|
||||
return Err(ProtocolFormat);
|
||||
}
|
||||
|
||||
let length = buffer.get_u16()? * 2;
|
||||
let length = buffer.read::<u16>()? * 2;
|
||||
error_by_expected_size((length + 3) as usize, data.len())?;
|
||||
|
||||
if !LegacyV1_6::is_protocol(&mut buffer)? {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs
|
||||
|
||||
use crate::{
|
||||
bufferer::Bufferer,
|
||||
buffer::Buffer,
|
||||
protocols::{
|
||||
types::{CommonPlayer, CommonResponse, GenericPlayer},
|
||||
GenericResponse,
|
||||
|
|
@ -12,6 +12,7 @@ use crate::{
|
|||
GDResult,
|
||||
};
|
||||
|
||||
use byteorder::ByteOrder;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -179,14 +180,14 @@ impl GameMode {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult<i32> {
|
||||
pub(crate) fn get_varint<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<i32> {
|
||||
let mut result = 0;
|
||||
|
||||
let msb: u8 = 0b10000000;
|
||||
let mask: u8 = !msb;
|
||||
|
||||
for i in 0 .. 5 {
|
||||
let current_byte = buffer.get_u8()?;
|
||||
let current_byte = buffer.read::<u8>()?;
|
||||
|
||||
result |= ((current_byte & mask) as i32) << (7 * i);
|
||||
|
||||
|
|
@ -227,12 +228,12 @@ pub(crate) fn as_varint(value: i32) -> Vec<u8> {
|
|||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult<String> {
|
||||
pub(crate) fn get_string<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<String> {
|
||||
let length = get_varint(buffer)? as usize;
|
||||
let mut text = Vec::with_capacity(length);
|
||||
|
||||
for _ in 0 .. length {
|
||||
text.push(buffer.get_u8()?)
|
||||
text.push(buffer.read::<u8>()?)
|
||||
}
|
||||
|
||||
String::from_utf8(text).map_err(|_| PacketBad)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use byteorder::LittleEndian;
|
||||
|
||||
use crate::buffer::{Buffer, Utf8Decoder};
|
||||
use crate::protocols::quake::types::Response;
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::socket::{Socket, UdpSocket};
|
||||
|
|
@ -15,10 +17,7 @@ pub(crate) trait QuakeClient {
|
|||
fn parse_player_string(data: Iter<&str>) -> GDResult<Self::Player>;
|
||||
}
|
||||
|
||||
fn get_data<Client: QuakeClient>(
|
||||
address: &SocketAddr,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Bufferer> {
|
||||
fn get_data<Client: QuakeClient>(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Vec<u8>> {
|
||||
let mut socket = UdpSocket::new(address)?;
|
||||
socket.apply_timeout(timeout_settings)?;
|
||||
|
||||
|
|
@ -32,24 +31,24 @@ fn get_data<Client: QuakeClient>(
|
|||
)?;
|
||||
|
||||
let data = socket.receive(None)?;
|
||||
let mut bufferer = Bufferer::new_with_data(Endianess::Little, &data);
|
||||
let mut bufferer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
if bufferer.get_u32()? != 4294967295 {
|
||||
if bufferer.read::<u32>()? != 4294967295 {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
let response_header = Client::get_response_header().as_bytes();
|
||||
if !bufferer.remaining_data().starts_with(response_header) {
|
||||
if !bufferer.remaining_bytes().starts_with(response_header) {
|
||||
Err(GDError::PacketBad)?
|
||||
}
|
||||
|
||||
bufferer.move_position_ahead(response_header.len());
|
||||
bufferer.move_cursor(response_header.len() as isize)?;
|
||||
|
||||
Ok(bufferer)
|
||||
Ok(bufferer.remaining_bytes().to_vec()) //TODO: Maybe fix?
|
||||
}
|
||||
|
||||
fn get_server_values(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String>> {
|
||||
let data = bufferer.get_string_utf8_newline()?;
|
||||
fn get_server_values(bufferer: &mut Buffer<LittleEndian>) -> GDResult<HashMap<String, String>> {
|
||||
let data = bufferer.read_string::<Utf8Decoder>(Some([0x0A]))?;
|
||||
let mut data_split = data.split('\\').collect::<Vec<&str>>();
|
||||
if let Some(first) = data_split.first() {
|
||||
if first == &"" {
|
||||
|
|
@ -74,11 +73,14 @@ fn get_server_values(bufferer: &mut Bufferer) -> GDResult<HashMap<String, String
|
|||
Ok(vars)
|
||||
}
|
||||
|
||||
fn get_players<Client: QuakeClient>(bufferer: &mut Bufferer) -> GDResult<Vec<Client::Player>> {
|
||||
fn get_players<Client: QuakeClient>(bufferer: &mut Buffer<LittleEndian>) -> GDResult<Vec<Client::Player>> {
|
||||
let mut players: Vec<Client::Player> = Vec::new();
|
||||
|
||||
while !bufferer.is_remaining_empty() && bufferer.remaining_data() != [0x00] {
|
||||
let data = bufferer.get_string_utf8_newline()?;
|
||||
// this needs to be looked at again as theres no way to check if the buffer has
|
||||
// a remaining null byte the original code was:
|
||||
// while !bufferer.is_remaining_empty() && bufferer.remaining_data() != [0x00]
|
||||
while !bufferer.remaining_length() == 0 {
|
||||
let data = bufferer.read_string::<Utf8Decoder>(Some([0x0A]))?;
|
||||
let data_split = data.split(' ').collect::<Vec<&str>>();
|
||||
let data_iter = data_split.iter();
|
||||
|
||||
|
|
@ -92,7 +94,8 @@ pub(crate) fn client_query<Client: QuakeClient>(
|
|||
address: &SocketAddr,
|
||||
timeout_settings: Option<TimeoutSettings>,
|
||||
) -> GDResult<Response<Client::Player>> {
|
||||
let mut bufferer = get_data::<Client>(address, timeout_settings)?;
|
||||
let data = get_data::<Client>(address, timeout_settings)?;
|
||||
let mut bufferer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let mut server_vars = get_server_values(&mut bufferer)?;
|
||||
let players = get_players::<Client>(&mut bufferer)?;
|
||||
|
|
|
|||
|
|
@ -56,10 +56,7 @@ impl QuakeClient for QuakeTwo {
|
|||
None => Err(GDError::PacketBad)?,
|
||||
Some(v) => remove_wrapping_quotes(v).to_string(),
|
||||
},
|
||||
address: match data.next() {
|
||||
None => None,
|
||||
Some(v) => Some(remove_wrapping_quotes(v).to_string()),
|
||||
},
|
||||
address: data.next().map(|v| remove_wrapping_quotes(v).to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
bufferer::{Bufferer, Endianess},
|
||||
buffer::Buffer,
|
||||
protocols::{
|
||||
types::TimeoutSettings,
|
||||
valve::{
|
||||
|
|
@ -27,7 +27,9 @@ use crate::{
|
|||
|
||||
use bzip2_rs::decoder::Decoder;
|
||||
|
||||
use crate::buffer::Utf8Decoder;
|
||||
use crate::protocols::valve::Packet;
|
||||
use byteorder::LittleEndian;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
|
|
@ -46,26 +48,27 @@ struct SplitPacket {
|
|||
}
|
||||
|
||||
impl SplitPacket {
|
||||
fn new(engine: &Engine, protocol: u8, buffer: &mut Bufferer) -> GDResult<Self> {
|
||||
let header = buffer.get_u32()?;
|
||||
let id = buffer.get_u32()?;
|
||||
fn new(engine: &Engine, protocol: u8, buffer: &mut Buffer<LittleEndian>) -> GDResult<Self> {
|
||||
let header = buffer.read()?; //buffer.get_u32()?;
|
||||
let id = buffer.read()?;
|
||||
let (total, number, size, compressed, decompressed_size, uncompressed_crc32) = match engine {
|
||||
Engine::GoldSrc(_) => {
|
||||
let (lower, upper) = u8_lower_upper(buffer.get_u8()?);
|
||||
let (lower, upper) = u8_lower_upper(buffer.read()?);
|
||||
(lower, upper, 0, false, None, None)
|
||||
}
|
||||
Engine::Source(_) => {
|
||||
let total = buffer.get_u8()?;
|
||||
let number = buffer.get_u8()?;
|
||||
let total = buffer.read()?;
|
||||
let number = buffer.read()?;
|
||||
let size = match protocol == 7 && (*engine == SteamApp::CSS.as_engine()) {
|
||||
// certain apps with protocol = 7 dont have this field
|
||||
false => buffer.get_u16()?,
|
||||
false => buffer.read()?,
|
||||
true => 1248,
|
||||
};
|
||||
let compressed = ((id >> 31) & 1) == 1;
|
||||
let compressed = ((id >> 31) & 1u32) == 1u32;
|
||||
|
||||
let (decompressed_size, uncompressed_crc32) = match compressed {
|
||||
false => (None, None),
|
||||
true => (Some(buffer.get_u32()?), Some(buffer.get_u32()?)),
|
||||
true => (Some(buffer.read()?), Some(buffer.read()?)),
|
||||
};
|
||||
(
|
||||
total,
|
||||
|
|
@ -87,7 +90,7 @@ impl SplitPacket {
|
|||
compressed,
|
||||
decompressed_size,
|
||||
uncompressed_crc32,
|
||||
payload: buffer.remaining_data().to_vec(),
|
||||
payload: buffer.remaining_bytes().to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +136,10 @@ impl ValveProtocol {
|
|||
|
||||
fn receive(&mut self, engine: &Engine, protocol: u8, buffer_size: usize) -> GDResult<Packet> {
|
||||
let data = self.socket.receive(Some(buffer_size))?;
|
||||
let mut buffer = Bufferer::new_with_data(Endianess::Little, &data);
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let header = buffer.get_u8()?;
|
||||
buffer.move_position_backward(1);
|
||||
let header: u8 = buffer.read()?;
|
||||
buffer.move_cursor(-1)?;
|
||||
if header == 0xFE {
|
||||
// the packet is split
|
||||
let mut main_packet = SplitPacket::new(engine, protocol, &mut buffer)?;
|
||||
|
|
@ -144,7 +147,7 @@ impl ValveProtocol {
|
|||
|
||||
for _ in 1 .. main_packet.total {
|
||||
let new_data = self.socket.receive(Some(buffer_size))?;
|
||||
buffer = Bufferer::new_with_data(Endianess::Little, &new_data);
|
||||
buffer = Buffer::<LittleEndian>::new(&new_data);
|
||||
let chunk_packet = SplitPacket::new(engine, protocol, &mut buffer)?;
|
||||
chunk_packets.push(chunk_packet);
|
||||
}
|
||||
|
|
@ -155,25 +158,21 @@ impl ValveProtocol {
|
|||
main_packet.payload.extend(chunk_packet.payload);
|
||||
}
|
||||
|
||||
let mut new_packet_buffer = Bufferer::new_with_data(Endianess::Little, &main_packet.get_payload()?);
|
||||
let payload = main_packet.get_payload()?; // Creating a non-temporary value here
|
||||
let mut new_packet_buffer = Buffer::<LittleEndian>::new(&payload); // Using the non-temporary value here
|
||||
Ok(Packet::new_from_bufferer(&mut new_packet_buffer)?)
|
||||
} else {
|
||||
Packet::new_from_bufferer(&mut buffer)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_kind_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult<Bufferer> {
|
||||
self.get_request_data(engine, protocol, kind as u8, kind.get_default_payload())
|
||||
pub fn get_kind_request_data(&mut self, engine: &Engine, protocol: u8, kind: Request) -> GDResult<Vec<u8>> {
|
||||
let data = self.get_request_data(engine, protocol, kind as u8, kind.get_default_payload())?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Ask for a specific request only.
|
||||
pub fn get_request_data(
|
||||
&mut self,
|
||||
engine: &Engine,
|
||||
protocol: u8,
|
||||
kind: u8,
|
||||
payload: Vec<u8>,
|
||||
) -> GDResult<Bufferer> {
|
||||
pub fn get_request_data(&mut self, engine: &Engine, protocol: u8, kind: u8, payload: Vec<u8>) -> GDResult<Vec<u8>> {
|
||||
let request_initial_packet = Packet::new(kind, payload).to_bytes();
|
||||
self.socket.send(&request_initial_packet)?;
|
||||
|
||||
|
|
@ -182,7 +181,7 @@ impl ValveProtocol {
|
|||
// 'A'
|
||||
let challenge = packet.payload;
|
||||
|
||||
const INFO: u8 = Request::Info as u8; // hmm, this could be unwanted and problematic
|
||||
const INFO: u8 = Request::Info as u8;
|
||||
let challenge_packet = Packet::new(
|
||||
kind,
|
||||
match kind {
|
||||
|
|
@ -197,48 +196,47 @@ impl ValveProtocol {
|
|||
packet = self.receive(engine, protocol, PACKET_SIZE)?;
|
||||
}
|
||||
|
||||
let data = packet.payload;
|
||||
Ok(Bufferer::new_with_data(Endianess::Little, &data))
|
||||
Ok(packet.payload)
|
||||
}
|
||||
|
||||
fn get_goldsrc_server_info(buffer: &mut Bufferer) -> GDResult<ServerInfo> {
|
||||
buffer.get_u8()?; //get the header (useless info)
|
||||
buffer.get_string_utf8()?; //get the server address (useless info)
|
||||
let name = buffer.get_string_utf8()?;
|
||||
let map = buffer.get_string_utf8()?;
|
||||
let folder = buffer.get_string_utf8()?;
|
||||
let game = buffer.get_string_utf8()?;
|
||||
let players = buffer.get_u8()?;
|
||||
let max_players = buffer.get_u8()?;
|
||||
let protocol = buffer.get_u8()?;
|
||||
let server_type = match buffer.get_u8()? {
|
||||
fn get_goldsrc_server_info(buffer: &mut Buffer<LittleEndian>) -> GDResult<ServerInfo> {
|
||||
let _header: u8 = buffer.read()?; //get the header (useless info)
|
||||
let _address: String = buffer.read_string::<Utf8Decoder>(None)?; //get the server address (useless info)
|
||||
let name = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let map = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let folder = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let game = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let players = buffer.read()?;
|
||||
let max_players = buffer.read()?;
|
||||
let protocol = buffer.read()?;
|
||||
let server_type = match buffer.read::<u8>()? {
|
||||
68 => Server::Dedicated, //'D'
|
||||
76 => Server::NonDedicated, //'L'
|
||||
80 => Server::TV, //'P'
|
||||
_ => Err(UnknownEnumCast)?,
|
||||
};
|
||||
let environment_type = match buffer.get_u8()? {
|
||||
let environment_type = match buffer.read::<u8>()? {
|
||||
76 => Environment::Linux, //'L'
|
||||
87 => Environment::Windows, //'W'
|
||||
_ => Err(UnknownEnumCast)?,
|
||||
};
|
||||
let has_password = buffer.get_u8()? == 1;
|
||||
let is_mod = buffer.get_u8()? == 1;
|
||||
let has_password = buffer.read::<u8>()? == 1;
|
||||
let is_mod = buffer.read::<u8>()? == 1;
|
||||
let mod_data = match is_mod {
|
||||
false => None,
|
||||
true => {
|
||||
Some(ModData {
|
||||
link: buffer.get_string_utf8()?,
|
||||
download_link: buffer.get_string_utf8()?,
|
||||
version: buffer.get_u32()?,
|
||||
size: buffer.get_u32()?,
|
||||
multiplayer_only: buffer.get_u8()? == 1,
|
||||
has_own_dll: buffer.get_u8()? == 1,
|
||||
link: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
download_link: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
version: buffer.read()?,
|
||||
size: buffer.read()?,
|
||||
multiplayer_only: buffer.read::<u8>()? == 1,
|
||||
has_own_dll: buffer.read::<u8>()? == 1,
|
||||
})
|
||||
}
|
||||
};
|
||||
let vac_secured = buffer.get_u8()? == 1;
|
||||
let bots = buffer.get_u8()?;
|
||||
let vac_secured = buffer.read::<u8>()? == 1;
|
||||
let bots = buffer.read::<u8>()?;
|
||||
|
||||
Ok(ServerInfo {
|
||||
protocol,
|
||||
|
|
@ -264,7 +262,8 @@ impl ValveProtocol {
|
|||
|
||||
/// Get the server information's.
|
||||
fn get_server_info(&mut self, engine: &Engine) -> GDResult<ServerInfo> {
|
||||
let mut buffer = self.get_kind_request_data(engine, 0, Request::Info)?;
|
||||
let data = self.get_kind_request_data(engine, 0, Request::Info)?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
if let Engine::GoldSrc(force) = engine {
|
||||
if *force {
|
||||
|
|
@ -272,58 +271,58 @@ impl ValveProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
let protocol = buffer.get_u8()?;
|
||||
let name = buffer.get_string_utf8()?;
|
||||
let map = buffer.get_string_utf8()?;
|
||||
let folder = buffer.get_string_utf8()?;
|
||||
let game = buffer.get_string_utf8()?;
|
||||
let mut appid = buffer.get_u16()? as u32;
|
||||
let players = buffer.get_u8()?;
|
||||
let max_players = buffer.get_u8()?;
|
||||
let bots = buffer.get_u8()?;
|
||||
let server_type = Server::from_gldsrc(buffer.get_u8()?)?;
|
||||
let environment_type = Environment::from_gldsrc(buffer.get_u8()?)?;
|
||||
let has_password = buffer.get_u8()? == 1;
|
||||
let vac_secured = buffer.get_u8()? == 1;
|
||||
let protocol = buffer.read()?;
|
||||
let name = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let map = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let folder = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let game = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let mut appid = buffer.read::<u16>()? as u32;
|
||||
let players = buffer.read()?;
|
||||
let max_players = buffer.read()?;
|
||||
let bots = buffer.read()?;
|
||||
let server_type = Server::from_gldsrc(buffer.read()?)?;
|
||||
let environment_type = Environment::from_gldsrc(buffer.read()?)?;
|
||||
let has_password = buffer.read::<u8>()? == 1;
|
||||
let vac_secured = buffer.read::<u8>()? == 1;
|
||||
let the_ship = match *engine == SteamApp::TS.as_engine() {
|
||||
false => None,
|
||||
true => {
|
||||
Some(TheShip {
|
||||
mode: buffer.get_u8()?,
|
||||
witnesses: buffer.get_u8()?,
|
||||
duration: buffer.get_u8()?,
|
||||
mode: buffer.read()?,
|
||||
witnesses: buffer.read()?,
|
||||
duration: buffer.read()?,
|
||||
})
|
||||
}
|
||||
};
|
||||
let version = buffer.get_string_utf8()?;
|
||||
let extra_data = match buffer.get_u8() {
|
||||
let version = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let extra_data = match buffer.read::<u8>() {
|
||||
Err(_) => None,
|
||||
Ok(value) => {
|
||||
Some(ExtraData {
|
||||
port: match (value & 0x80) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer.get_u16()?),
|
||||
true => Some(buffer.read()?),
|
||||
},
|
||||
steam_id: match (value & 0x10) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer.get_u64()?),
|
||||
true => Some(buffer.read()?),
|
||||
},
|
||||
tv_port: match (value & 0x40) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer.get_u16()?),
|
||||
true => Some(buffer.read()?),
|
||||
},
|
||||
tv_name: match (value & 0x40) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer.get_string_utf8()?),
|
||||
true => Some(buffer.read_string::<Utf8Decoder>(None)?),
|
||||
},
|
||||
keywords: match (value & 0x20) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer.get_string_utf8()?),
|
||||
true => Some(buffer.read_string::<Utf8Decoder>(None)?),
|
||||
},
|
||||
game_id: match (value & 0x01) > 0 {
|
||||
false => None,
|
||||
true => {
|
||||
let gid = buffer.get_u64()?;
|
||||
let gid = buffer.read()?;
|
||||
appid = (gid & ((1 << 24) - 1)) as u32;
|
||||
|
||||
Some(gid)
|
||||
|
|
@ -357,25 +356,26 @@ impl ValveProtocol {
|
|||
|
||||
/// Get the server player's.
|
||||
fn get_server_players(&mut self, engine: &Engine, protocol: u8) -> GDResult<Vec<ServerPlayer>> {
|
||||
let mut buffer = self.get_kind_request_data(engine, protocol, Request::Players)?;
|
||||
let data = self.get_kind_request_data(engine, protocol, Request::Players)?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let count = buffer.get_u8()? as usize;
|
||||
let count = buffer.read::<u8>()? as usize;
|
||||
let mut players: Vec<ServerPlayer> = Vec::with_capacity(count);
|
||||
|
||||
for _ in 0 .. count {
|
||||
buffer.move_position_ahead(1); //skip the index byte
|
||||
buffer.move_cursor(1)?; //skip the index byte
|
||||
|
||||
players.push(ServerPlayer {
|
||||
name: buffer.get_string_utf8()?,
|
||||
score: buffer.get_u32()?,
|
||||
duration: buffer.get_f32()?,
|
||||
name: buffer.read_string::<Utf8Decoder>(None)?,
|
||||
score: buffer.read()?,
|
||||
duration: buffer.read()?,
|
||||
deaths: match *engine == SteamApp::TS.as_engine() {
|
||||
false => None,
|
||||
true => Some(buffer.get_u32()?),
|
||||
true => Some(buffer.read()?),
|
||||
},
|
||||
money: match *engine == SteamApp::TS.as_engine() {
|
||||
false => None,
|
||||
true => Some(buffer.get_u32()?),
|
||||
true => Some(buffer.read()?),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -385,14 +385,15 @@ impl ValveProtocol {
|
|||
|
||||
/// Get the server's rules.
|
||||
fn get_server_rules(&mut self, engine: &Engine, protocol: u8) -> GDResult<HashMap<String, String>> {
|
||||
let mut buffer = self.get_kind_request_data(engine, protocol, Request::Rules)?;
|
||||
let data = self.get_kind_request_data(engine, protocol, Request::Rules)?;
|
||||
let mut buffer = Buffer::<LittleEndian>::new(&data);
|
||||
|
||||
let count = buffer.get_u16()? as usize;
|
||||
let count = buffer.read::<u16>()? as usize;
|
||||
let mut rules: HashMap<String, String> = HashMap::with_capacity(count);
|
||||
|
||||
for _ in 0 .. count {
|
||||
let name = buffer.get_string_utf8()?;
|
||||
let value = buffer.get_string_utf8()?;
|
||||
let name = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
let value = buffer.read_string::<Utf8Decoder>(None)?;
|
||||
|
||||
rules.insert(name, value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ use std::collections::HashMap;
|
|||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::GDError::UnknownEnumCast;
|
||||
use crate::GDResult;
|
||||
use crate::{bufferer::Bufferer, protocols::GenericResponse};
|
||||
use crate::{buffer::Buffer, protocols::GenericResponse};
|
||||
use byteorder::LittleEndian;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -209,11 +210,11 @@ impl Packet {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_from_bufferer(buffer: &mut Bufferer) -> GDResult<Self> {
|
||||
pub fn new_from_bufferer(buffer: &mut Buffer<LittleEndian>) -> GDResult<Self> {
|
||||
Ok(Self {
|
||||
header: buffer.get_u32()?,
|
||||
kind: buffer.get_u8()?,
|
||||
payload: buffer.remaining_data().to_vec(),
|
||||
header: buffer.read::<u32>()?,
|
||||
kind: buffer.read::<u8>()?,
|
||||
payload: buffer.remaining_bytes().to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use crate::socket::{Socket, UdpSocket};
|
||||
use crate::valve_master_server::{Region, SearchFilters};
|
||||
use crate::{GDError, GDResult};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
socket::{Socket, UdpSocket},
|
||||
valve_master_server::{Region, SearchFilters},
|
||||
GDError,
|
||||
GDResult,
|
||||
};
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
use byteorder::BigEndian;
|
||||
|
||||
/// The default master ip, which is the one for Source.
|
||||
pub fn default_master_address() -> SocketAddr {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(208, 64, 201, 194)), 27011) // hl2master.steampowered.com
|
||||
|
|
@ -63,21 +69,22 @@ impl ValveMasterServer {
|
|||
self.socket.send(&payload)?;
|
||||
|
||||
let received_data = self.socket.receive(Some(1400))?;
|
||||
let mut buf = Bufferer::new_with_data(Endianess::Big, &received_data);
|
||||
let mut buf = Buffer::<BigEndian>::new(&received_data);
|
||||
|
||||
if buf.get_u32()? != 4294967295 || buf.get_u16()? != 26122 {
|
||||
if buf.read::<u32>()? != 4294967295 || buf.read::<u16>()? != 26122 {
|
||||
return Err(GDError::PacketBad);
|
||||
}
|
||||
|
||||
let mut ips: Vec<(IpAddr, u16)> = Vec::new();
|
||||
while !buf.is_remaining_empty() {
|
||||
|
||||
while !buf.remaining_length() == 0 {
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(
|
||||
buf.get_u8()?,
|
||||
buf.get_u8()?,
|
||||
buf.get_u8()?,
|
||||
buf.get_u8()?,
|
||||
buf.read::<u8>()?,
|
||||
buf.read::<u8>()?,
|
||||
buf.read::<u8>()?,
|
||||
buf.read::<u8>()?,
|
||||
));
|
||||
let port = buf.get_u16()?;
|
||||
let port = buf.read::<u16>()?;
|
||||
|
||||
ips.push((ip, port));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue