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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cesu8"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -626,7 +632,9 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"minecraft-protocol",
|
"minecraft-protocol",
|
||||||
|
"named-binary-tag",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"quartz_nbt",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
"rcon",
|
"rcon",
|
||||||
"serde",
|
"serde",
|
||||||
@ -634,6 +642,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"uuid",
|
||||||
"version-compare",
|
"version-compare",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
@ -660,6 +669,12 @@ dependencies = [
|
|||||||
"value-bag",
|
"value-bag",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@ -669,7 +684,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=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
|
source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#d26a525c7b29b61d2db64805181fb5471ea4317a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"minecraft-protocol-derive",
|
"minecraft-protocol-derive",
|
||||||
@ -682,7 +697,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=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
|
source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#d26a525c7b29b61d2db64805181fb5471ea4317a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -723,9 +738,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "named-binary-tag"
|
name = "named-binary-tag"
|
||||||
version = "0.2.3"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d654702943d37d67f1491769ed46484c306f9b9d0d258348904bd63ffb101e8"
|
checksum = "523298fac63bd954f9a2e03b962b8a4a0e95110ad1b2fa3e0d7048660ffecec3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -846,6 +861,30 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "quick-error"
|
name = "quick-error"
|
||||||
version = "1.2.3"
|
version = "1.2.3"
|
||||||
@ -1251,6 +1290,7 @@ version = "0.7.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"md5",
|
||||||
"rand 0.6.5",
|
"rand 0.6.5",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -31,7 +31,7 @@ derive_builder = "0.10"
|
|||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
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 = "31041b8" }
|
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", branch = "lazymc-v1_17_1" }
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
@ -44,6 +44,11 @@ version-compare = "0.1"
|
|||||||
# Feature: rcon
|
# Feature: rcon
|
||||||
rust_rcon = { package = "rcon", version = "0.5", optional = true }
|
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]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
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 action;
|
||||||
pub(crate) mod cli;
|
pub(crate) mod cli;
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
|
pub(crate) mod lobby;
|
||||||
pub(crate) mod mc;
|
pub(crate) mod mc;
|
||||||
pub(crate) mod monitor;
|
pub(crate) mod monitor;
|
||||||
pub(crate) mod os;
|
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.
|
/// Login state, login start packet ID.
|
||||||
pub const LOGIN_PACKET_ID_LOGIN_START: i32 = 0;
|
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.
|
/// Client state.
|
||||||
///
|
///
|
||||||
/// Note: this does not keep track of compression/encryption states because packets are never
|
/// 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.
|
/// State to login to server.
|
||||||
Login,
|
Login,
|
||||||
|
|
||||||
|
/// State to play on the server.
|
||||||
|
Play,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientState {
|
impl ClientState {
|
||||||
@ -85,6 +113,7 @@ impl ClientState {
|
|||||||
Self::Handshake => 0,
|
Self::Handshake => 0,
|
||||||
Self::Status => 1,
|
Self::Status => 1,
|
||||||
Self::Login => 2,
|
Self::Login => 2,
|
||||||
|
Self::Play => -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ use std::ops::Deref;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::server::State;
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use minecraft_protocol::data::chat::{Message, Payload};
|
use minecraft_protocol::data::chat::{Message, Payload};
|
||||||
use minecraft_protocol::data::server_status::*;
|
use minecraft_protocol::data::server_status::*;
|
||||||
@ -17,8 +16,9 @@ use tokio::net::TcpStream;
|
|||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
|
use crate::lobby;
|
||||||
use crate::proto::{self, Client, ClientState, RawPacket};
|
use crate::proto::{self, Client, ClientState, RawPacket};
|
||||||
use crate::server::{self, Server};
|
use crate::server::{self, Server, State};
|
||||||
use crate::service;
|
use crate::service;
|
||||||
|
|
||||||
/// Proxy the given inbound stream to a target address.
|
/// Proxy the given inbound stream to a target address.
|
||||||
@ -31,7 +31,7 @@ pub async fn serve(
|
|||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let (mut reader, mut writer) = inbound.split();
|
let (mut reader, mut writer) = inbound.split();
|
||||||
|
|
||||||
// Incoming buffer
|
// Incoming buffer and packet holding queue
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
|
|
||||||
// Remember inbound packets, used for client holding and forwarding
|
// Remember inbound packets, used for client holding and forwarding
|
||||||
@ -120,8 +120,26 @@ pub async fn serve(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start server if not starting yet
|
if !lobby::DONT_START_SERVER {
|
||||||
Server::start(config.clone(), server.clone(), username).await;
|
// 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
|
// Use join occupy methods
|
||||||
for method in &config.join.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();
|
let mut data = Vec::new();
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
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(|_| ())
|
writer.write_all(&response).await.map_err(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user