use std::{ collections::HashMap, fmt::Debug, fs::read_to_string, io::{Cursor, Read, Write}, net::{Shutdown, SocketAddr, TcpListener, TcpStream}, path::PathBuf, sync::Arc, thread, }; use potato_protocol::{ datatypes::{identifier::Identifier, pack::Pack, var_int::VarInt}, packet::{ Packet, clientbound::{ self, GameEventPacket, SetChunkCacheCenterPacket, registry_data::{ Biome, BiomeEffects, DamageType, DimensionType, PaintingVariant, RegistryData, RegistryDataEntry, WolfVariant, }, status_response, }, serverbound, }, packet_encodable::{Json, Nbt, PacketDecodeError, PacketEncodable, PacketEncodeError}, }; use thiserror::Error; fn main() { println!("Looking for datapacks..."); let datapacks: Vec = std::fs::read_dir("run/datapacks") .expect("Failed to read datapacks directory") .map(|f| f.unwrap().path()) .filter(|p| p.is_dir()) .collect(); println!("Found {} datapacks", datapacks.len()); println!("Loading datapacks..."); let mut damage_types = HashMap::new(); for datapack in datapacks { let datapack_name = datapack.file_name().unwrap().to_str().unwrap(); println!("Loading datapack: {}", datapack_name); let path = datapack.join("data/minecraft/damage_type"); if let Ok(files) = std::fs::read_dir(path) { for file in files { let file = file.unwrap().path(); let name = file.file_stem().unwrap().to_str().unwrap().to_owned(); let data: DamageType = serde_json::from_str(&read_to_string(file).unwrap()) .expect("Failed to parse damage type"); damage_types.insert(Identifier::minecraft(name), data); } } } println!("Finished loading datapacks"); println!("Loaded {} damage types", damage_types.len()); let registries = Arc::new(Registries { damage_types }); let listener = TcpListener::bind("127.0.0.1:25565").unwrap(); println!("listening started, ready to accept"); for stream in listener.incoming() { let registries = registries.clone(); match stream { Err(e) => println!("Accept ERROR: {}", e), // Explicit drop because of the diverging branches Ok(stream) => drop(thread::spawn(|| handle_client(stream, registries))), } } } fn handle_client(stream: TcpStream, registries: Arc) { if let Ok(mut client) = ClientConnection::new(stream, registries) { loop { match client.read_packet() { Ok(_) => (), Err(e) => { println!("[{}] Error while handling packets: {}", client.address, e); // Make sure the connection is closed. let _ = client.stream.shutdown(Shutdown::Both); break; } } } } } #[derive(Debug)] enum ConnectionState { Handshaking, Status, Login, Configuration, Play, } struct ClientConnection { stream: TcpStream, address: SocketAddr, state: ConnectionState, recv_buffer: Vec, registries: Arc, } struct Registries { damage_types: HashMap, } // Max packet size is 2MB in vanilla const RECV_BUFFER_SIZE: usize = 1024 * 1024 * 2; #[allow(clippy::enum_variant_names)] #[derive(Error, Debug)] enum ConnectionError { #[error("IO error while reading packet: {0}")] IoError(#[from] std::io::Error), #[error("Error while decoding packet: {0}")] DecodeError(#[from] PacketDecodeError), #[error("Error while encoding packet: {0}")] EncodeError(#[from] PacketEncodeError), #[error("Client provided invalid next state")] InvalidNextState, #[error("Finished")] Finished, } // TODO: Need to start sending keep alive packets once we enter configuration phase impl ClientConnection { fn new( stream: TcpStream, registries: Arc, ) -> Result { let address = stream.peer_addr()?; Ok(ClientConnection { state: ConnectionState::Handshaking, address, stream, recv_buffer: vec![0; RECV_BUFFER_SIZE], registries, }) } fn read_packet(&mut self) -> Result<(), ConnectionError> { let packet_size: usize = VarInt::read(&mut self.stream)?.into(); self.stream .read_exact(&mut self.recv_buffer[..packet_size])?; self.handle_packet(packet_size) } fn send_packet(&mut self, packet: &T) -> Result<(), ConnectionError> { println!("[{} ({:?})] -> {:?}", self.address, self.state, packet); let buffer = &mut Vec::new(); // Encode ID let packet_id: i32 = match self.state { ConnectionState::Handshaking => T::HANDSHAKE_ID, ConnectionState::Status => T::STATUS_ID, ConnectionState::Login => T::LOGIN_ID, ConnectionState::Configuration => T::CONFIGURATION_ID, ConnectionState::Play => T::PLAY_ID, }; if packet_id == -1 { return Err(ConnectionError::EncodeError( PacketEncodeError::InvalidPacketId(packet_id), )); } let packet_id: VarInt = packet_id.into(); packet_id.encode_packet(buffer)?; // Encode packet packet.encode_packet(buffer)?; // Encode length let length_buffer = &mut Vec::new(); let length: VarInt = buffer.len().into(); length.encode_packet(length_buffer)?; // Print raw packet bytes // println!( // "{:X?} ({}) {:X?} ({})", // length_buffer, // length_buffer.len(), // buffer, // buffer.len() // ); // Send packet self.stream.write_all(length_buffer)?; self.stream.write_all(buffer)?; Ok(()) } fn handle_packet(&mut self, length: usize) -> Result<(), ConnectionError> { match self.state { ConnectionState::Handshaking => { self.handle_handshaking(length)?; } ConnectionState::Status => { self.handle_status(length)?; } ConnectionState::Login => { self.handle_login(length)?; } ConnectionState::Configuration => { self.handle_configuration(length)?; } ConnectionState::Play => { self.handle_play(length)?; } } Ok(()) } fn handle_handshaking(&mut self, length: usize) -> Result<(), ConnectionError> { let cursor = &mut Cursor::new(&self.recv_buffer[..length]); let id = VarInt::decode_packet(cursor)?.into(); match id { serverbound::IntentionPacket::HANDSHAKE_ID => { let packet = serverbound::IntentionPacket::decode_packet(cursor)?; println!("[{} (Handshaking)] <- {:?}", self.address, packet,); self.change_state_num(packet.next_state.into())? } // TODO: Legacy server list ping _ => { println!( "[{} (Handshaking)] <- Unknown packet id: {} with length {} data: {:X?}", self.address, id, length, &self.recv_buffer[..length] ); } } Ok(()) } fn handle_status(&mut self, length: usize) -> Result<(), ConnectionError> { let cursor = &mut Cursor::new(&self.recv_buffer[..length]); let id: i32 = VarInt::decode_packet(cursor)?.into(); match id { serverbound::StatusRequestPacket::STATUS_ID => { let packet = serverbound::StatusRequestPacket; println!("[{} (Status)] <- {:?}", self.address, packet); self.send_packet(&clientbound::StatusResponsePacket { status: Json(status_response::StatusResponseData { version: status_response::Version { name: "Rust 1.21.4".to_owned(), protocol: 769, }, players: status_response::Players { max: 500, online: 0, sample: vec![], }, description: status_response::Description { text: "A rust server!".to_owned(), }, favicon: None, enforce_secure_chat: false, }), })?; } serverbound::PingRequestPacket::STATUS_ID => { let packet = serverbound::PingRequestPacket::decode_packet(cursor)?; println!("[{} (Status)] <- {:?}", self.address, packet); self.send_packet(&clientbound::PongResponsePacket { timestamp: packet.timestamp, })?; self.stream.shutdown(Shutdown::Both)?; return Err(ConnectionError::Finished); } _ => { println!( "[{} (Status)] <- Unknown packet id: {} with length {} data: {:X?}", self.address, id, length, &self.recv_buffer[..length] ); } } Ok(()) } fn handle_login(&mut self, length: usize) -> Result<(), ConnectionError> { let cursor = &mut Cursor::new(&self.recv_buffer[..length]); let id: i32 = VarInt::decode_packet(cursor)?.into(); match id { serverbound::HelloPacket::LOGIN_ID => { let packet = serverbound::HelloPacket::decode_packet(cursor)?; println!("[{} (Login)] <- {:?}", self.address, packet); self.send_packet(&clientbound::LoginFinishedPacket { uuid: packet.uuid, username: packet.name, properties: vec![], })?; } serverbound::LoginAcknowledgedPacket::LOGIN_ID => { let packet = serverbound::LoginAcknowledgedPacket::decode_packet(cursor)?; println!("[{} (Login)] <- {:?}", self.address, packet); self.change_state(ConnectionState::Configuration)?; self.send_packet(&clientbound::SelectKnownPacksPacket { packs: vec![Pack { namespace: "minecraft".to_owned(), id: "core".to_owned(), version: "1.21.4".to_owned(), }], })?; } _ => { println!( "[{} (Login)] <- Unknown packet id: {} with length {} data: {:X?}", self.address, id, length, &self.recv_buffer[..length] ); } } Ok(()) } fn handle_configuration(&mut self, length: usize) -> Result<(), ConnectionError> { let cursor = &mut Cursor::new(&self.recv_buffer[..length]); let id: i32 = VarInt::decode_packet(cursor)?.into(); match id { serverbound::ClientInformationPacket::CONFIGURATION_ID => { let packet = serverbound::ClientInformationPacket::decode_packet(cursor)?; println!("[{} (Configuration)] <- {:?}", self.address, packet); } serverbound::CustomPayloadPacket::CONFIGURATION_ID => { let packet = serverbound::CustomPayloadPacket::decode_packet(cursor)?; println!("[{} (Configuration)] <- {:?}", self.address, packet); } serverbound::SelectKnownPacksPacket::CONFIGURATION_ID => { let packet = serverbound::SelectKnownPacksPacket::decode_packet(cursor)?; println!("[{} (Configuration)] <- {:?}", self.address, packet); self.send_packet(&clientbound::RegistryDataPacket { registry_id: Identifier::minecraft_str("dimension_type"), entries: vec![RegistryDataEntry { id: Identifier::minecraft_str("overworld"), data: Some(Nbt(RegistryData::DimensionType(DimensionType { fixed_time: None, has_skylight: 1, has_ceiling: 0, ultrawarm: 0, natural: 1, coordinate_scale: 1., bed_works: 1, respawn_anchor_works: 0, min_y: 0, height: 256, logical_height: 256, infiniburn: "#minecraft:infiniburn_overworld".to_owned(), effects: "minecraft:overworld".to_owned(), ambient_light: 0., piglin_safe: 1, has_raids: 1, monster_spawn_light_level: 0, monster_spawn_block_light_limit: 0, }))), }], })?; self.send_packet(&clientbound::RegistryDataPacket { registry_id: Identifier::minecraft_str("painting_variant"), entries: vec![RegistryDataEntry { id: Identifier::minecraft_str("backyard"), data: Some(Nbt(RegistryData::PaintingVariant(PaintingVariant { asset_id: "minecraft:backyard".to_owned(), height: 2, width: 2, }))), }], })?; self.send_packet(&clientbound::RegistryDataPacket { registry_id: Identifier::minecraft_str("wolf_variant"), entries: vec![RegistryDataEntry { id: Identifier::minecraft_str("ashen"), data: Some(Nbt(RegistryData::WolfVariant(WolfVariant { wild_texture: "minecraft:entity/wolf/wolf_ashen".to_owned(), tame_texture: "minecraft:entity/wolf/wolf_ashen_tame".to_owned(), angry_texture: "minecraft:entity/wolf/wolf_ashen_angry".to_owned(), biomes: vec![], }))), }], })?; let damage_type_registry_entries = self .registries .damage_types .iter() .map(|(key, value)| RegistryDataEntry { id: key.clone(), data: Some(Nbt(RegistryData::DamegeType(value.clone()))), }) .collect(); self.send_packet(&clientbound::RegistryDataPacket { registry_id: Identifier::minecraft_str("damage_type"), entries: damage_type_registry_entries, })?; self.send_packet(&clientbound::RegistryDataPacket { registry_id: Identifier::minecraft_str("worldgen/biome"), entries: vec![RegistryDataEntry { id: Identifier::minecraft_str("plains"), data: Some(Nbt(RegistryData::Biome(Biome { has_precipitation: 0, temperature: 0., temperature_modifier: None, downfall: 0., effects: BiomeEffects { fog_color: 0, water_color: 0, water_fog_color: 0, sky_color: 0, foliage_color: None, grass_color: None, grass_color_modifier: None, particle: None, ambient_sound: None, mood_sound: None, additions_sound: None, music: None, }, }))), }], })?; self.send_packet(&clientbound::FinishConfigurationPacket)?; } serverbound::FinishConfigurationPacket::CONFIGURATION_ID => { let packet = serverbound::finish_configuration::FinishConfigurationPacket; println!("[{} (Configuration)] <- {:?}", self.address, packet); self.change_state(ConnectionState::Play)?; self.send_packet(&clientbound::LoginPacket { entity_id: 0, is_hardcore: false, dimension_names: vec![], max_players: 500.into(), view_distance: 10.into(), simulation_distance: 10.into(), reduced_debug_info: false, enable_respawn_screen: true, do_limited_crafting: false, dimension_type: 0.into(), dimension_name: Identifier::minecraft("overworld".to_owned()), hashed_seed: 0, game_mode: 1, previous_game_mode: -1, is_debug: false, is_flat: false, death_info: None, portal_cooldown: 0.into(), sea_level: 63.into(), enforces_secure_chat: false, })?; self.send_packet(&GameEventPacket { event: 13, data: 0., })?; self.send_packet(&SetChunkCacheCenterPacket { x: 0.into(), z: 0.into(), })?; } _ => { println!( "[{} (Configuration)] <- Unknown packet id: {} with length {} data: {:X?}", self.address, id, length, &self.recv_buffer[..length] ); } } Ok(()) } fn handle_play(&mut self, length: usize) -> Result<(), ConnectionError> { let cursor = &mut Cursor::new(&self.recv_buffer[..length]); let id: i32 = VarInt::decode_packet(cursor)?.into(); match id { serverbound::ClientTickEndPacket::PLAY_ID => { serverbound::ClientTickEndPacket::decode_packet(cursor)?; } serverbound::MovePlayerPosPacket::PLAY_ID => { let _packet = serverbound::MovePlayerPosPacket::decode_packet(cursor)?; // println!("[{} (Play)] <- {:?}", self.address, packet); } serverbound::MovePlayerPosRotPacket::PLAY_ID => { let _packet = serverbound::MovePlayerPosRotPacket::decode_packet(cursor)?; // println!("[{} (Play)] <- {:?}", self.address, packet); } serverbound::MovePlayerRotPacket::PLAY_ID => { let _packet = serverbound::MovePlayerRotPacket::decode_packet(cursor)?; // println!("[{} (Play)] <- {:?}", self.address, packet); } _ => { println!( "[{} (Play)] <- Unknown packet id: {} with length {} data: {:X?}", self.address, id, length, &self.recv_buffer[..length] ); } } Ok(()) } fn change_state_num(&mut self, state: i32) -> Result<(), ConnectionError> { let new_state = match state { 1 => ConnectionState::Status, 2 => ConnectionState::Login, _ => return Err(ConnectionError::InvalidNextState), }; self.change_state(new_state) } fn change_state(&mut self, new_state: ConnectionState) -> Result<(), ConnectionError> { println!("[{}] {:?} -> {:?}", self.address, self.state, new_state); self.state = new_state; Ok(()) } }