Extract all packet writing logic to single function

This commit is contained in:
timvisee 2021-11-16 17:57:34 +01:00
parent 7df3829e00
commit 47fe7d0387
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
6 changed files with 236 additions and 294 deletions

4
Cargo.lock generated
View File

@ -685,7 +685,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]] [[package]]
name = "minecraft-protocol" name = "minecraft-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=d26a525#d26a525c7b29b61d2db64805181fb5471ea4317a" source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=4e6a1f9#4e6a1f93807f35671943630c9bdc0d0c5da67eb8"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"minecraft-protocol-derive", "minecraft-protocol-derive",
@ -698,7 +698,7 @@ dependencies = [
[[package]] [[package]]
name = "minecraft-protocol-derive" name = "minecraft-protocol-derive"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=d26a525#d26a525c7b29b61d2db64805181fb5471ea4317a" source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=4e6a1f9#4e6a1f93807f35671943630c9bdc0d0c5da67eb8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -40,7 +40,7 @@ dotenv = "0.15"
flate2 = { version = "1.0", default-features = false, features = ["default"] } flate2 = { version = "1.0", default-features = false, features = ["default"] }
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
log = "0.4" log = "0.4"
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "d26a525" } minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "4e6a1f9" }
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
rand = "0.8" rand = "0.8"
serde = "1.0" serde = "1.0"

View File

