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;
use packet_type::PacketType;
@ -6,7 +10,7 @@ use packet_type::PacketType;
pub enum RconError {
Io(io::Error),
AuthFailed,
InvalidResponse(String)
InvalidResponse(String),
}
pub type RconResult<T> = Result<T, RconError>;
@ -15,7 +19,7 @@ impl Display for RconError {
match self {
RconError::Io(e) => write!(f, "io error: {e}"),
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 {
stream: TcpStream,
next_id: i32
next_id: i32,
}
impl RconClient {
@ -41,13 +45,19 @@ impl RconClient {
fn recv_packet(&mut self) -> RconResult<(i32, PacketType, String)> {
let length = self.read_i32_le()?;
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 ptype = match PacketType::try_from(self.read_i32_le()?) {
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;
@ -91,7 +101,7 @@ impl RconClient {
Ok(())
}
pub fn connect(addr: impl ToSocketAddrs, password: &str) -> RconResult<Self> {
let stream = TcpStream::connect(addr)?;
let mut client = RconClient { stream, next_id: 1 };
@ -104,12 +114,16 @@ impl RconClient {
let (resp_id, resp_type, payload) = self.recv_packet()?;
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 {
return Err(RconError::InvalidResponse("mismatched packet id".to_string()))
return Err(RconError::InvalidResponse(
"mismatched packet id".to_string(),
));
}
Ok(payload)
}
}
}

View file

@ -2,7 +2,7 @@
pub enum PacketType {
Login = 3,
Command = 2,
Response = 0
Response = 0,
}
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::Command as i32 => Ok(Self::Command),
x if x == Self::Response as i32 => Ok(Self::Response),
_ => Err(())
_ => Err(()),
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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