Start experimenting with lobby, implement loading into lobby with text
This commit is contained in:
parent
db99289ea7
commit
e01fd212f7
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -213,6 +213,12 @@ version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -626,7 +632,9 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"minecraft-protocol",
|
||||
"named-binary-tag",
|
||||
"pretty_env_logger",
|
||||
"quartz_nbt",
|
||||
"rand 0.8.4",
|
||||
"rcon",
|
||||
"serde",
|
||||
@ -634,6 +642,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"uuid",
|
||||
"version-compare",
|
||||
"winapi",
|
||||
]
|
||||
@ -660,6 +669,12 @@ dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
@ -669,7 +684,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
[[package]]
|
||||
name = "minecraft-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#d26a525c7b29b61d2db64805181fb5471ea4317a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"minecraft-protocol-derive",
|
||||
@ -682,7 +697,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "minecraft-protocol-derive"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#d26a525c7b29b61d2db64805181fb5471ea4317a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -723,9 +738,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "named-binary-tag"
|
||||
version = "0.2.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d654702943d37d67f1491769ed46484c306f9b9d0d258348904bd63ffb101e8"
|
||||
checksum = "523298fac63bd954f9a2e03b962b8a4a0e95110ad1b2fa3e0d7048660ffecec3"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"flate2",
|
||||
@ -846,6 +861,30 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quartz_nbt"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24532990479062a9c515987986225879bd115ccb97672b1fb56d788a5adb7d39"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"cesu8",
|
||||
"flate2",
|
||||
"quartz_nbt_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quartz_nbt_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "289baa0c8a4d1f840d2de528a7f8c29e0e9af48b3018172b3edad4f716e8daed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@ -1251,6 +1290,7 @@ version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
||||
dependencies = [
|
||||
"md5",
|
||||
"rand 0.6.5",
|
||||
"serde",
|
||||
]
|
||||
|
@ -31,7 +31,7 @@ derive_builder = "0.10"
|
||||
dotenv = "0.15"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
log = "0.4"
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "31041b8" }
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", branch = "lazymc-v1_17_1" }
|
||||
pretty_env_logger = "0.4"
|
||||
rand = "0.8"
|
||||
serde = "1.0"
|
||||
@ -44,6 +44,11 @@ version-compare = "0.1"
|
||||
# Feature: rcon
|
||||
rust_rcon = { package = "rcon", version = "0.5", optional = true }
|
||||
|
||||
# Feature: lobby
|
||||
named-binary-tag = "0.6"
|
||||
quartz_nbt = "0.2"
|
||||
uuid = { version = "0.7", features = ["v3"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
|
18
res/dimension.snbt
Normal file
18
res/dimension.snbt
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
piglin_safe: 1b,
|
||||
natural: 0b,
|
||||
ambient_light: 0.0f,
|
||||
fixed_time: 0,
|
||||
infiniburn: "minecraft:infiniburn_overworld",
|
||||
respawn_anchor_works: 0b,
|
||||
has_skylight: 1b,
|
||||
bed_works: 0b,
|
||||
effects: "minecraft:the_end",
|
||||
has_raids: 0b,
|
||||
min_y: 0,
|
||||
height: 256,
|
||||
logical_height: 256,
|
||||
coordinate_scale: 1.0d,
|
||||
ultrawarm: 0b,
|
||||
has_ceiling: 0b
|
||||
}
|
2093
res/dimension_codec.snbt
Normal file
2093
res/dimension_codec.snbt
Normal file
File diff suppressed because it is too large
Load Diff
426
src/lobby.rs
Normal file
426
src/lobby.rs
Normal file
@ -0,0 +1,426 @@
|
||||
// TODO: remove this before feature release!
|
||||
#![allow(unused)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use minecraft_protocol::data::chat::{Message, Payload};
|
||||
use minecraft_protocol::data::server_status::*;
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::version::v1_14_4::game::{GameMode, MessagePosition};
|
||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||
use minecraft_protocol::version::v1_14_4::login::{LoginDisconnect, LoginStart, LoginSuccess};
|
||||
use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
||||
use minecraft_protocol::version::v1_17_1::game::{
|
||||
ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, JoinGame, PlayerPositionAndLook,
|
||||
SetTitleSubtitle, SetTitleText, SetTitleTimes, SpawnPosition, TimeUpdate,
|
||||
};
|
||||
use nbt::CompoundTag;
|
||||
use tokio::io::{self, AsyncWriteExt};
|
||||
use tokio::net::tcp::WriteHalf;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::proto::{self, Client, ClientState, RawPacket};
|
||||
use crate::server::{self, Server, State};
|
||||
use crate::service;
|
||||
|
||||
// TODO: remove this before releasing feature
|
||||
pub const USE_LOBBY: bool = true;
|
||||
pub const DONT_START_SERVER: bool = true;
|
||||
const STARTING_BANNER: &str = "§2 Server is starting...";
|
||||
const STARTING_BANNER_SUB: &str = "§7⌛ Please wait...";
|
||||
|
||||
// TODO: do not drop error here, return Box<dyn Error>
|
||||
pub async fn serve(
|
||||
client: Client,
|
||||
mut inbound: TcpStream,
|
||||
config: Arc<Config>,
|
||||
server: Arc<Server>,
|
||||
queue: BytesMut,
|
||||
) -> Result<(), ()> {
|
||||
let (mut reader, mut writer) = inbound.split();
|
||||
|
||||
// TODO: note this assumes the first receiving packet (over queue) is login start
|
||||
// TODO: assert client is in login mode!
|
||||
|
||||
// Incoming buffer and packet holding queue
|
||||
let mut buf = queue;
|
||||
let mut hold_queue = BytesMut::new();
|
||||
|
||||
loop {
|
||||
// Read packet from stream
|
||||
let (packet, raw) = match proto::read_packet(&mut buf, &mut reader).await {
|
||||
Ok(Some(packet)) => packet,
|
||||
Ok(None) => break,
|
||||
Err(_) => {
|
||||
error!(target: "lazymc", "Closing connection, error occurred");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Grab client state
|
||||
let client_state = client.state();
|
||||
|
||||
// Hijack login start
|
||||
if client_state == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_START {
|
||||
// Try to get login username
|
||||
let login_start = LoginStart::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||
|
||||
// TODO: remove debug message
|
||||
debug!(target: "LOBBY", "Login {:?}", login_start.name);
|
||||
|
||||
// Respond with login success
|
||||
let packet = LoginSuccess {
|
||||
// TODO: use correct username here
|
||||
uuid: Uuid::new_v3(
|
||||
&Uuid::new_v3(&Uuid::NAMESPACE_OID, b"OfflinePlayer"),
|
||||
login_start.name.as_bytes(),
|
||||
),
|
||||
username: login_start.name,
|
||||
// uuid: Uuid::parse_str("35ee313b-d89a-41b8-b25e-d32e8aff0389").unwrap(),
|
||||
// username: "Username".into(),
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::LOGIN_PACKET_ID_LOGIN_SUCCESS, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
// Update client state to play
|
||||
client.set_state(ClientState::Play);
|
||||
|
||||
// TODO: remove debug message
|
||||
debug!(target: "LOBBY", "Sent login success, moving to play state");
|
||||
|
||||
// TODO: handle errors here
|
||||
play_packets(&mut writer).await;
|
||||
|
||||
debug!(target: "LOBBY", "Done with playing packets, client disconnect?");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if client_state == ClientState::Play
|
||||
&& packet.id == proto::packets::play::SERVER_CLIENT_SETTINGS
|
||||
{
|
||||
debug!(target: "LOBBY", "Ignoring client settings packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
if client_state == ClientState::Play
|
||||
&& packet.id == proto::packets::play::SERVER_PLUGIN_MESSAGE
|
||||
{
|
||||
debug!(target: "LOBBY", "Ignoring plugin message packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
if client_state == ClientState::Play
|
||||
&& packet.id == proto::packets::play::SERVER_PLAYER_POS_ROT
|
||||
{
|
||||
debug!(target: "LOBBY", "Ignoring player pos rot packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
if client_state == ClientState::Play && packet.id == proto::packets::play::SERVER_PLAYER_POS
|
||||
{
|
||||
debug!(target: "LOBBY", "Ignoring player pos packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Show unhandled packet warning
|
||||
debug!(target: "lazymc", "Received unhandled packet:");
|
||||
debug!(target: "lazymc", "- State: {:?}", client_state);
|
||||
debug!(target: "lazymc", "- Packet ID: 0x{:02X} ({})", packet.id, packet.id);
|
||||
}
|
||||
|
||||
// Gracefully close connection
|
||||
match writer.shutdown().await {
|
||||
Ok(_) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
||||
Err(_) => return Err(()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kick client with a message.
|
||||
///
|
||||
/// Should close connection afterwards.
|
||||
async fn kick(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(proto::LOGIN_PACKET_ID_DISCONNECT, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn play_packets(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
debug!(target: "LOBBY", "Send play packets");
|
||||
|
||||
// See: https://wiki.vg/Protocol_FAQ#What.27s_the_normal_login_sequence_for_a_client.3F
|
||||
|
||||
// Send game join
|
||||
send_join_game(writer).await?;
|
||||
|
||||
// TODO: send brand plugin message
|
||||
|
||||
// After this, we receive:
|
||||
// - PLAY_PAKCET_ID_CLIENT_SETTINGS
|
||||
// - PLAY_PAKCET_ID_PLUGIN_MESSAGE
|
||||
// - PLAY_PAKCET_ID_PLAYER_POS_ROT
|
||||
// - PLAY_PAKCET_ID_PLAYER_POS ...
|
||||
|
||||
// TODO: send Update View Position ?
|
||||
// TODO: send Update View Distance ?
|
||||
|
||||
// Send chunk data
|
||||
// TODO: send_chunk_data(writer).await?;
|
||||
|
||||
// TODO: probably not required
|
||||
send_spawn_pos(writer).await?;
|
||||
|
||||
// Send player location, disables download terrain screen
|
||||
send_player_pos(writer).await?;
|
||||
|
||||
// TODO: send Update View Position
|
||||
// TODO: send Spawn Position
|
||||
// TODO: send Position and Look (one more time)
|
||||
|
||||
// Send time update
|
||||
send_time_update(writer).await?;
|
||||
|
||||
// // Keep sending keep alive packets
|
||||
send_keep_alive_loop(writer).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_join_game(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// // TODO: use proper values here!
|
||||
// let packet = JoinGame {
|
||||
// // entity_id: 0,
|
||||
// // game_mode: GameMode::Spectator,
|
||||
// entity_id: 27,
|
||||
// game_mode: GameMode::Hardcore,
|
||||
// dimension: 23,
|
||||
// max_players: 100,
|
||||
// level_type: String::from("default"),
|
||||
// view_distance: 10,
|
||||
// reduced_debug_info: true,
|
||||
// };
|
||||
|
||||
// TODO: use proper values here!
|
||||
let packet = JoinGame {
|
||||
entity_id: 0x6d,
|
||||
hardcore: false,
|
||||
game_mode: 3,
|
||||
previous_game_mode: -1i8 as u8, // use -1i8 as u8?
|
||||
world_names: vec![
|
||||
"minecraft:overworld".into(),
|
||||
"minecraft:the_nether".into(),
|
||||
"minecraft:the_end".into(),
|
||||
],
|
||||
dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")),
|
||||
dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")),
|
||||
world_name: "minecraft:overworld".into(),
|
||||
hashed_seed: 0,
|
||||
max_players: 20,
|
||||
view_distance: 10,
|
||||
reduced_debug_info: true,
|
||||
enable_respawn_screen: false,
|
||||
is_debug: false,
|
||||
is_flat: false,
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_JOIN_GAME, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// // TODO: this is possibly broken?
|
||||
// async fn send_chunk_data(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// // Send player location, disables download terrain screen
|
||||
// let packet = ChunkData {
|
||||
// x: 0,
|
||||
// z: 0,
|
||||
// primary_mask: Vec::new(),
|
||||
// heightmaps: CompoundTag::named("HeightMaps"),
|
||||
// biomes: Vec::new(),
|
||||
// data_size: 0,
|
||||
// data: Vec::new(),
|
||||
// block_entities_size: 0,
|
||||
// block_entities: Vec::new(),
|
||||
// // primary_mask: 65535,
|
||||
// // heights: CompoundTag::named("HeightMaps"),
|
||||
// // data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
// // tiles: vec![CompoundTag::named("TileEntity")],
|
||||
// };
|
||||
|
||||
// let mut data = Vec::new();
|
||||
// packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
// let response = RawPacket::new(proto::CLIENT_CHUNK_DATA, data).encode()?;
|
||||
// writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
async fn send_spawn_pos(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
let packet = SpawnPosition {
|
||||
position: 0,
|
||||
angle: 0.0,
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_SPAWN_POS, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_player_pos(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// Send player location, disables download terrain screen
|
||||
let packet = PlayerPositionAndLook {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
yaw: 0.0,
|
||||
pitch: 90.0,
|
||||
flags: 0b00000000,
|
||||
teleport_id: 0,
|
||||
dismount_vehicle: true,
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_PLAYER_POS_LOOK, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_time_update(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
const MC_TIME_NOON: i64 = 6000;
|
||||
|
||||
// Send player location, disables download terrain screen
|
||||
let packet = TimeUpdate {
|
||||
world_age: MC_TIME_NOON,
|
||||
time_of_day: MC_TIME_NOON,
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_TIME_UPDATE, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_keep_alive(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// Send player location, disables download terrain screen
|
||||
// TODO: keep picking random ID!
|
||||
let packet = ClientBoundKeepAlive { id: 0 };
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_KEEP_ALIVE, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
// TODO: require to receive correct keepalive!
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_keep_alive_loop(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// TODO: use interval of 10 sec?
|
||||
let mut poll_interval = time::interval(Duration::from_secs(10));
|
||||
|
||||
loop {
|
||||
// TODO: wait for start signal over channel instead of polling
|
||||
poll_interval.tick().await;
|
||||
|
||||
debug!(target: "LOBBY", "Sending keep-alive to client");
|
||||
send_keep_alive(writer).await?;
|
||||
|
||||
send_title(writer).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_title(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// Set title
|
||||
let packet = SetTitleText {
|
||||
text: Message::new(Payload::text(STARTING_BANNER)),
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_SET_TITLE_TEXT, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
// Set subtitle
|
||||
let packet = SetTitleSubtitle {
|
||||
text: Message::new(Payload::text(STARTING_BANNER_SUB)),
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response =
|
||||
RawPacket::new(proto::packets::play::CLIENT_SET_TITLE_SUBTITLE, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
// Set times
|
||||
let packet = SetTitleTimes {
|
||||
fade_in: 0,
|
||||
stay: i32::MAX,
|
||||
fade_out: 0,
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_SET_TITLE_TIMES, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read NBT CompoundTag from SNBT.
|
||||
fn snbt_to_compound_tag(data: &str) -> CompoundTag {
|
||||
use nbt::decode::read_compound_tag;
|
||||
use quartz_nbt::io::{self, Flavor};
|
||||
use quartz_nbt::snbt;
|
||||
use std::io::Cursor;
|
||||
|
||||
// Parse SNBT data
|
||||
let compound = snbt::parse(data).expect("failed to parse SNBT");
|
||||
|
||||
// Encode to binary
|
||||
let mut binary = Vec::new();
|
||||
io::write_nbt(&mut binary, None, &compound, Flavor::Uncompressed);
|
||||
|
||||
// Parse binary with usable NBT create
|
||||
read_compound_tag(&mut &*binary).unwrap()
|
||||
}
|
@ -10,6 +10,7 @@ extern crate log;
|
||||
pub(crate) mod action;
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod lobby;
|
||||
pub(crate) mod mc;
|
||||
pub(crate) mod monitor;
|
||||
pub(crate) mod os;
|
||||
|
29
src/proto.rs
29
src/proto.rs
@ -30,6 +30,31 @@ pub const STATUS_PACKET_ID_PING: i32 = 1;
|
||||
/// Login state, login start packet ID.
|
||||
pub const LOGIN_PACKET_ID_LOGIN_START: i32 = 0;
|
||||
|
||||
/// Login state, disconnect packet ID.
|
||||
pub const LOGIN_PACKET_ID_DISCONNECT: i32 = 0;
|
||||
|
||||
/// Login state, login success packet ID.
|
||||
pub const LOGIN_PACKET_ID_LOGIN_SUCCESS: i32 = 2;
|
||||
|
||||
pub mod packets {
|
||||
pub mod play {
|
||||
pub const CLIENT_JOIN_GAME: i32 = 0x26;
|
||||
pub const SERVER_CLIENT_SETTINGS: i32 = 0x05;
|
||||
pub const SERVER_PLUGIN_MESSAGE: i32 = 0x0A;
|
||||
pub const SERVER_PLAYER_POS_ROT: i32 = 0x12;
|
||||
pub const SERVER_PLAYER_POS: i32 = 0x11;
|
||||
pub const CLIENT_KEEP_ALIVE: i32 = 0x21;
|
||||
pub const CLIENT_CHUNK_DATA: i32 = 0x22;
|
||||
pub const CLIENT_PLAYER_POS_LOOK: i32 = 0x38;
|
||||
pub const CLIENT_SET_TITLE_TEXT: i32 = 0x59;
|
||||
pub const CLIENT_SET_TITLE_SUBTITLE: i32 = 0x57;
|
||||
pub const CLIENT_SET_TITLE_TIMES: i32 = 0x5A;
|
||||
pub const CLIENT_TIME_UPDATE: i32 = 0x58;
|
||||
pub const CLIENT_CHAT_MSG: i32 = 0x0F;
|
||||
pub const CLIENT_SPAWN_POS: i32 = 0x4B;
|
||||
}
|
||||
}
|
||||
|
||||
/// Client state.
|
||||
///
|
||||
/// Note: this does not keep track of compression/encryption states because packets are never
|
||||
@ -66,6 +91,9 @@ pub enum ClientState {
|
||||
|
||||
/// State to login to server.
|
||||
Login,
|
||||
|
||||
/// State to play on the server.
|
||||
Play,
|
||||
}
|
||||
|
||||
impl ClientState {
|
||||
@ -85,6 +113,7 @@ impl ClientState {
|
||||
Self::Handshake => 0,
|
||||
Self::Status => 1,
|
||||
Self::Login => 2,
|
||||
Self::Play => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::server::State;
|
||||
use bytes::BytesMut;
|
||||
use minecraft_protocol::data::chat::{Message, Payload};
|
||||
use minecraft_protocol::data::server_status::*;
|
||||
@ -17,8 +16,9 @@ use tokio::net::TcpStream;
|
||||
use tokio::time;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::lobby;
|
||||
use crate::proto::{self, Client, ClientState, RawPacket};
|
||||
use crate::server::{self, Server};
|
||||
use crate::server::{self, Server, State};
|
||||
use crate::service;
|
||||
|
||||
/// Proxy the given inbound stream to a target address.
|
||||
@ -31,7 +31,7 @@ pub async fn serve(
|
||||
) -> Result<(), ()> {
|
||||
let (mut reader, mut writer) = inbound.split();
|
||||
|
||||
// Incoming buffer
|
||||
// Incoming buffer and packet holding queue
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
// Remember inbound packets, used for client holding and forwarding
|
||||
@ -120,8 +120,26 @@ pub async fn serve(
|
||||
break;
|
||||
}
|
||||
|
||||
// Start server if not starting yet
|
||||
Server::start(config.clone(), server.clone(), username).await;
|
||||
if !lobby::DONT_START_SERVER {
|
||||
// Start server if not starting yet
|
||||
Server::start(config.clone(), server.clone(), username).await;
|
||||
}
|
||||
|
||||
// Lobby mode
|
||||
if lobby::USE_LOBBY {
|
||||
// // Hold login packet and remaining read bytes
|
||||
// hold_queue.extend(raw);
|
||||
// hold_queue.extend(buf.split_off(0));
|
||||
|
||||
// Build queue with login packet and any additionally received
|
||||
let mut queue = BytesMut::with_capacity(raw.len() + buf.len());
|
||||
queue.extend(raw);
|
||||
queue.extend(buf.split_off(0));
|
||||
|
||||
// Start lobby
|
||||
lobby::serve(client, inbound, config, server, queue).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Use join occupy methods
|
||||
for method in &config.join.methods {
|
||||
@ -283,7 +301,7 @@ async fn kick(msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(0, data).encode()?;
|
||||
let response = RawPacket::new(proto::LOGIN_PACKET_ID_DISCONNECT, data).encode()?;
|
||||
writer.write_all(&response).await.map_err(|_| ())
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user