@ -8,15 +8,13 @@ use bytes::BytesMut;
use futures::FutureExt; use futures::FutureExt;
use minecraft_protocol::data::chat::{Message, Payload}; use minecraft_protocol::data::chat::{Message, Payload};
use minecraft_protocol::decoder::Decoder; use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake; use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_14_4::login::{LoginStart, LoginSuccess, SetCompression}; use minecraft_protocol::version::v1_14_4::login::{LoginStart, LoginSuccess, SetCompression};
use minecraft_protocol::version::v1_17_1::game::{ use minecraft_protocol::version::v1_17_1::game::{
ClientBoundKeepAlive, JoinGame, NamedSoundEffect, PlayerPositionAndLook, PluginMessage, ClientBoundKeepAlive, ClientBoundPluginMessage, JoinGame, NamedSoundEffect,
Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate, PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate,
}; };
use nbt::CompoundTag; use nbt::CompoundTag;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::select; use tokio::select;
@ -28,8 +26,7 @@ use crate::mc;
use crate::net; use crate::net;
use crate::proto; use crate::proto;
use crate::proto::client::{Client, ClientInfo, ClientState}; use crate::proto::client::{Client, ClientInfo, ClientState};
use crate::proto::packet::{self, RawPacket}; use crate::proto::{packet, packets};
use crate::proto::packets;
use crate::proxy; use crate::proxy;
use crate::server::{Server, State}; use crate::server::{Server, State};
@ -185,15 +182,7 @@ async fn respond_set_compression(
writer: &mut WriteHalf<'_>, writer: &mut WriteHalf<'_>,
threshold: i32, threshold: i32,
) -> Result<(), ()> { ) -> Result<(), ()> {
let packet = SetCompression { threshold }; packet::write_packet(SetCompression { threshold }, client, writer).await
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::login::CLIENT_SET_COMPRESSION, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Respond to client with login success packet /// Respond to client with login success packet
@ -203,21 +192,18 @@ async fn respond_login_success(
writer: &mut WriteHalf<'_>, writer: &mut WriteHalf<'_>,
login_start: &LoginStart, login_start: &LoginStart,
) -> Result<(), ()> { ) -> Result<(), ()> {
let packet = LoginSuccess { packet::write_packet(
uuid: Uuid::new_v3( LoginSuccess {
&Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"), uuid: Uuid::new_v3(
login_start.name.as_bytes(), &Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"),
), login_start.name.as_bytes(),
username: login_start.name.clone(), ),
}; username: login_start.name.clone(),
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::login::CLIENT_LOGIN_SUCCESS, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Play lobby ready sound effect if configured. /// Play lobby ready sound effect if configured.
@ -271,84 +257,75 @@ async fn send_lobby_join_game(
server: &Server, server: &Server,
) -> Result<(), ()> { ) -> Result<(), ()> {
// Send Minecrafts default states, slightly customised for lobby world // Send Minecrafts default states, slightly customised for lobby world
let packet = { packet::write_packet(
let status = server.status().await; {
let status = server.status().await;
JoinGame { JoinGame {
// Player ID must be unique, if it collides with another server entity ID the player gets // Player ID must be unique, if it collides with another server entity ID the player gets
// in a weird state and cannot move // in a weird state and cannot move
entity_id: 0, entity_id: 0,
// TODO: use real server value // TODO: use real server value
hardcore: false, hardcore: false,
game_mode: 3, game_mode: 3,
previous_game_mode: -1i8 as u8, previous_game_mode: -1i8 as u8,
world_names: vec![ world_names: vec![
"minecraft:overworld".into(), "minecraft:overworld".into(),
"minecraft:the_nether".into(), "minecraft:the_nether".into(),
"minecraft:the_end".into(), "minecraft:the_end".into(),
], ],
dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")), dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")),
dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")), dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")),
world_name: "lazymc:lobby".into(), world_name: "lazymc:lobby".into(),
hashed_seed: 0, hashed_seed: 0,
max_players: status.as_ref().map(|s| s.players.max as i32).unwrap_or(20), max_players: status.as_ref().map(|s| s.players.max as i32).unwrap_or(20),
// TODO: use real server value // TODO: use real server value
view_distance: 10, view_distance: 10,
// TODO: use real server value // TODO: use real server value
reduced_debug_info: false, reduced_debug_info: false,
// TODO: use real server value // TODO: use real server value
enable_respawn_screen: true, enable_respawn_screen: true,
is_debug: true, is_debug: true,
is_flat: false, is_flat: false,
} }
}; },
client,
let mut data = Vec::new(); writer,
packet.encode(&mut data).map_err(|_| ())?; )
.await
let response = RawPacket::new(packets::play::CLIENT_JOIN_GAME, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Send lobby brand to client. /// Send lobby brand to client.
async fn send_lobby_brand(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> { async fn send_lobby_brand(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = PluginMessage { packet::write_packet(
channel: "minecraft:brand".into(), ClientBoundPluginMessage {
data: SERVER_BRAND.into(), channel: "minecraft:brand".into(),
}; data: SERVER_BRAND.into(),
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_PLUGIN_MESSAGE, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Send lobby player position to client. /// Send lobby player position to client.
async fn send_lobby_player_pos(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> { async fn send_lobby_player_pos(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
// Send player location, disables download terrain screen // Send player location, disables download terrain screen
let packet = PlayerPositionAndLook { packet::write_packet(
x: 0.0, PlayerPositionAndLook {
y: 0.0, x: 0.0,
z: 0.0, y: 0.0,
yaw: 0.0, z: 0.0,
pitch: 90.0, yaw: 0.0,
flags: 0b00000000, pitch: 90.0,
teleport_id: 0, flags: 0b00000000,
dismount_vehicle: true, teleport_id: 0,
}; dismount_vehicle: true,
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_PLAYER_POS_LOOK, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Send lobby time update to client. /// Send lobby time update to client.
@ -356,38 +333,32 @@ async fn send_lobby_time_update(client: &Client, writer: &mut WriteHalf<'_>) ->
const MC_TIME_NOON: i64 = 6000; const MC_TIME_NOON: i64 = 6000;
// Send time update, required once for keep-alive packets // Send time update, required once for keep-alive packets
let packet = TimeUpdate { packet::write_packet(
world_age: MC_TIME_NOON, TimeUpdate {
time_of_day: MC_TIME_NOON, world_age: MC_TIME_NOON,
}; time_of_day: MC_TIME_NOON,
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_TIME_UPDATE, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Send keep alive packet to client. /// Send keep alive packet to client.
/// ///
/// Required periodically in play mode to prevent client timeout. /// Required periodically in play mode to prevent client timeout.
async fn send_keep_alive(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> { async fn send_keep_alive(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = ClientBoundKeepAlive { packet::write_packet(
// Keep sending new IDs ClientBoundKeepAlive {
id: KEEP_ALIVE_ID.fetch_add(1, Ordering::Relaxed), // Keep sending new IDs
}; id: KEEP_ALIVE_ID.fetch_add(1, Ordering::Relaxed),
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_KEEP_ALIVE, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
// TODO: verify we receive keep alive response with same ID from client // TODO: verify we receive keep alive response with same ID from client
Ok(())
} }
/// Send lobby title packets to client. /// Send lobby title packets to client.
@ -405,50 +376,45 @@ async fn send_lobby_title(
let subtitle = text.lines().skip(1).collect::<Vec<_>>().join("\n"); let subtitle = text.lines().skip(1).collect::<Vec<_>>().join("\n");
// Set title // Set title
let packet = SetTitleText { packet::write_packet(
text: Message::new(Payload::text(title)), SetTitleText {
}; text: Message::new(Payload::text(title)),
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_SET_TITLE_TEXT, data).encode(client)?; .await?;
writer.write_all(&response).await.map_err(|_| ())?;
// Set subtitle // Set subtitle
let packet = SetTitleSubtitle { packet::write_packet(
text: Message::new(Payload::text(&subtitle)), SetTitleSubtitle {
}; text: Message::new(Payload::text(&subtitle)),
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_SET_TITLE_SUBTITLE, data).encode(client)?; .await?;
writer.write_all(&response).await.map_err(|_| ())?;
// Set title times // Set title times
let packet = if title.is_empty() && subtitle.is_empty() { packet::write_packet(
// Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail if title.is_empty() && subtitle.is_empty() {
SetTitleTimes { // Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
fade_in: 10, SetTitleTimes {
stay: 70, fade_in: 10,
fade_out: 20, stay: 70,
} fade_out: 20,
} else { }
SetTitleTimes { } else {
fade_in: 0, SetTitleTimes {
stay: KEEP_ALIVE_INTERVAL.as_secs() as i32 * mc::TICKS_PER_SECOND as i32 * 2, fade_in: 0,
fade_out: 0, stay: KEEP_ALIVE_INTERVAL.as_secs() as i32 * mc::TICKS_PER_SECOND as i32 * 2,
} fade_out: 0,
}; }
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_SET_TITLE_TIMES, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Send lobby ready sound effect to client. /// Send lobby ready sound effect to client.
@ -457,23 +423,20 @@ async fn send_lobby_sound_effect(
writer: &mut WriteHalf<'_>, writer: &mut WriteHalf<'_>,
sound_name: &str, sound_name: &str,
) -> Result<(), ()> { ) -> Result<(), ()> {
let packet = NamedSoundEffect { packet::write_packet(
sound_name: sound_name.into(), NamedSoundEffect {
sound_category: 0, sound_name: sound_name.into(),
effect_pos_x: 0, sound_category: 0,
effect_pos_y: 0, effect_pos_x: 0,
effect_pos_z: 0, effect_pos_y: 0,
volume: 1.0, effect_pos_z: 0,
pitch: 1.0, volume: 1.0,
}; pitch: 1.0,
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_NAMED_SOUND_EFFECT, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// Send respawn packet to client to jump from lobby into now loaded server. /// Send respawn packet to client to jump from lobby into now loaded server.
@ -484,24 +447,21 @@ async fn send_respawn_from_join(
writer: &mut WriteHalf<'_>, writer: &mut WriteHalf<'_>,
join_game: JoinGame, join_game: JoinGame,
) -> Result<(), ()> { ) -> Result<(), ()> {
let packet = Respawn { packet::write_packet(
dimension: join_game.dimension, Respawn {
world_name: join_game.world_name, dimension: join_game.dimension,
hashed_seed: join_game.hashed_seed, world_name: join_game.world_name,
game_mode: join_game.game_mode, hashed_seed: join_game.hashed_seed,
previous_game_mode: join_game.previous_game_mode, game_mode: join_game.game_mode,
is_debug: join_game.is_debug, previous_game_mode: join_game.previous_game_mode,
is_flat: join_game.is_flat, is_debug: join_game.is_debug,
copy_metadata: false, is_flat: join_game.is_flat,
}; copy_metadata: false,
},
let mut data = Vec::new(); client,
packet.encode(&mut data).map_err(|_| ())?; writer,
)
let response = RawPacket::new(packets::play::CLIENT_RESPAWN, data).encode(client)?; .await
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
} }
/// An infinite keep-alive loop. /// An infinite keep-alive loop.
@ -634,29 +594,27 @@ async fn connect_to_server_no_timeout(
tmp_client.set_state(ClientState::Login); tmp_client.set_state(ClientState::Login);
// Handshake packet // Handshake packet
let packet = Handshake { packet::write_packet(
protocol_version: client_info.protocol_version.unwrap(), Handshake {
server_addr: config.server.address.ip().to_string(), protocol_version: client_info.protocol_version.unwrap(),
server_port: config.server.address.port(), server_addr: config.server.address.ip().to_string(),
next_state: ClientState::Login.to_id(), server_port: config.server.address.port(),
}; next_state: ClientState::Login.to_id(),
},
let mut data = Vec::new(); &tmp_client,
packet.encode(&mut data).map_err(|_| ())?; &mut writer,
)
let request = RawPacket::new(packets::handshake::SERVER_HANDSHAKE, data).encode(&tmp_client)?; .await?;
writer.write_all(&request).await.map_err(|_| ())?;
// Request login start // Request login start
let packet = LoginStart { packet::write_packet(
name: client_info.username.ok_or(())?, LoginStart {
}; name: client_info.username.ok_or(())?,
},
let mut data = Vec::new(); &tmp_client,
packet.encode(&mut data).map_err(|_| ())?; &mut writer,
)
let request = RawPacket::new(packets::login::SERVER_LOGIN_START, data).encode(&tmp_client)?; .await?;
writer.write_all(&request).await.map_err(|_| ())?;
// Incoming buffer // Incoming buffer
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();

View File

@ -7,18 +7,17 @@ use std::time::Duration;
use bytes::BytesMut; use bytes::BytesMut;
use minecraft_protocol::data::server_status::ServerStatus; use minecraft_protocol::data::server_status::ServerStatus;
use minecraft_protocol::decoder::Decoder; use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake; use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_14_4::status::{PingRequest, PingResponse, StatusResponse}; use minecraft_protocol::version::v1_14_4::status::{
PingRequest, PingResponse, StatusRequest, StatusResponse,
};
use rand::Rng; use rand::Rng;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::time; use tokio::time;
use crate::config::Config; use crate::config::Config;
use crate::proto::client::{Client, ClientState}; use crate::proto::client::{Client, ClientState};
use crate::proto::packet::{self, RawPacket}; use crate::proto::{packet, packets};
use crate::proto::packets;
use crate::server::{Server, State}; use crate::server::{Server, State};
/// Monitor ping inverval in seconds. /// Monitor ping inverval in seconds.
@ -125,45 +124,28 @@ async fn send_handshake(
config: &Config, config: &Config,
addr: SocketAddr, addr: SocketAddr,
) -> Result<(), ()> { ) -> Result<(), ()> {
let handshake = Handshake { packet::write_packet(
protocol_version: config.public.protocol as i32, Handshake {
server_addr: addr.ip().to_string(), protocol_version: config.public.protocol as i32,
server_port: addr.port(), server_addr: addr.ip().to_string(),
next_state: ClientState::Status.to_id(), server_port: addr.port(),
}; next_state: ClientState::Status.to_id(),
},
let mut packet = Vec::new(); client,
handshake.encode(&mut packet).map_err(|_| ())?; &mut stream.split().1,
)
let raw = RawPacket::new(packets::handshake::SERVER_HANDSHAKE, packet) .await
.encode(client)
.map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?;
Ok(())
} }
/// Send status request. /// Send status request.
async fn request_status(client: &Client, stream: &mut TcpStream) -> Result<(), ()> { async fn request_status(client: &Client, stream: &mut TcpStream) -> Result<(), ()> {
let raw = RawPacket::new(packets::status::SERVER_STATUS, vec![]) packet::write_packet(StatusRequest {}, client, &mut stream.split().1).await
.encode(client)
.map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?;
Ok(())
} }
/// Send status request. /// Send status request.
async fn send_ping(client: &Client, stream: &mut TcpStream) -> Result<u64, ()> { async fn send_ping(client: &Client, stream: &mut TcpStream) -> Result<u64, ()> {
let token = rand::thread_rng().gen(); let token = rand::thread_rng().gen();
let ping = PingRequest { time: token }; packet::write_packet(PingRequest { time: token }, client, &mut stream.split().1).await?;
let mut packet = Vec::new();
ping.encode(&mut packet).map_err(|_| ())?;
let raw = RawPacket::new(packets::status::SERVER_PING, packet)
.encode(client)
.map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?;
Ok(token) Ok(token)
} }

View File

@ -1,51 +1,36 @@
use minecraft_protocol::data::chat::{Message, Payload}; use minecraft_protocol::data::chat::{Message, Payload};
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::game::GameDisconnect; use minecraft_protocol::version::v1_14_4::game::GameDisconnect;
use minecraft_protocol::version::v1_14_4::login::LoginDisconnect; use minecraft_protocol::version::v1_14_4::login::LoginDisconnect;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp::WriteHalf; use tokio::net::tcp::WriteHalf;
use crate::proto::client::{Client, ClientState}; use crate::proto::client::{Client, ClientState};
use crate::proto::packet::RawPacket; use crate::proto::packet;
use crate::proto::packets;
/// Kick client with a message. /// Kick client with a message.
/// ///
/// Should close connection afterwards. /// Should close connection afterwards.
pub async fn kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> { pub async fn kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
match client.state() { match client.state() {
ClientState::Login => login_kick(client, msg, writer).await, ClientState::Login => {
ClientState::Play => play_kick(client, msg, writer).await, packet::write_packet(
LoginDisconnect {
reason: Message::new(Payload::text(msg)),
},
client,
writer,
)
.await
}
ClientState::Play => {
packet::write_packet(
GameDisconnect {
reason: Message::new(Payload::text(msg)),
},
client,
writer,
)
.await
}
_ => Err(()), _ => Err(()),
} }
} }
/// Kick client with a message in login state.
///
/// Should close connection afterwards.
async fn login_kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = LoginDisconnect {
reason: Message::new(Payload::text(msg)),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::login::CLIENT_DISCONNECT, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())
}
/// Kick client with a message in play state.
///
/// Should close connection afterwards.
async fn play_kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = GameDisconnect {
reason: Message::new(Payload::text(msg)),
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packets::play::CLIENT_DISCONNECT, data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())
}

View File

@ -4,9 +4,11 @@ use bytes::BytesMut;
use flate2::read::ZlibDecoder; use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder; use flate2::write::ZlibEncoder;
use flate2::Compression; use flate2::Compression;
use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::PacketId;
use tokio::io; use tokio::io;
use tokio::io::AsyncReadExt; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::ReadHalf; use tokio::net::tcp::{ReadHalf, WriteHalf};
use crate::proto::client::Client; use crate::proto::client::Client;
use crate::proto::BUF_SIZE; use crate::proto::BUF_SIZE;
@ -197,3 +199,18 @@ pub async fn read_packet(
Ok(Some((packet, raw.to_vec()))) Ok(Some((packet, raw.to_vec())))
} }
/// Write packet to stream writer.
pub async fn write_packet(
packet: impl PacketId + Encoder,
client: &Client,
writer: &mut WriteHalf<'_>,
) -> Result<(), ()> {
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(packet.packet_id(), data).encode(client)?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
}