mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
Initial valve setup and tf2 game setup
This commit is contained in:
parent
e2be20ee53
commit
8098136d09
10 changed files with 234 additions and 12 deletions
14
examples/tf2.rs
Normal file
14
examples/tf2.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
use gamedig::TF2;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let response = TF2::query("5.15.202.107", None);
|
||||||
|
match response {
|
||||||
|
Err(_) => println!("fuck"),
|
||||||
|
Ok(r) => {
|
||||||
|
println!("{:?}", r);
|
||||||
|
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/errors.rs
Normal file
15
src/errors.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use core::fmt;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum GDError {
|
||||||
|
IDK(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for GDError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
GDError::IDK(details) => write!(f, "IDK: {details}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/games/mod.rs
Normal file
4
src/games/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
pub mod tf2;
|
||||||
|
|
||||||
|
pub use tf2::*;
|
||||||
14
src/games/tf2.rs
Normal file
14
src/games/tf2.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::errors::GDError;
|
||||||
|
use crate::protocol::Protocol;
|
||||||
|
use crate::protocols::valve::{Response, ValveProtocol};
|
||||||
|
|
||||||
|
pub struct TF2;
|
||||||
|
|
||||||
|
impl TF2 {
|
||||||
|
pub fn query(address: &str, port: Option<u16>) -> Result<Response, GDError> {
|
||||||
|
ValveProtocol::query(address, match port {
|
||||||
|
None => 27015,
|
||||||
|
Some(port) => port
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -1,14 +1,8 @@
|
||||||
pub fn add(left: usize, right: usize) -> usize {
|
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
mod errors;
|
||||||
mod tests {
|
mod protocol;
|
||||||
use super::*;
|
mod protocols;
|
||||||
|
mod utils;
|
||||||
|
pub mod games;
|
||||||
|
|
||||||
#[test]
|
pub use games::*;
|
||||||
fn it_works() {
|
|
||||||
let result = add(2, 2);
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
7
src/protocol.rs
Normal file
7
src/protocol.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
use crate::errors::GDError;
|
||||||
|
|
||||||
|
pub trait Protocol {
|
||||||
|
type Response;
|
||||||
|
|
||||||
|
fn query(address: &str, port: u16) -> Result<Self::Response, GDError>;
|
||||||
|
}
|
||||||
2
src/protocols/mod.rs
Normal file
2
src/protocols/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
pub mod valve;
|
||||||
137
src/protocols/valve.rs
Normal file
137
src/protocols/valve.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
use std::net::UdpSocket;
|
||||||
|
use crate::errors::GDError;
|
||||||
|
use crate::protocol::Protocol;
|
||||||
|
use crate::utils::{combine_two_u8, complete_address, concat_u8, find_null_in_array};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Server {
|
||||||
|
Dedicated,
|
||||||
|
NonDedicated,
|
||||||
|
SourceTV
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Environment {
|
||||||
|
Linux,
|
||||||
|
Windows,
|
||||||
|
Mac
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Response {
|
||||||
|
pub protocol: u8,
|
||||||
|
pub map: String,
|
||||||
|
pub name: String,
|
||||||
|
pub folder: String,
|
||||||
|
pub game: String,
|
||||||
|
pub id: u16,
|
||||||
|
pub players: u8,
|
||||||
|
pub max_players: u8,
|
||||||
|
pub bots: u8,
|
||||||
|
pub server_type: Server,
|
||||||
|
pub environment_type: Environment,
|
||||||
|
pub has_password: bool,
|
||||||
|
pub vac_secured: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Request {
|
||||||
|
A2sInfo(Option<[u8; 4]>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ValveProtocol {
|
||||||
|
socket: UdpSocket,
|
||||||
|
complete_address: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValveProtocol {
|
||||||
|
fn new(address: &str, port: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
socket: UdpSocket::bind("0.0.0.0:0").unwrap(),
|
||||||
|
complete_address: complete_address(address, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_request(&self, kind: Request, data_packet: Option<&[u8]>) -> bool {
|
||||||
|
let default: Vec<u8> = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20,
|
||||||
|
0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00];
|
||||||
|
|
||||||
|
let request_kind_packet = match kind {
|
||||||
|
Request::A2sInfo(challenge) => match challenge {
|
||||||
|
None => default,
|
||||||
|
Some(value) => concat_u8(&default, &value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut packet = request_kind_packet;
|
||||||
|
match data_packet {
|
||||||
|
None => (),
|
||||||
|
Some(data) => packet.extend_from_slice(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.socket.send_to(&packet, &self.complete_address) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(_) => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(&self) -> Vec<u8> {
|
||||||
|
self.receive_with_size(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive_with_size(&self, buffer_size: usize) -> Vec<u8> {
|
||||||
|
let mut buffer: Vec<u8> = vec![0; buffer_size];
|
||||||
|
let (amt, _) = self.socket.recv_from(&mut buffer.as_mut_slice()).unwrap();
|
||||||
|
buffer[..amt].to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Protocol for ValveProtocol {
|
||||||
|
type Response = Response;
|
||||||
|
|
||||||
|
fn query(address: &str, port: u16) -> Result<Response, GDError> {
|
||||||
|
let client = ValveProtocol::new(address, port);
|
||||||
|
|
||||||
|
client.do_request(Request::A2sInfo(None), None);
|
||||||
|
let mut buf = client.receive();
|
||||||
|
|
||||||
|
if buf[4] == 0x41 {
|
||||||
|
client.do_request(Request::A2sInfo(Some([buf[5], buf[6], buf[7], buf[8]])), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = client.receive_with_size(256);
|
||||||
|
println!("{:x?}", &buf);
|
||||||
|
|
||||||
|
let name_nul_pos = find_null_in_array(&mut buf);
|
||||||
|
let map_nul_pos = name_nul_pos + 1 + find_null_in_array(&mut buf[name_nul_pos + 1..]);
|
||||||
|
let folder_nul_pos = map_nul_pos + 1 + find_null_in_array(&mut buf[map_nul_pos + 1..]);
|
||||||
|
let game_nul_pos = folder_nul_pos + 1 + find_null_in_array(&mut buf[folder_nul_pos + 1..]);
|
||||||
|
|
||||||
|
let server_type = match buf[game_nul_pos + 6] as char {
|
||||||
|
'd' => Server::Dedicated,
|
||||||
|
'l' => Server::NonDedicated,
|
||||||
|
_ => Server::SourceTV
|
||||||
|
};
|
||||||
|
|
||||||
|
let environment_type = match buf[game_nul_pos + 7] as char {
|
||||||
|
'l' => Environment::Linux,
|
||||||
|
'w' => Environment::Windows,
|
||||||
|
_ => Environment::Mac
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Response {
|
||||||
|
protocol: buf[5],
|
||||||
|
name: String::from_utf8(Vec::from(&mut buf[6..name_nul_pos])).expect("cacat"),
|
||||||
|
map: String::from_utf8(Vec::from(&mut buf[name_nul_pos + 1..map_nul_pos])).expect("cacat"),
|
||||||
|
folder: String::from_utf8(Vec::from(&mut buf[map_nul_pos + 1..folder_nul_pos])).expect("cacat"),
|
||||||
|
game: String::from_utf8(Vec::from(&mut buf[folder_nul_pos + 1..game_nul_pos])).expect("cacat"),
|
||||||
|
id: combine_two_u8(buf[game_nul_pos + 2], buf[game_nul_pos + 1]),
|
||||||
|
players: buf[game_nul_pos + 3],
|
||||||
|
max_players: buf[game_nul_pos + 4],
|
||||||
|
bots: buf[game_nul_pos + 5],
|
||||||
|
server_type,
|
||||||
|
environment_type,
|
||||||
|
has_password: buf[game_nul_pos + 8] == 1,
|
||||||
|
vac_secured: buf[game_nul_pos + 9] != 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/utils.rs
Normal file
20
src/utils.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
pub fn concat_u8(first: &[u8], second: &[u8]) -> Vec<u8> {
|
||||||
|
[first, second].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_null_in_array(arr: &[u8]) -> usize {
|
||||||
|
match arr.iter().position(|&x| x == 0) {
|
||||||
|
None => arr.len(),
|
||||||
|
Some(position) => position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete_address(address: &str, port: u16) -> String {
|
||||||
|
String::from(address.to_owned() + ":").add(&*port.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn combine_two_u8(high: u8, low: u8) -> u16 {
|
||||||
|
((high as u16) << 8) | low as u16
|
||||||
|
}
|
||||||
15
tests/test.rs
Normal file
15
tests/test.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use gamedig::protocols::valve::ValveProtocol;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tf2() {
|
||||||
|
let response = ValveProtocol::query("5.15.202.107", 27015);
|
||||||
|
match response {
|
||||||
|
Err(_) => println!("fuck"),
|
||||||
|
Ok(r) => println!("{}", r.name)
|
||||||
|
}
|
||||||
|
assert_eq!(4, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue