mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
Minecraft implementation (#6)
* Initial minecraft support * Made previews_chat an option * Better error handling and removed version structure * Minecraft Server types * Fixed compilation and renamed stuff * 'extract till you drop!' extracted sockets * extracted java version and fixed socket udp receive * Legacy 1.4 and 1.6 implementation (incomplete) * Furter implementation * Implementations work * Protocol beta v1.8+ implemented * Removed bedrock support * Added auto query * Renamed minecraft to mc and added to md's * Docs, renames and small optimization changes * Changed java version to be able to return None on players sample
This commit is contained in:
parent
974e093e23
commit
ee0223a7a3
23 changed files with 810 additions and 80 deletions
|
|
@ -1,10 +1,10 @@
|
|||
use std::net::UdpSocket;
|
||||
use bzip2_rs::decoder::Decoder;
|
||||
use crate::{GDError, GDResult};
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::protocols::valve::{App, ModData, SteamID};
|
||||
use crate::protocols::valve::types::{Environment, ExtraData, GatheringSettings, Request, Response, Server, ServerInfo, ServerPlayer, ServerRule, TheShip};
|
||||
use crate::utils::{buffer, complete_address, u8_lower_upper};
|
||||
use crate::socket::{Socket, UdpSocket};
|
||||
use crate::utils::{buffer, u8_lower_upper};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Packet {
|
||||
|
|
@ -42,7 +42,7 @@ impl Packet {
|
|||
fn initial(kind: Request) -> Self {
|
||||
Self {
|
||||
header: 4294967295, //FF FF FF FF
|
||||
kind: kind as u8,
|
||||
kind: kind.clone() as u8,
|
||||
payload: match kind {
|
||||
Request::INFO => String::from("Source Engine Query\0").into_bytes(),
|
||||
_ => vec![0xFF, 0xFF, 0xFF, 0xFF]
|
||||
|
|
@ -140,49 +140,29 @@ impl SplitPacket {
|
|||
}
|
||||
|
||||
struct ValveProtocol {
|
||||
socket: UdpSocket,
|
||||
complete_address: String
|
||||
socket: UdpSocket
|
||||
}
|
||||
|
||||
static PACKET_SIZE: usize = 1400;
|
||||
|
||||
impl ValveProtocol {
|
||||
fn new(address: &str, port: u16, timeout_settings: TimeoutSettings) -> GDResult<Self> {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| GDError::SocketBind(e.to_string()))?;
|
||||
|
||||
socket.set_read_timeout(timeout_settings.get_read()).unwrap(); //unwrapping because TimeoutSettings::new
|
||||
socket.set_write_timeout(timeout_settings.get_write()).unwrap();//checks if these are 0 and throws an error
|
||||
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
|
||||
let socket = UdpSocket::new(address, port)?;
|
||||
socket.apply_timeout(timeout_settings)?;
|
||||
|
||||
Ok(Self {
|
||||
socket,
|
||||
complete_address: complete_address(address, port)?
|
||||
socket
|
||||
})
|
||||
}
|
||||
|
||||
fn send(&self, data: &[u8]) -> GDResult<()> {
|
||||
self.socket.send_to(&data, &self.complete_address).map_err(|e| GDError::PacketSend(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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()))?;
|
||||
|
||||
if amt < 6 {
|
||||
return Err(GDError::PacketUnderflow("Any Valve Protocol response can't be under 6 bytes long.".to_string()));
|
||||
}
|
||||
|
||||
Ok(buf[..amt].to_vec())
|
||||
}
|
||||
|
||||
fn receive(&self, app: &App, protocol: u8, buffer_size: usize) -> GDResult<Packet> {
|
||||
let mut buf = self.receive_raw(buffer_size)?;
|
||||
fn receive(&mut self, app: &App, protocol: u8, buffer_size: usize) -> GDResult<Packet> {
|
||||
let mut buf = self.socket.receive(Some(buffer_size))?;
|
||||
|
||||
if buf[0] == 0xFE { //the packet is split
|
||||
let mut main_packet = SplitPacket::new(&app, protocol, &buf)?;
|
||||
|
||||
for _ in 1..main_packet.total {
|
||||
buf = self.receive_raw(buffer_size)?;
|
||||
buf = self.socket.receive(Some(buffer_size))?;
|
||||
let chunk_packet = SplitPacket::new(&app, protocol, &buf)?;
|
||||
main_packet.payload.extend(chunk_packet.payload);
|
||||
}
|
||||
|
|
@ -195,10 +175,10 @@ impl ValveProtocol {
|
|||
}
|
||||
|
||||
/// Ask for a specific request only.
|
||||
fn get_request_data(&self, app: &App, protocol: u8, kind: Request) -> GDResult<Vec<u8>> {
|
||||
fn get_request_data(&mut self, app: &App, protocol: u8, kind: Request) -> GDResult<Vec<u8>> {
|
||||
let request_initial_packet = Packet::initial(kind.clone()).to_bytes();
|
||||
|
||||
self.send(&request_initial_packet)?;
|
||||
self.socket.send(&request_initial_packet)?;
|
||||
let packet = self.receive(app, protocol, PACKET_SIZE)?;
|
||||
|
||||
if packet.kind != 0x41 { //'A'
|
||||
|
|
@ -208,7 +188,7 @@ impl ValveProtocol {
|
|||
let challenge = packet.payload;
|
||||
let challenge_packet = Packet::challenge(kind.clone(), challenge).to_bytes();
|
||||
|
||||
self.send(&challenge_packet)?;
|
||||
self.socket.send(&challenge_packet)?;
|
||||
Ok(self.receive(app, protocol, PACKET_SIZE)?.payload)
|
||||
}
|
||||
|
||||
|
|
@ -216,11 +196,11 @@ impl ValveProtocol {
|
|||
let mut pos = 0;
|
||||
|
||||
buffer::get_u8(&buf, &mut pos)?; //get the header (useless info)
|
||||
buffer::get_string(&buf, &mut pos)?; //get the server address (useless info)
|
||||
let name = buffer::get_string(&buf, &mut pos)?;
|
||||
let map = buffer::get_string(&buf, &mut pos)?;
|
||||
let folder = buffer::get_string(&buf, &mut pos)?;
|
||||
let game = buffer::get_string(&buf, &mut pos)?;
|
||||
buffer::get_string_utf8_le(&buf, &mut pos)?; //get the server address (useless info)
|
||||
let name = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let map = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let folder = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let game = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let players = buffer::get_u8(&buf, &mut pos)?;
|
||||
let max_players = buffer::get_u8(&buf, &mut pos)?;
|
||||
let protocol = buffer::get_u8(&buf, &mut pos)?;
|
||||
|
|
@ -240,8 +220,8 @@ impl ValveProtocol {
|
|||
let mod_data = match is_mod {
|
||||
false => None,
|
||||
true => Some(ModData {
|
||||
link: buffer::get_string(&buf, &mut pos)?,
|
||||
download_link: buffer::get_string(&buf, &mut pos)?,
|
||||
link: buffer::get_string_utf8_le(&buf, &mut pos)?,
|
||||
download_link: buffer::get_string_utf8_le(&buf, &mut pos)?,
|
||||
version: buffer::get_u32_le(&buf, &mut pos)?,
|
||||
size: buffer::get_u32_le(&buf, &mut pos)?,
|
||||
multiplayer_only: buffer::get_u8(&buf, &mut pos)? == 1,
|
||||
|
|
@ -274,7 +254,7 @@ impl ValveProtocol {
|
|||
}
|
||||
|
||||
/// Get the server information's.
|
||||
fn get_server_info(&self, app: &App) -> GDResult<ServerInfo> {
|
||||
fn get_server_info(&mut self, app: &App) -> GDResult<ServerInfo> {
|
||||
let buf = self.get_request_data(&app, 0, Request::INFO)?;
|
||||
if let App::GoldSrc(force) = app {
|
||||
if *force {
|
||||
|
|
@ -285,10 +265,10 @@ impl ValveProtocol {
|
|||
let mut pos = 0;
|
||||
|
||||
let protocol = buffer::get_u8(&buf, &mut pos)?;
|
||||
let name = buffer::get_string(&buf, &mut pos)?;
|
||||
let map = buffer::get_string(&buf, &mut pos)?;
|
||||
let folder = buffer::get_string(&buf, &mut pos)?;
|
||||
let game = buffer::get_string(&buf, &mut pos)?;
|
||||
let name = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let map = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let folder = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let game = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let mut appid = buffer::get_u16_le(&buf, &mut pos)? as u32;
|
||||
let players = buffer::get_u8(&buf, &mut pos)?;
|
||||
let max_players = buffer::get_u8(&buf, &mut pos)?;
|
||||
|
|
@ -315,7 +295,7 @@ impl ValveProtocol {
|
|||
duration: buffer::get_u8(&buf, &mut pos)?
|
||||
})
|
||||
};
|
||||
let version = buffer::get_string(&buf, &mut pos)?;
|
||||
let version = buffer::get_string_utf8_le(&buf, &mut pos)?;
|
||||
let extra_data = match buffer::get_u8(&buf, &mut pos) {
|
||||
Err(_) => None,
|
||||
Ok(value) => Some(ExtraData {
|
||||
|
|
@ -333,11 +313,11 @@ impl ValveProtocol {
|
|||
},
|
||||
tv_name: match (value & 0x40) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer::get_string(&buf, &mut pos)?)
|
||||
true => Some(buffer::get_string_utf8_le(&buf, &mut pos)?)
|
||||
},
|
||||
keywords: match (value & 0x20) > 0 {
|
||||
false => None,
|
||||
true => Some(buffer::get_string(&buf, &mut pos)?)
|
||||
true => Some(buffer::get_string_utf8_le(&buf, &mut pos)?)
|
||||
},
|
||||
game_id: match (value & 0x01) > 0 {
|
||||
false => None,
|
||||
|
|
@ -374,17 +354,17 @@ impl ValveProtocol {
|
|||
}
|
||||
|
||||
/// Get the server player's.
|
||||
fn get_server_players(&self, app: &App, protocol: u8) -> GDResult<Vec<ServerPlayer>> {
|
||||
fn get_server_players(&mut self, app: &App, protocol: u8) -> GDResult<Vec<ServerPlayer>> {
|
||||
let buf = self.get_request_data(&app, protocol, Request::PLAYERS)?;
|
||||
let mut pos = 0;
|
||||
|
||||
let count = buffer::get_u8(&buf, &mut pos)?;
|
||||
let mut players: Vec<ServerPlayer> = Vec::new();
|
||||
let count = buffer::get_u8(&buf, &mut pos)? as usize;
|
||||
let mut players: Vec<ServerPlayer> = Vec::with_capacity(count);
|
||||
|
||||
for _ in 0..count {
|
||||
pos += 1; //skip the index byte
|
||||
players.push(ServerPlayer {
|
||||
name: buffer::get_string(&buf, &mut pos)?,
|
||||
name: buffer::get_string_utf8_le(&buf, &mut pos)?,
|
||||
score: buffer::get_u32_le(&buf, &mut pos)?,
|
||||
duration: buffer::get_f32_le(&buf, &mut pos)?,
|
||||
deaths: match *app == SteamID::TS.as_app() {
|
||||
|
|
@ -402,7 +382,7 @@ impl ValveProtocol {
|
|||
}
|
||||
|
||||
/// Get the server rules's.
|
||||
fn get_server_rules(&self, app: &App, protocol: u8) -> GDResult<Option<Vec<ServerRule>>> {
|
||||
fn get_server_rules(&mut self, app: &App, protocol: u8) -> GDResult<Option<Vec<ServerRule>>> {
|
||||
if *app == SteamID::CSGO.as_app() { //cause csgo wont respond to this since feb 21 2014 update
|
||||
return Ok(None);
|
||||
}
|
||||
|
|
@ -410,13 +390,13 @@ impl ValveProtocol {
|
|||
let buf = self.get_request_data(&app, protocol, Request::RULES)?;
|
||||
let mut pos = 0;
|
||||
|
||||
let count = buffer::get_u16_le(&buf, &mut pos)?;
|
||||
let mut rules: Vec<ServerRule> = Vec::new();
|
||||
let count = buffer::get_u16_le(&buf, &mut pos)? as usize;
|
||||
let mut rules: Vec<ServerRule> = Vec::with_capacity(count);
|
||||
|
||||
for _ in 0..count {
|
||||
rules.push(ServerRule {
|
||||
name: buffer::get_string(&buf, &mut pos)?,
|
||||
value: buffer::get_string(&buf, &mut pos)?
|
||||
name: buffer::get_string_utf8_le(&buf, &mut pos)?,
|
||||
value: buffer::get_string_utf8_le(&buf, &mut pos)?
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -428,12 +408,11 @@ impl ValveProtocol {
|
|||
/// Providing None to the settings results in using the default values for them (GatherSettings::[default](GatheringSettings::default), TimeoutSettings::[default](TimeoutSettings::default)).
|
||||
pub fn query(address: &str, port: u16, app: App, gather_settings: Option<GatheringSettings>, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
|
||||
let response_gather_settings = gather_settings.unwrap_or(GatheringSettings::default());
|
||||
let response_timeout_settings = timeout_settings.unwrap_or(TimeoutSettings::default());
|
||||
get_response(address, port, app, response_gather_settings, response_timeout_settings)
|
||||
get_response(address, port, app, response_gather_settings, timeout_settings)
|
||||
}
|
||||
|
||||
fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSettings, timeout_settings: TimeoutSettings) -> GDResult<Response> {
|
||||
let client = ValveProtocol::new(address, port, timeout_settings)?;
|
||||
fn get_response(address: &str, port: u16, app: App, gather_settings: GatheringSettings, timeout_settings: Option<TimeoutSettings>) -> GDResult<Response> {
|
||||
let mut client = ValveProtocol::new(address, port, timeout_settings)?;
|
||||
|
||||
let info = client.get_server_info(&app)?;
|
||||
let protocol = info.protocol;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue