This commit is contained in:
Raptorox 2026-08-01 19:17:08 +02:00
parent 1485cf975d
commit d43b588e51
No known key found for this signature in database
GPG key ID: 8B3556FC3ED1F6D8
12 changed files with 116 additions and 77 deletions

View file

@ -1,4 +1,8 @@
use std::{fmt::Display, io::{self, Read, Write}, net::{TcpStream, ToSocketAddrs}}; use std::{
fmt::Display,
io::{self, Read, Write},
net::{TcpStream, ToSocketAddrs},
};
mod packet_type; mod packet_type;
use packet_type::PacketType; use packet_type::PacketType;
@ -6,7 +10,7 @@ use packet_type::PacketType;
pub enum RconError { pub enum RconError {
Io(io::Error), Io(io::Error),
AuthFailed, AuthFailed,
InvalidResponse(String) InvalidResponse(String),
} }
pub type RconResult<T> = Result<T, RconError>; pub type RconResult<T> = Result<T, RconError>;
@ -15,7 +19,7 @@ impl Display for RconError {
match self { match self {
RconError::Io(e) => write!(f, "io error: {e}"), RconError::Io(e) => write!(f, "io error: {e}"),
RconError::AuthFailed => write!(f, "auth failed"), RconError::AuthFailed => write!(f, "auth failed"),
RconError::InvalidResponse(e) => write!(f, "invalid response from server: {e}") RconError::InvalidResponse(e) => write!(f, "invalid response from server: {e}"),
} }
} }
} }
@ -28,7 +32,7 @@ impl From<io::Error> for RconError {
pub struct RconClient { pub struct RconClient {
stream: TcpStream, stream: TcpStream,
next_id: i32 next_id: i32,
} }
impl RconClient { impl RconClient {
@ -41,13 +45,19 @@ impl RconClient {
fn recv_packet(&mut self) -> RconResult<(i32, PacketType, String)> { fn recv_packet(&mut self) -> RconResult<(i32, PacketType, String)> {
let length = self.read_i32_le()?; let length = self.read_i32_le()?;
if length < 10 { if length < 10 {
return Err(RconError::InvalidResponse("response length < 10".to_string())) return Err(RconError::InvalidResponse(
"response length < 10".to_string(),
));
} }
let id = self.read_i32_le()?; let id = self.read_i32_le()?;
let ptype = match PacketType::try_from(self.read_i32_le()?) { let ptype = match PacketType::try_from(self.read_i32_le()?) {
Ok(ptype) => ptype, Ok(ptype) => ptype,
Err(()) => return Err(RconError::InvalidResponse("unknown packet type".to_string())) Err(()) => {
return Err(RconError::InvalidResponse(
"unknown packet type".to_string(),
));
}
}; };
let payload_len = (length - 10) as usize; let payload_len = (length - 10) as usize;
@ -91,7 +101,7 @@ impl RconClient {
Ok(()) Ok(())
} }
pub fn connect(addr: impl ToSocketAddrs, password: &str) -> RconResult<Self> { pub fn connect(addr: impl ToSocketAddrs, password: &str) -> RconResult<Self> {
let stream = TcpStream::connect(addr)?; let stream = TcpStream::connect(addr)?;
let mut client = RconClient { stream, next_id: 1 }; let mut client = RconClient { stream, next_id: 1 };
@ -104,12 +114,16 @@ impl RconClient {
let (resp_id, resp_type, payload) = self.recv_packet()?; let (resp_id, resp_type, payload) = self.recv_packet()?;
if resp_type != PacketType::Response { if resp_type != PacketType::Response {
return Err(RconError::InvalidResponse("received packet not of response type".to_string())) return Err(RconError::InvalidResponse(
"received packet not of response type".to_string(),
));
} }
if resp_id != id { if resp_id != id {
return Err(RconError::InvalidResponse("mismatched packet id".to_string())) return Err(RconError::InvalidResponse(
"mismatched packet id".to_string(),
));
} }
Ok(payload) Ok(payload)
} }
} }

View file

@ -2,7 +2,7 @@
pub enum PacketType { pub enum PacketType {
Login = 3, Login = 3,
Command = 2, Command = 2,
Response = 0 Response = 0,
} }
impl PacketType { impl PacketType {
@ -19,7 +19,7 @@ impl TryFrom<i32> for PacketType {
x if x == Self::Login as i32 => Ok(Self::Login), x if x == Self::Login as i32 => Ok(Self::Login),
x if x == Self::Command as i32 => Ok(Self::Command), x if x == Self::Command as i32 => Ok(Self::Command),
x if x == Self::Response as i32 => Ok(Self::Response), x if x == Self::Response as i32 => Ok(Self::Response),
_ => Err(()) _ => Err(()),
} }
} }
} }

View file

@ -1,12 +1,14 @@
use super::{Command, Selector};
#[derive(Default)] #[derive(Default)]
pub struct BanCommand { pub struct BanCommand {
target: Option<super::Selector>, target: Option<Selector>,
reason: Option<String>, reason: Option<String>,
ip: Option<bool> ip: Option<bool>,
} }
impl BanCommand { impl BanCommand {
pub fn target(mut self, target: super::Selector) -> Self { pub fn target(mut self, target: Selector) -> Self {
self.target = Some(target); self.target = Some(target);
self self
} }
@ -21,13 +23,19 @@ impl BanCommand {
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let target = self.target.unwrap_or(super::Selector::Player("nonexistent".to_string())); let target = self
.target
.unwrap_or(Selector::Player("nonexistent".to_string()));
let ip = self.ip.unwrap_or(false); let ip = self.ip.unwrap_or(false);
let mut s = if ip { format!("ban-ip {target}") } else { format!("ban {target}") }; let mut s = if ip {
format!("ban-ip {target}")
} else {
format!("ban {target}")
};
if let Some(reason) = self.reason { if let Some(reason) = self.reason {
s.push_str(&format!(" {reason}")); s.push_str(&format!(" {reason}"));
}; };
super::Command(s) Command(s)
} }
} }

View file

@ -1,8 +1,9 @@
use super::Command;
use std::marker::PhantomData; use std::marker::PhantomData;
#[derive(Default)] #[derive(Default)]
pub struct BanlistCommand<State> { pub struct BanlistCommand<State> {
state: PhantomData<State> state: PhantomData<State>,
} }
pub struct NoFilter; pub struct NoFilter;
@ -22,19 +23,19 @@ impl BanlistCommand<NoFilter> {
BanlistCommand { state: PhantomData } BanlistCommand { state: PhantomData }
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
super::Command::raw("banlist") Command::raw("banlist")
} }
} }
impl BanlistCommand<Players> { impl BanlistCommand<Players> {
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
super::Command::raw("banlist players") Command::raw("banlist players")
} }
} }
impl BanlistCommand<Ips> { impl BanlistCommand<Ips> {
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
super::Command::raw("banlist ips") Command::raw("banlist ips")
} }
} }

View file

@ -1,17 +1,19 @@
use super::{Command, Gamemode};
#[derive(Default)] #[derive(Default)]
pub struct DefaultGamemodeCommand { pub struct DefaultGamemodeCommand {
gamemode: Option<super::Gamemode> gamemode: Option<Gamemode>,
} }
impl DefaultGamemodeCommand { impl DefaultGamemodeCommand {
pub fn gamemode(mut self, gamemode: super::Gamemode) -> Self { pub fn gamemode(mut self, gamemode: Gamemode) -> Self {
self.gamemode = Some(gamemode); self.gamemode = Some(gamemode);
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let gamemode = self.gamemode.unwrap_or(super::Gamemode::Survival); let gamemode = self.gamemode.unwrap_or(Gamemode::Survival);
let s = format!("defaultgamemode {gamemode}"); let s = format!("defaultgamemode {gamemode}");
super::Command(s) Command(s)
} }
} }

View file

@ -1,17 +1,19 @@
use super::{Command, Selector};
#[derive(Default)] #[derive(Default)]
pub struct DeopCommand { pub struct DeopCommand {
target: Option<super::Selector> target: Option<Selector>,
} }
impl DeopCommand { impl DeopCommand {
pub fn target(mut self, target: super::Selector) -> Self { pub fn target(mut self, target: Selector) -> Self {
self.target = Some(target); self.target = Some(target);
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let target = self.target.unwrap_or(super::Selector::Executor); let target = self.target.unwrap_or(Selector::Executor);
let s = format!("deop {target}"); let s = format!("deop {target}");
super::Command(s) Command(s)
} }
} }

View file

@ -1,19 +1,21 @@
use super::{Command, Difficulty};
#[derive(Default)] #[derive(Default)]
pub struct DifficultyCommand { pub struct DifficultyCommand {
difficulty: Option<super::Difficulty> difficulty: Option<Difficulty>
} }
impl DifficultyCommand { impl DifficultyCommand {
pub fn difficulty(mut self, difficulty: super::Difficulty) -> Self { pub fn difficulty(mut self, difficulty: Difficulty) -> Self {
self.difficulty = Some(difficulty); self.difficulty = Some(difficulty);
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let mut s = format!("difficulty"); let mut s = format!("difficulty");
if let Some(difficulty) = self.difficulty { if let Some(difficulty) = self.difficulty {
s.push_str(&format!(" {difficulty}")) s.push_str(&format!(" {difficulty}"))
}; };
super::Command(s) Command(s)
} }
} }

View file

@ -1,26 +1,28 @@
use super::{Command, Gamemode, Selector};
#[derive(Default)] #[derive(Default)]
pub struct GamemodeCommand { pub struct GamemodeCommand {
gamemode: Option<super::Gamemode>, gamemode: Option<Gamemode>,
target: Option<super::Selector> target: Option<Selector>
} }
impl GamemodeCommand { impl GamemodeCommand {
pub fn gamemode(mut self, gamemode: super::Gamemode) -> Self { pub fn gamemode(mut self, gamemode: Gamemode) -> Self {
self.gamemode = Some(gamemode); self.gamemode = Some(gamemode);
self self
} }
pub fn target(mut self, target: super::Selector) -> Self { pub fn target(mut self, target: Selector) -> Self {
self.target = Some(target); self.target = Some(target);
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let gamemode = self.gamemode.unwrap_or(super::Gamemode::Survival); let gamemode = self.gamemode.unwrap_or(Gamemode::Survival);
let mut s = format!("defaultgamemode {gamemode}"); let mut s = format!("defaultgamemode {gamemode}");
if let Some(target) = self.target { if let Some(target) = self.target {
s.push_str(&format!(" {target}")) s.push_str(&format!(" {target}"))
} }
super::Command(s) Command(s)
} }
} }

View file

@ -1,17 +1,19 @@
use super::{Command, Selector};
#[derive(Default)] #[derive(Default)]
pub struct KillCommand { pub struct KillCommand {
target: Option<super::Selector> target: Option<Selector>,
} }
impl KillCommand { impl KillCommand {
pub fn target(mut self, target: super::Selector) -> Self { pub fn target(mut self, target: Selector) -> Self {
self.target = Some(target); self.target = Some(target);
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let target = self.target.unwrap_or(super::Selector::Executor); let target = self.target.unwrap_or(Selector::Executor);
let s = format!("kill {target}"); let s = format!("kill {target}");
super::Command(s) Command(s)
} }
} }

View file

@ -1,17 +1,19 @@
use super::Command;
#[derive(Default)] #[derive(Default)]
pub struct ListCommand { pub struct ListCommand {
uuids: Option<bool> uuids: Option<bool>,
} }
impl ListCommand { impl ListCommand {
pub fn uuids(mut self) -> Self { pub fn uuids(mut self) -> Self {
self.uuids = Some(true); self.uuids = Some(true);
self self
}
pub fn build(self) -> super::Command {
let uuids = self.uuids.unwrap_or(false);
let s = format!("list {}", if uuids { "uuids" } else {""} );
super::Command(s)
} }
}
pub fn build(self) -> Command {
let uuids = self.uuids.unwrap_or(false);
let s = format!("list {}", if uuids { "uuids" } else { "" });
Command(s)
}
}

View file

@ -1,17 +1,19 @@
use super::{Command, Selector};
#[derive(Default)] #[derive(Default)]
pub struct OpCommand { pub struct OpCommand {
target: Option<super::Selector> target: Option<Selector>,
} }
impl OpCommand { impl OpCommand {
pub fn target(mut self, target: super::Selector) -> Self { pub fn target(mut self, target: Selector) -> Self {
self.target = Some(target); self.target = Some(target);
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let target = self.target.unwrap_or(super::Selector::Executor); let target = self.target.unwrap_or(Selector::Executor);
let s = format!("op {target}"); let s = format!("op {target}");
super::Command(s) Command(s)
} }
} }

View file

@ -1,17 +1,19 @@
use super::Command;
#[derive(Default)] #[derive(Default)]
pub struct SayCommand { pub struct SayCommand {
message: Option<String> message: Option<String>,
} }
impl SayCommand { impl SayCommand {
pub fn message(mut self, msg: impl Into<String>) -> Self { pub fn message(mut self, msg: impl Into<String>) -> Self {
self.message = Some(msg.into()); self.message = Some(msg.into());
self self
} }
pub fn build(self) -> super::Command { pub fn build(self) -> Command {
let message = self.message.unwrap_or("Hello, world!".to_string()); let message = self.message.unwrap_or("Hello, world!".to_string());
let s = format!("say {message}"); let s = format!("say {message}");
super::Command(s) Command(s)
} }
} }