use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; use anyhow::Result; use craftio_rs::CraftIo; use mcproto_rs::nbt::Tag; use mcproto_rs::protocol::State; use mcproto_rs::Serializer; use mcproto_rs::status::{StatusPlayersSpec, StatusSpec, StatusVersionSpec}; use mcproto_rs::types::{BytesSerializer, Chat, IntPosition, ItemStack, NamedNbtTag, RemainingBytes, Vec3}; use mcproto_rs::uuid::UUID4; use mcproto_rs::v1_19_3::{BitSet, BlobArray, BookSettings, ChunkDataAndUpdateLightSpec, ChunkDataSpec, ChunkSection, CommandNode, CommandNodeSpec, CommandsSpec, EntityEventSpec, InitializeWorldBorderSpec, KeepAliveClientBoundSpec, LightDataSpec, PalettedContainer, PluginMessageSpec, RecipeBookAction, RecipeBookInitSpec, SetCenterChunkSpec, SetContainerContentSpec, SetDefaultSpawnPositionSpec, SynchronizePlayerPositionSpec, TagType, TypedTagList, UpdateRecipeBookSpec}; use mcproto_rs::v1_19_3::{GameMode, HandshakeNextState, LoginPlaySpec, LoginSuccessSpec, Packet761, Packet761Kind, PingRequestSpec, PingResponseSpec, PreviousGameMode, SetHeldItemClientSpec, StatusResponseSpec, UpdateRecipesSpec, UpdateTagsSpec}; 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; pub mod connect; pub mod nbtdsl; #[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 { client.connection.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, properties: vec![].into(), })) .await?; let min_build_height = -64i32; let max_build_height = 256i32; let world_height = max_build_height - min_build_height; let min_world_section = min_build_height >> 4; let max_world_section = ((max_build_height - 1) >> 4) + 1; let section_count = max_world_section - min_world_section; let overworld_spec = nbt! { "id" :: (0), "name" :: ("minecraft:overworld"), "element" :: { "ambient_light" :: (false), "bed_works" :: (true), "coordinate_scale" :: (1), "effects" :: ("minecraft:overworld"), "has_ceiling" :: (false), "has_skylight" :: (true), "has_raids" :: (false), "height" :: (world_height), "infiniburn" :: ("#minecraft:infiniburn_overworld"), "logical_height" :: (world_height), "min_y" :: (min_build_height), "monster_spawn_block_light_limit" :: (0), "monster_spawn_light_level" :: { "type" :: ("minecraft:uniform"), "value" :: { "max_inclusive" :: (7), "min_inclusive" :: (0), }, }, "natural" :: (true), "piglin_safe" :: (false), "respawn_anchor_works" :: (false), "ultrawarm" :: (false), }, }; let biome_spec = nbt!( [ { "id" :: (0), "name" :: ("minecraft:plains"), "element" :: { "downfall" :: (0.5), "precipitation" :: ("none"), "temperature" :: (0.5), "effects" :: { "fog_color" :: (12638463), "sky_color" :: (12638463), "water_color" :: (12638463), "water_fog_color" :: (12638463), "mood_sound" :: { "block_search_extent" :: (8), "offset" :: (2), "sound" :: ("minecraft:ambient.cave"), "tick_delay" :: (6000), }, } } } ] ); let registry_doex = nbt! { "minecraft:dimension_type" :: { "type" :: ("minecraft:dimension_type"), "value" :: [(overworld_spec)] }, "minecraft:worldgen/biome" :: { "type" :: ("minecraft:worldgen/biome"), "value" :: (biome_spec), } }; client.connection.set_state(State::Play); client .send_packet(Packet761::LoginPlay(LoginPlaySpec { entity_id: 0, is_hardcore: false, gamemode: GameMode::Survival, previous_gamemode: PreviousGameMode::NoPrevious, dimension_names: vec!["world".into()].into(), registry_codex: NamedNbtTag { root: registry_doex.with_name("root"), }, dimension_type: "minecraft:overworld".into(), dimension_name: "world".into(), hashed_seed: 0, max_players: 10.into(), view_distance: 10.into(), simulation_distance: 10.into(), reduced_debug_info: false, enable_respawn_screen: false, is_debug: false, is_flat: false, death_location: None, })) .await?; let client_information = await_packet!(ClientInformation, client); println!("Client Information: {:?}", client_information); client.send_packet(Packet761::SetHeldItemClient(SetHeldItemClientSpec { selected_slot: 0, })).await?; client.send_packet(Packet761::UpdateRecipes(UpdateRecipesSpec { recipes: vec![].into() })).await?; client.send_packet(Packet761::UpdateTags(UpdateTagsSpec { tags: vec![ TypedTagList { tag_type: TagType::Block, tags: vec![].into(), }, TypedTagList { tag_type: TagType::Item, tags: vec![].into(), }, TypedTagList { tag_type: TagType::EntityType, tags: vec![].into(), }, TypedTagList { tag_type: TagType::GameEvent, tags: vec![].into(), }, TypedTagList { tag_type: TagType::Fluid, tags: vec![].into(), }, ].into(), })).await?; client.send_packet(Packet761::EntityEvent(EntityEventSpec { entity_id: 0, entity_status: 24 /* OP permission level = 0 */, })).await?; client.send_packet(Packet761::Commands(CommandsSpec { nodes: vec![ CommandNodeSpec { children_indices: vec![].into(), redirect_node: None, is_executable: false, node: CommandNode::Root, } ].into(), root_index: 0.into(), })).await?; client.send_packet(Packet761::UpdateRecipeBook(UpdateRecipeBookSpec { action: RecipeBookAction::Init(RecipeBookInitSpec { book_settings: BookSettings::default(), known_recipes: vec![].into(), highlighted_recipes: vec![].into(), }) })).await?; client.send_packet(Packet761::SynchronizePlayerPosition(SynchronizePlayerPositionSpec { position: Vec3 { x: 0f64, y: 100f64, z: 0f64, }, yaw: 0.0, pitch: 0.0, flags: Default::default(), teleport_id: 0.into(), dismount_vehicle: false, })).await?; client.send_packet(Packet761::SetCenterChunk(SetCenterChunkSpec { chunk_x: 0.into(), chunk_z: 0.into(), })).await?; for i in -7..7 { for j in -7..7 { let mut heightmap = BlobArray::new(9); for cx in 0..(16 * 16) { heightmap.set_entry(cx, 2) } let mut chunk_dat_serializer = BytesSerializer::default(); for i in 0..section_count { let section = ChunkSection { block_count: if i < 4 { 16 * 16 * 16 } else { 0 }, block_state: PalettedContainer { singular_value: if i < 4 { 1.into() } else { 0.into() } }, biomes: PalettedContainer { singular_value: 0.into() }, }; chunk_dat_serializer.serialize_other(§ion)?; } let chunk_column_bytes = chunk_dat_serializer.into_bytes(); client.send_packet(Packet761::ChunkDataAndUpdateLight(ChunkDataAndUpdateLightSpec { chunk_x: i, chunk_z: j, chunk_data: ChunkDataSpec { heightmaps: NamedNbtTag { root: nbt! { "MOTION_BLOCKING" :: (Tag::LongArray(heightmap.data.iter().map(|n| n.to_owned() as _).collect())), "WORLD_SURFACE" :: (Tag::LongArray(heightmap.data.iter().map(|n| n.to_owned() as _).collect())), }.with_name("root") }, buffer: chunk_column_bytes.into(), block_entities: vec![].into(), }, light_data: LightDataSpec { trust_edges: true, sky_y_mask: BitSet::empty(), block_y_mask: BitSet::empty(), empty_sky_y_mask: BitSet::empty(), empty_block_y_mask: BitSet::empty(), sky_updates: vec![].into(), block_updates: vec![].into(), }, })).await?; } } client.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(), })).await?; client.send_packet(Packet761::SetDefaultSpawnPosition(SetDefaultSpawnPositionSpec { location: IntPosition { x: 0, y: 0, z: 0, }, angle: 0.0, })).await?; client.send_packet(Packet761::SynchronizePlayerPosition(SynchronizePlayerPositionSpec { position: Vec3 { x: 0f64, y: 100f64, z: 0f64, }, yaw: 0.0, pitch: 0.0, flags: Default::default(), teleport_id: 0.into(), dismount_vehicle: false, })).await?; client.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, })).await?; let mut i = 0; loop { client.send_packet(Packet761::KeepAliveClientBound(KeepAliveClientBoundSpec { keep_alive_id: i, })).await?; if i == 2 { let mut brand_encoder = BytesSerializer::default(); brand_encoder.serialize_other(&String::from("MGASI"))?; client.send_packet(Packet761::PluginMessage(PluginMessageSpec { channel: "minecraft:brand".to_string(), data: RemainingBytes { data: brand_encoder.into_bytes() }, })).await?; } i += 1; tokio::time::sleep(Duration::from_secs(10)).await; } } else if hs.next_state == HandshakeNextState::Status { return handle_status(client).await; } else { anyhow::bail!("Unknown next status") } } async fn handle_status(mut client: MinecraftClient) -> Result<()> { client.connection.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, }, })) .await?; } Packet761::PingRequest(PingRequestSpec { payload }) => { client .send_packet(Packet761::PingResponse(PingResponseSpec { payload })) .await?; return Ok(()); } _ => anyhow::bail!("Unexpected packet during status communication"), } } }