use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; use anyhow::Result; use mcproto_rs::protocol::State; use mcproto_rs::Serializer; use mcproto_rs::status::{StatusPlayersSpec, StatusSpec, StatusVersionSpec}; use mcproto_rs::types::{BytesSerializer, Chat, IntPosition, ItemStack, RemainingBytes, Vec3}; use mcproto_rs::uuid::UUID4; use mcproto_rs::v1_19_3::{EntityEventSpec, InitializeWorldBorderSpec, KeepAliveClientBoundSpec, PluginMessageSpec, SetContainerContentSpec, SetDefaultSpawnPositionSpec}; use mcproto_rs::v1_19_3::{HandshakeNextState, LoginSuccessSpec, Packet761, Packet761Kind, PingRequestSpec, PingResponseSpec, SetHeldItemClientSpec, StatusResponseSpec}; use tokio; use tokio::net::TcpStream; use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::broadcast; use tokio::sync::broadcast::Receiver; use tokio::task::JoinHandle; use crate::connect::MinecraftClient; use crate::world::DebugWorld; pub mod connect; pub mod nbtdsl; pub mod player; pub mod convention; pub mod world; #[tokio::main] async fn main() -> Result<()> { let (shutdown_send, _recv) = broadcast::channel::<()>(1); let bind = tokio::net::TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 25565)).await?; let server_rx = shutdown_send.subscribe(); let server_handle = tokio::spawn(async { listener(bind, server_rx).await; () }); println!("Awaiting shutdown request"); signal(SignalKind::interrupt())?.recv().await; println!("Shutdown request detected"); shutdown_send.send(())?; server_handle.await?; Ok(()) } async fn listener(listener: tokio::net::TcpListener, mut receiver: Receiver<()>) { println!("Starting server"); let mut handles: Vec> = Vec::new(); loop { let x: Option<(TcpStream, SocketAddr)> = tokio::select! { _ = receiver.recv() => { break; } conn = listener.accept() => { conn.ok() } }; let (stream, from) = match x { None => { continue } Some(y) => { y } }; println!("Connection accepted from {}", from); let client = MinecraftClient::from_stream(stream); let handle = tokio::spawn(async { if let Err(x) = handle_conn(client).await { println!("Error: {:?}", x); } }); handles.push(handle); } println!("Shutdown started"); for x in &handles { x.abort(); } /* TODO: proper kicks so that we can just join for x in handles { match x.await { Ok(_) => {} Err(_) => { println!("Failed to join server during exit!") } }; }*/ } async fn handle_conn(mut client: MinecraftClient) -> Result<()> { let hs = assert_packet!(Handshake, client, "Missing packet"); if hs.next_state == HandshakeNextState::Login { return handle_login(client).await; } else if hs.next_state == HandshakeNextState::Status { return handle_status(client).await; } else { anyhow::bail!("Unknown next status") } } async fn handle_login(mut client: MinecraftClient) -> Result<()> { client.set_state(State::Login)?; let loginstart = assert_packet!( LoginStart, client .read_next_packet() .await? .ok_or(anyhow::anyhow!("Missing packet"))? ); println!("Login: {:?}", loginstart); let uuid = UUID4::random(); client .send_packet(Packet761::LoginSuccess(LoginSuccessSpec { uuid, username: loginstart.name.clone(), properties: vec![].into(), }))?; client.set_state(State::Play)?; let mut player = player::Player::new(client, loginstart.name.clone(), Vec3 { x: 0.0, y: 0.0, z: 0.0, }, 0.0, 0.0); let universe = Arc::new(convention::Universe::default_meta(-64, 256)); let world = DebugWorld::new(universe.clone()); player.send_packet(universe.create_login_play_packet())?; let client_information = await_packet!(ClientInformation, player.client); println!("Client Information: {:?}", client_information); player.send_packet(Packet761::SetHeldItemClient(SetHeldItemClientSpec { selected_slot: 0, }))?; player.send_all_packets(universe.create_tags_and_recipes())?; player.send_packet(Packet761::EntityEvent(EntityEventSpec { entity_id: 0, entity_status: 24 /* OP permission level = 0 */, }))?; player.send_packet(universe.empty_commands())?; player.send_packet(universe.init_empty_recipe_book())?; player.send_packet(player.create_teleport_packet())?; player.synchronize_center_chunk()?; player.send_all_nearby_chunks(&world).await?; player.send_packet(Packet761::InitializeWorldBorder(InitializeWorldBorderSpec { x: 0.0, z: 0.0, old_diameter: 10000000.0, new_diameter: 10000000.0, speed: 0.into(), portal_teleport_boundary: 29999984.into(), warning_blocks: 0.into(), warning_time: 0.into(), }))?; player.send_packet(Packet761::SetDefaultSpawnPosition(SetDefaultSpawnPositionSpec { location: IntPosition { x: 0, y: 0, z: 0, }, angle: 0.0, }))?; player.send_packet(player.create_teleport_packet())?; player.send_packet(Packet761::SetContainerContent(SetContainerContentSpec { window_id: 0, state_id: 0.into(), slot_data: vec![Some(ItemStack { item_id: 1.into(), item_count: 1, nbt: None, }); 45].into(), carried_item: None, }))?; let mut i = 0; loop { player.send_packet(Packet761::KeepAliveClientBound(KeepAliveClientBoundSpec { keep_alive_id: i, }))?; if i == 2 { let mut brand_encoder = BytesSerializer::default(); brand_encoder.serialize_other(&String::from("MGASI"))?; player.send_packet(Packet761::PluginMessage(PluginMessageSpec { channel: "minecraft:brand".to_string(), data: RemainingBytes { data: brand_encoder.into_bytes() }, }))?; } i += 1; tokio::time::sleep(Duration::from_secs(10)).await; } } async fn handle_status(mut client: MinecraftClient) -> Result<()> { client.set_state(State::Status)?; loop { match client .read_next_packet() .await? .ok_or(anyhow::anyhow!("Missing packet"))? { Packet761::StatusRequest(_) => { client.send_packet(Packet761::StatusRespone(StatusResponseSpec { response: StatusSpec { version: Some(StatusVersionSpec { name: "1.19.3 mgasi".to_string(), protocol: 761, }), players: StatusPlayersSpec { max: 100, online: 0, sample: vec![], }, description: Chat::from_text("hehehe"), favicon: None, }, }))?; } Packet761::PingRequest(PingRequestSpec { payload }) => { client.send_packet(Packet761::PingResponse(PingResponseSpec { payload }))?; return Ok(()); } _ => anyhow::bail!("Unexpected packet during status communication"), } } }