Remove obsolete file
This commit is contained in:
parent
c9290827be
commit
32317a4c2f
@ -1,953 +0,0 @@
|
||||
// TODO: remove this before feature release!
|
||||
#![allow(unused)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::FutureExt;
|
||||
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, GameDisconnect, JoinGame,
|
||||
PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, SpawnPosition,
|
||||
TimeUpdate,
|
||||
};
|
||||
use nbt::CompoundTag;
|
||||
use tokio::io::{self, AsyncWriteExt};
|
||||
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::proto::{self, Client, ClientState, RawPacket};
|
||||
use crate::proxy;
|
||||
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 = false;
|
||||
const STARTING_BANNER: &str = "§2Server is starting";
|
||||
const STARTING_BANNER_SUB: &str = "§7⌛ Please wait...";
|
||||
|
||||
/// Client holding server state poll interval.
|
||||
const HOLD_POLL_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
// 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 server_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;
|
||||
|
||||
send_keep_alive(&mut writer).await?;
|
||||
|
||||
send_title(&mut writer).await?;
|
||||
|
||||
// Wait for server to come online
|
||||
wait_for_server(config.clone(), server.clone(), &mut writer).await?;
|
||||
|
||||
// Connect to server
|
||||
let (mut outbound, client_queue) = connect_to_server(client, &config, server).await?;
|
||||
|
||||
// Wait for join game packet
|
||||
// TODO: do something with this excess buffer
|
||||
let (join_game, client_buf) = wait_for_server_join_game(&mut outbound).await?;
|
||||
|
||||
dbg!(join_game.entity_id);
|
||||
|
||||
send_title_reset(&mut writer).await?;
|
||||
|
||||
// Send respawn apcket
|
||||
send_respawn(&mut writer, join_game.clone()).await?;
|
||||
send_respawn(&mut writer, join_game).await?;
|
||||
|
||||
if !client_buf.is_empty() {
|
||||
// TODO: handle this
|
||||
// TODO: remove error message
|
||||
error!(target: "LOBBY", "Got excess data from server for client! ({} bytes)", client_buf.len());
|
||||
}
|
||||
|
||||
// Drain inbound
|
||||
// TODO: do not drain, send packets to server, except keep-alive
|
||||
drain_stream(&mut reader).await?;
|
||||
|
||||
// TODO: route any following packets to client
|
||||
|
||||
// Wait a little because Notchian servers are slow
|
||||
// See: https://wiki.vg/Protocol#Login_Success
|
||||
// TODO: improve this
|
||||
|
||||
// debug!(target: "LOBBY", "Waiting 2 sec for server");
|
||||
// time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
debug!(target: "LOBBY", "Moving client to proxy");
|
||||
route_proxy_queue(inbound, outbound, config, client_queue, server_queue);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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(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,
|
||||
// ID must not collide with anything existing
|
||||
// entity_id: 1337133700,
|
||||
entity_id: 0,
|
||||
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")),
|
||||
// TODO: is this ok?
|
||||
// world_name: "minecraft:overworld".into(),
|
||||
world_name: "lazymc:lobby".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(())
|
||||
}
|
||||
|
||||
async fn send_respawn(writer: &mut WriteHalf<'_>, join_game: JoinGame) -> Result<(), ()> {
|
||||
// TODO: use proper values here!
|
||||
let packet = Respawn {
|
||||
dimension: join_game.dimension,
|
||||
world_name: join_game.world_name,
|
||||
hashed_seed: join_game.hashed_seed,
|
||||
game_mode: join_game.game_mode,
|
||||
previous_game_mode: join_game.previous_game_mode,
|
||||
is_debug: join_game.is_debug,
|
||||
is_flat: join_game.is_flat,
|
||||
copy_metadata: false,
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let response = RawPacket::new(proto::packets::play::CLIENT_RESPAWN, 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?;
|
||||
}
|
||||
|
||||
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
|
||||
// TODO: do not make this longer than 2x keep alive packet interval
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn send_title_reset(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
// Set title
|
||||
let packet = SetTitleText {
|
||||
text: Message::new(Payload::text("")),
|
||||
};
|
||||
|
||||
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("")),
|
||||
};
|
||||
|
||||
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
|
||||
// TODO: do not make this longer than 2x keep alive packet interval
|
||||
let packet = SetTitleTimes {
|
||||
fade_in: 10,
|
||||
stay: 100,
|
||||
fade_out: 10,
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
// TODO: go through this, use proper error messages
|
||||
pub async fn wait_for_server<'a>(
|
||||
config: Arc<Config>,
|
||||
server: Arc<Server>,
|
||||
writer: &mut WriteHalf<'a>,
|
||||
) -> Result<(), ()> {
|
||||
debug!(target: "lazymc", "Waiting on server...");
|
||||
|
||||
// Set up polling interval, get timeout
|
||||
let mut poll_interval = time::interval(HOLD_POLL_INTERVAL);
|
||||
let since = Instant::now();
|
||||
let timeout = config.time.hold_client_for as u64;
|
||||
|
||||
loop {
|
||||
// TODO: wait for start signal over channel instead of polling
|
||||
poll_interval.tick().await;
|
||||
|
||||
trace!("Poloutboundng server state for holding client...");
|
||||
|
||||
// TODO: shouldn't do this here
|
||||
send_keep_alive(writer).await?;
|
||||
|
||||
match server.state() {
|
||||
// Still waiting on server start
|
||||
State::Starting => {
|
||||
trace!(target: "lazymc", "Server not ready, holding client for longer");
|
||||
|
||||
// TODO: timeout
|
||||
// // If hold timeout is reached, kick client
|
||||
// if since.elapsed().as_secs() >= timeout {
|
||||
// warn!(target: "lazymc", "Held client reached timeout of {}s, disconnecting", timeout);
|
||||
// kick(&config.messages.login_starting, &mut inbound.split().1).await?;
|
||||
// return Ok(());
|
||||
// }
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Server started, start relaying and proxy
|
||||
State::Started => {
|
||||
// TODO: drop client if already disconnected
|
||||
|
||||
// // Relay client to proxy
|
||||
// info!(target: "lazymc", "Server ready for held client, relaying to server");
|
||||
// service::server::route_proxy_queue(inbound, config, hold_queue);
|
||||
|
||||
info!(target: "lazymc", "Server ready for lobby client, connecting to server");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Server stopping, this shouldn't happen, kick
|
||||
State::Stopping => {
|
||||
// TODO: kick message
|
||||
// warn!(target: "lazymc", "Server stopping for held client, disconnecting");
|
||||
// kick(&config.messages.login_stopping, &mut inbound.split().1).await?;
|
||||
break;
|
||||
}
|
||||
|
||||
// Server stopped, this shouldn't happen, disconnect
|
||||
State::Stopped => {
|
||||
error!(target: "lazymc", "Server stopped for held client, disconnecting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub async fn connect_to_server(
|
||||
real_client: Client,
|
||||
config: &Config,
|
||||
server: Arc<Server>,
|
||||
) -> Result<(TcpStream, BytesMut), ()> {
|
||||
// Set up connection to server
|
||||
// TODO: on connect fail, ping server and redirect to serve_status if offline
|
||||
let mut outbound = TcpStream::connect(config.server.address)
|
||||
.await
|
||||
.map_err(|_| ())?;
|
||||
|
||||
let (mut reader, mut writer) = outbound.split();
|
||||
|
||||
let tmp_client = Client::default();
|
||||
tmp_client.set_state(ClientState::Login);
|
||||
|
||||
// TODO: use client version
|
||||
let packet = Handshake {
|
||||
protocol_version: 755,
|
||||
server_addr: config.server.address.ip().to_string(),
|
||||
server_port: config.server.address.port(),
|
||||
next_state: ClientState::Login.to_id(),
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let request = RawPacket::new(proto::HANDSHAKE_PACKET_ID_HANDSHAKE, data).encode()?;
|
||||
writer.write_all(&request).await.map_err(|_| ())?;
|
||||
|
||||
// Request login start
|
||||
// TODO: use client username
|
||||
let packet = LoginStart {
|
||||
name: "timvisee".into(),
|
||||
};
|
||||
|
||||
let mut data = Vec::new();
|
||||
packet.encode(&mut data).map_err(|_| ())?;
|
||||
|
||||
let request = RawPacket::new(proto::LOGIN_PACKET_ID_LOGIN_START, data).encode()?;
|
||||
writer.write_all(&request).await.map_err(|_| ())?;
|
||||
|
||||
// # Wait for server responses
|
||||
|
||||
// Incoming buffer and packet holding queue
|
||||
let mut buf = BytesMut::new();
|
||||
let mut client_queue = BytesMut::new();
|
||||
let mut server_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 = tmp_client.state();
|
||||
|
||||
// Hijack login success
|
||||
if client_state == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_SUCCESS {
|
||||
debug!(target: "LOBBY", "Login success received");
|
||||
|
||||
// TODO: fix reading this packet
|
||||
// let login_success =
|
||||
// LoginSuccess::decode(&mut packet.data.as_slice()).map_err(|err| {
|
||||
// dbg!(err);
|
||||
// ()
|
||||
// })?;
|
||||
|
||||
// // TODO: remove debug message
|
||||
// debug!(target: "LOBBY", "Login success: {:?}", login_success.username);
|
||||
|
||||
// Switch to play state
|
||||
tmp_client.set_state(ClientState::Play);
|
||||
|
||||
debug!(target: "LOBBY", "Server connection ready");
|
||||
|
||||
// TODO: also return buf!
|
||||
assert!(
|
||||
buf.is_empty(),
|
||||
"server incomming buf not empty, will lose data"
|
||||
);
|
||||
|
||||
return Ok((outbound, client_queue));
|
||||
}
|
||||
|
||||
// // Hijack join game
|
||||
// if client_state == ClientState::Play && packet.id == proto::packets::play::CLIENT_JOIN_GAME
|
||||
// {
|
||||
// // We must receive join game packet
|
||||
|
||||
// // TODO: also parse packet!
|
||||
|
||||
// // TODO: remove debug message
|
||||
// debug!(target: "LOBBY", "Received join packet, will relay to client");
|
||||
|
||||
// // TODO: send join game packet?
|
||||
// debug!(target: "LOBBY", "client_queue + {} bytes", raw.len());
|
||||
// client_queue.extend(raw);
|
||||
|
||||
// // TODO: real client and tmp_client must match state
|
||||
|
||||
// return Ok((outbound, client_queue));
|
||||
// }
|
||||
|
||||
// TODO: // Hijack join game
|
||||
// TODO: // TODO: client_state == ClientState::Play &&
|
||||
// TODO: if packet.id == proto::packets::play::CLIENT_JOIN_GAME {
|
||||
// TODO: let join_game = JoinGame::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||
|
||||
// TODO: // TODO: remove debug message
|
||||
// TODO: debug!(target: "LOBBY", "GOT JOIN GAME!");
|
||||
|
||||
// TODO: continue;
|
||||
// TODO: }
|
||||
|
||||
// // Hijack disconnect message
|
||||
// // TODO: remove this?
|
||||
// if packet.id == proto::packets::play::CLIENT_DISCONNECT {
|
||||
// let disconnect = GameDisconnect::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||
|
||||
// debug!(target: "LOBBY", "DISCONNECT REASON: {:?}", disconnect.reason);
|
||||
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // 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;
|
||||
|
||||
// send_keep_alive(&mut writer).await?;
|
||||
|
||||
// // Wait for server to come online
|
||||
// wait_for_server(config.clone(), server.clone()).await?;
|
||||
|
||||
// // Connect to server
|
||||
// connect_to_server(client, config, server).await?;
|
||||
|
||||
// // Keep sending keep alive packets
|
||||
// debug!(target: "LOBBY", "Keep sending keep-alive now...");
|
||||
// send_keep_alive_loop(&mut writer).await?;
|
||||
|
||||
// debug!(target: "LOBBY", "Done with playing packets, client disconnect?");
|
||||
|
||||
// return Ok(());
|
||||
// }
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// TODO: we should receive login success from server
|
||||
// TODO: we should receive join game packet, relay this to client
|
||||
|
||||
// // Gracefully close connection
|
||||
// match writer.shutdown().await {
|
||||
// Ok(_) => {}
|
||||
// Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
||||
// Err(_) => return Err(()),
|
||||
// }
|
||||
|
||||
// We only reach this on errors
|
||||
// TODO: do we actually ever reach this?
|
||||
Err(())
|
||||
}
|
||||
|
||||
// TODO: remove unused fields
|
||||
// TODO: do not drop error here, return Box<dyn Error>
|
||||
// TODO: add timeout
|
||||
pub async fn wait_for_server_join_game(
|
||||
// client: Client,
|
||||
mut outbound: &mut TcpStream,
|
||||
// config: Arc<Config>,
|
||||
// server: Arc<Server>,
|
||||
// queue: BytesMut,
|
||||
) -> Result<(JoinGame, BytesMut), ()> {
|
||||
let (mut reader, mut writer) = outbound.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 = 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;
|
||||
}
|
||||
};
|
||||
|
||||
// Hijack login start
|
||||
if packet.id == proto::packets::play::CLIENT_JOIN_GAME {
|
||||
let join_game = JoinGame::decode(&mut packet.data.as_slice()).map_err(|err| {
|
||||
// TODO: remove this debug
|
||||
dbg!(err);
|
||||
()
|
||||
})?;
|
||||
|
||||
// TODO: remove debug message
|
||||
debug!(target: "LOBBY", "GOT JOIN FROM SERVER");
|
||||
|
||||
return Ok((join_game, buf));
|
||||
}
|
||||
|
||||
// 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(()),
|
||||
}
|
||||
|
||||
// TODO: will we ever reach this?
|
||||
Err(())
|
||||
}
|
||||
|
||||
// TODO: update name and description
|
||||
/// Route inbound TCP stream to proxy with queued data, spawning a new task.
|
||||
#[inline]
|
||||
pub fn route_proxy_queue(
|
||||
inbound: TcpStream,
|
||||
outbound: TcpStream,
|
||||
config: Arc<Config>,
|
||||
client_queue: BytesMut,
|
||||
server_queue: BytesMut,
|
||||
) {
|
||||
// When server is online, proxy all
|
||||
let service = async move {
|
||||
proxy::proxy_inbound_outbound_with_queue(inbound, outbound, &client_queue, &server_queue)
|
||||
.map(|r| {
|
||||
if let Err(err) = r {
|
||||
warn!(target: "lazymc", "Failed to proxy: {}", err);
|
||||
}
|
||||
|
||||
// TODO: remove after debug
|
||||
debug!(target: "LOBBY", "Done with playing packets, client disconnect?");
|
||||
})
|
||||
.await
|
||||
};
|
||||
|
||||
tokio::spawn(service);
|
||||
}
|
||||
|
||||
// TODO: go through this, use proper error messages
|
||||
pub async fn drain_stream<'a>(reader: &mut ReadHalf<'a>) -> Result<(), ()> {
|
||||
// TODO: remove after debug
|
||||
debug!(target: "lazymc", "Draining stream...");
|
||||
|
||||
// TODO: use other size?
|
||||
let mut drain_buf = [0; 1024];
|
||||
|
||||
loop {
|
||||
match reader.try_read(&mut drain_buf) {
|
||||
// TODO: stop if read < drain_buf.len() ?
|
||||
Ok(read) if read == 0 => return Ok(()),
|
||||
Ok(read) => continue,
|
||||
Err(err) => {
|
||||
// TODO: remove after debug
|
||||
dbg!("drain err", err);
|
||||
return 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()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user