mirror of
https://git.mentality.rip/numas13/xash3d-master.git
synced 2025-01-09 06:28:03 +00:00
Add update client message
This commit is contained in:
parent
c587e0dab7
commit
ed83a77b3b
@ -13,3 +13,10 @@ port = 27010
|
||||
challenge = 300
|
||||
# Time in seconds while server is valid
|
||||
server = 300
|
||||
|
||||
[client]
|
||||
# If client version is less then show update message
|
||||
version = "0.20"
|
||||
update_title = "https://github.com/FWGS/xash3d-fwgs"
|
||||
update_map = "Update please"
|
||||
update_addr = "127.0.0.1:27010"
|
||||
|
@ -41,6 +41,7 @@ pub enum Packet<'a> {
|
||||
ServerAdd(Option<u32>, ServerInfo<&'a str>),
|
||||
ServerRemove,
|
||||
QueryServers(Region, Filter<'a>),
|
||||
ServerInfo,
|
||||
}
|
||||
|
||||
impl<'a> Packet<'a> {
|
||||
@ -70,6 +71,7 @@ impl<'a> Packet<'a> {
|
||||
}
|
||||
[b'b', b'\n'] => Ok(Self::ServerRemove),
|
||||
[b'q'] => Ok(Self::Challenge(None)),
|
||||
[0xff, 0xff, 0xff, 0xff, b'i', b'n', b'f', b'o', b' ', _, _] => Ok(Self::ServerInfo),
|
||||
_ => Err(Error::InvalidPacket),
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,15 @@
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
|
||||
use std::path::Path;
|
||||
|
||||
use log::LevelFilter;
|
||||
use serde::{de::Error as _, Deserialize, Deserializer};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::filter::Version;
|
||||
|
||||
pub const DEFAULT_CONFIG_PATH: &str = "config/main.toml";
|
||||
|
||||
pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
@ -31,6 +33,8 @@ pub struct Config {
|
||||
pub log: LogConfig,
|
||||
#[serde(default)]
|
||||
pub server: ServerConfig,
|
||||
#[serde(default)]
|
||||
pub client: ClientConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@ -88,6 +92,20 @@ impl Default for TimeoutConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ClientConfig {
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_version")]
|
||||
pub version: Version,
|
||||
#[serde(default)]
|
||||
pub update_map: Box<str>,
|
||||
#[serde(default)]
|
||||
pub update_title: Box<str>,
|
||||
#[serde(default)]
|
||||
pub update_addr: Option<SocketAddrV4>,
|
||||
}
|
||||
|
||||
fn default_log_level() -> LevelFilter {
|
||||
LevelFilter::Warn
|
||||
}
|
||||
@ -109,8 +127,7 @@ where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <&str>::deserialize(de)?;
|
||||
parse_log_level(s)
|
||||
.ok_or_else(|| D::Error::custom(format!("Invalid value for log option: \"{}\"", s)))
|
||||
parse_log_level(s).ok_or_else(|| D::Error::custom(format!("Invalid log level: \"{}\"", s)))
|
||||
}
|
||||
|
||||
pub fn parse_log_level(s: &str) -> Option<LevelFilter> {
|
||||
@ -136,6 +153,15 @@ pub fn parse_log_level(s: &str) -> Option<LevelFilter> {
|
||||
Some(level_filter)
|
||||
}
|
||||
|
||||
fn deserialize_version<'de, D>(de: D) -> Result<Version, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <&str>::deserialize(de)?;
|
||||
s.parse()
|
||||
.map_err(|_| D::Error::custom(format!("Invalid version: \"{}\"", s)))
|
||||
}
|
||||
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
|
||||
let data = fs::read(path)?;
|
||||
Ok(toml::de::from_slice(&data)?)
|
||||
|
@ -28,7 +28,10 @@
|
||||
//! * Do not have bots
|
||||
//! * Is not protected by a password
|
||||
|
||||
use std::fmt;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use log::{debug, log_enabled, Level};
|
||||
@ -79,6 +82,49 @@ impl<T> From<&ServerInfo<T>> for FilterFlags {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Version {
|
||||
pub major: u8,
|
||||
pub minor: u8,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub fn new(major: u8, minor: u8) -> Self {
|
||||
Self { major, minor }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Version {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}.{}", self.major, self.minor)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}.{}", self.major, self.minor)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (major, minor) = s.split_once('.').unwrap_or((s, "0"));
|
||||
Ok(Self::new(major.parse()?, minor.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue<'_> for Version {
|
||||
type Err = ParserError;
|
||||
|
||||
fn parse(p: &mut Parser<'_>) -> Result<Self, Self::Err> {
|
||||
let s = p.parse::<&str>()?;
|
||||
let v = s.parse()?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Filter<'a> {
|
||||
/// Servers running the specified modification (ex. cstrike)
|
||||
@ -92,7 +138,7 @@ pub struct Filter<'a> {
|
||||
/// Return only servers on the specified IP address (port supported and optional)
|
||||
pub gameaddr: Option<SocketAddrV4>,
|
||||
/// Client version.
|
||||
pub clver: Option<&'a str>,
|
||||
pub clver: Option<Version>,
|
||||
|
||||
pub flags: FilterFlags,
|
||||
pub flags_mask: FilterFlags,
|
||||
|
@ -14,7 +14,7 @@ use thiserror::Error;
|
||||
|
||||
use crate::client::Packet;
|
||||
use crate::config::{self, Config};
|
||||
use crate::filter::Filter;
|
||||
use crate::filter::{Filter, Version};
|
||||
use crate::server::Server;
|
||||
use crate::server_info::Region;
|
||||
|
||||
@ -74,13 +74,19 @@ impl<T> Deref for Entry<T> {
|
||||
|
||||
struct MasterServer {
|
||||
sock: UdpSocket,
|
||||
start_time: Instant,
|
||||
challenges: HashMap<SocketAddrV4, Entry<u32>>,
|
||||
servers: HashMap<SocketAddrV4, Entry<Server>>,
|
||||
rng: Rng,
|
||||
|
||||
start_time: Instant,
|
||||
cleanup_challenges: usize,
|
||||
cleanup_servers: usize,
|
||||
timeout: config::TimeoutConfig,
|
||||
|
||||
clver: Version,
|
||||
update_title: Box<str>,
|
||||
update_map: Box<str>,
|
||||
update_addr: SocketAddrV4,
|
||||
}
|
||||
|
||||
impl MasterServer {
|
||||
@ -88,6 +94,13 @@ impl MasterServer {
|
||||
let addr = SocketAddr::new(cfg.server.ip, cfg.server.port);
|
||||
info!("Listen address: {}", addr);
|
||||
let sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?;
|
||||
let update_addr =
|
||||
cfg.client
|
||||
.update_addr
|
||||
.unwrap_or_else(|| match sock.local_addr().unwrap() {
|
||||
SocketAddr::V4(addr) => addr,
|
||||
_ => todo!(),
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
sock,
|
||||
@ -98,6 +111,10 @@ impl MasterServer {
|
||||
cleanup_challenges: 0,
|
||||
cleanup_servers: 0,
|
||||
timeout: cfg.server.timeout,
|
||||
clver: cfg.client.version,
|
||||
update_title: cfg.client.update_title,
|
||||
update_map: cfg.client.update_map,
|
||||
update_addr,
|
||||
})
|
||||
}
|
||||
|
||||
@ -120,7 +137,14 @@ impl MasterServer {
|
||||
}
|
||||
|
||||
fn handle_packet(&mut self, from: SocketAddrV4, s: &[u8]) -> Result<(), Error> {
|
||||
let packet = Packet::decode(s)?;
|
||||
let packet = match Packet::decode(s) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
trace!("{}: Failed to decode {:?}", from, s);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
trace!("{}: recv {:?}", from, packet);
|
||||
|
||||
match packet {
|
||||
@ -158,13 +182,48 @@ impl MasterServer {
|
||||
self.remove_outdated_servers();
|
||||
}
|
||||
Packet::ServerRemove => { /* ignore */ }
|
||||
Packet::QueryServers(region, filter) => match Filter::from_bytes(&filter) {
|
||||
Ok(filter) => self.send_server_list(from, region, &filter)?,
|
||||
_ => {
|
||||
warn!("{}: Invalid filter: {:?}", from, filter);
|
||||
return Ok(());
|
||||
Packet::QueryServers(region, filter) => {
|
||||
let filter = match Filter::from_bytes(&filter) {
|
||||
Ok(f) => f,
|
||||
_ => {
|
||||
warn!("{}: Invalid filter: {:?}", from, filter);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if filter.clver.map_or(true, |v| v < self.clver) {
|
||||
let iter = std::iter::once(&self.update_addr);
|
||||
self.send_server_list(from, iter)?;
|
||||
} else {
|
||||
let now = self.now();
|
||||
let iter = self
|
||||
.servers
|
||||
.iter()
|
||||
.filter(|i| i.1.is_valid(now, self.timeout.server))
|
||||
.filter(|i| i.1.matches(*i.0, region, &filter))
|
||||
.map(|i| i.0);
|
||||
self.send_server_list(from, iter)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
Packet::ServerInfo => {
|
||||
let mut buf = [0; MAX_PACKET_SIZE];
|
||||
let mut cur = Cursor::new(&mut buf[..]);
|
||||
cur.write_all(b"\xff\xff\xff\xffinfo\n")?;
|
||||
cur.write_all(b"\\p\\49")?;
|
||||
cur.write_all(b"\\map\\")?;
|
||||
cur.write_all(self.update_map.as_bytes())?;
|
||||
cur.write_all(b"\\dm\\1")?;
|
||||
cur.write_all(b"\\team\\0")?;
|
||||
cur.write_all(b"\\coop\\0")?;
|
||||
cur.write_all(b"\\numcl\\0")?;
|
||||
cur.write_all(b"\\maxcl\\0")?;
|
||||
cur.write_all(b"\\gamedir\\valve")?;
|
||||
cur.write_all(b"\\password\\0")?;
|
||||
cur.write_all(b"\\host\\")?;
|
||||
cur.write_all(self.update_title.as_bytes())?;
|
||||
let n = cur.position() as usize;
|
||||
self.sock.send_to(&buf[..n], from)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -240,20 +299,11 @@ impl MasterServer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_server_list<A: ToSocketAddrs>(
|
||||
&self,
|
||||
to: A,
|
||||
region: Region,
|
||||
filter: &Filter,
|
||||
) -> Result<(), io::Error> {
|
||||
let now = self.now();
|
||||
let mut iter = self
|
||||
.servers
|
||||
.iter()
|
||||
.filter(|i| i.1.is_valid(now, self.timeout.server))
|
||||
.filter(|i| i.1.matches(*i.0, region, filter))
|
||||
.map(|i| i.0);
|
||||
|
||||
fn send_server_list<'a, A, I>(&self, to: A, mut iter: I) -> Result<(), io::Error>
|
||||
where
|
||||
A: ToSocketAddrs,
|
||||
I: Iterator<Item = &'a SocketAddrV4>,
|
||||
{
|
||||
let mut buf = [0; MAX_PACKET_SIZE];
|
||||
let mut done = false;
|
||||
while !done {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
||||
|
||||
use std::num::ParseIntError;
|
||||
use std::str;
|
||||
|
||||
use thiserror::Error;
|
||||
@ -19,6 +20,12 @@ pub enum Error {
|
||||
InvalidInteger,
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(_: ParseIntError) -> Self {
|
||||
Error::InvalidInteger
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
pub struct Parser<'a> {
|
||||
|
Loading…
Reference in New Issue
Block a user