diff --git a/Cargo.lock b/Cargo.lock index c39b7d8d..000da56b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,7 @@ dependencies = [ "base64", "chrono", "env_logger", + "md-5", "num-bigint", "once_cell", "reqwest", @@ -1637,6 +1638,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.6.4" @@ -2878,6 +2889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", + "md-5", "serde", ] diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 3e25b6a4..93d9c7f3 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -25,7 +25,8 @@ serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" thiserror = "1.0.50" tokio = { version = "1.34.0", features = ["fs"] } -uuid = { version = "1.5.0", features = ["serde"] } +uuid = { version = "1.5.0", features = ["serde", "v3"] } +md-5 = "0.10.6" [dev-dependencies] env_logger = "0.10.1" diff --git a/azalea-auth/src/lib.rs b/azalea-auth/src/lib.rs index bd151eb3..1643bf04 100755 --- a/azalea-auth/src/lib.rs +++ b/azalea-auth/src/lib.rs @@ -4,6 +4,7 @@ mod auth; pub mod cache; pub mod certs; pub mod game_profile; +pub mod offline; pub mod sessionserver; pub use auth::*; diff --git a/azalea-auth/src/offline.rs b/azalea-auth/src/offline.rs new file mode 100644 index 00000000..737555e8 --- /dev/null +++ b/azalea-auth/src/offline.rs @@ -0,0 +1,17 @@ +use md5::{Digest, Md5}; +use uuid::Uuid; + +pub fn generate_uuid(username: &str) -> Uuid { + uuid::Builder::from_md5_bytes(hash(format!("OfflinePlayer:{username}").as_bytes())).into_uuid() +} + +fn hash(data: &[u8]) -> [u8; 16] { + let mut hasher = Md5::new(); + + hasher.update(data); + + let mut bytes = [0; 16]; + bytes.copy_from_slice(&hasher.finalize()[..16]); + + bytes +} diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index 0be4146b..7ef942ed 100755 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -200,6 +200,13 @@ impl Account { } } } + + /// Get the UUID of this account. This will generate an offline-mode UUID + /// by making a hash with the username if the `uuid` field is None. + pub fn uuid_or_offline(&self) -> Uuid { + self.uuid + .unwrap_or_else(|| azalea_auth::offline::generate_uuid(&self.username)) + } } #[derive(Error, Debug)] @@ -226,3 +233,5 @@ impl Account { Ok(()) } } + +fn uuid_from_username() {} diff --git a/azalea-client/src/attack.rs b/azalea-client/src/attack.rs index 287a2dde..c22de727 100644 --- a/azalea-client/src/attack.rs +++ b/azalea-client/src/attack.rs @@ -13,11 +13,8 @@ use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; use crate::{ - interact::SwingArmEvent, - local_player::{LocalGameMode, SendPacketEvent}, - movement::MoveEventsSet, - respawn::perform_respawn, - Client, + interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet, + packet_handling::game::SendPacketEvent, respawn::perform_respawn, Client, }; pub struct AttackPlugin; diff --git a/azalea-client/src/chat.rs b/azalea-client/src/chat.rs index dbc2843c..f805c20f 100755 --- a/azalea-client/src/chat.rs +++ b/azalea-client/src/chat.rs @@ -22,7 +22,7 @@ use uuid::Uuid; use crate::{ client::Client, - local_player::{handle_send_packet_event, SendPacketEvent}, + packet_handling::game::{handle_send_packet_event, SendPacketEvent}, }; /// A chat packet, either a system message or a chat message. diff --git a/azalea-client/src/chunks.rs b/azalea-client/src/chunks.rs index e91e6b01..072fbd31 100644 --- a/azalea-client/src/chunks.rs +++ b/azalea-client/src/chunks.rs @@ -21,7 +21,7 @@ use tracing::{error, trace}; use crate::{ interact::handle_block_interact_event, inventory::InventorySet, - local_player::{handle_send_packet_event, SendPacketEvent}, + packet_handling::game::{handle_send_packet_event, SendPacketEvent}, respawn::perform_respawn, InstanceHolder, }; diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 21d3a80a..16d9442b 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -7,12 +7,16 @@ use crate::{ interact::{CurrentSequenceNumber, InteractPlugin}, inventory::{InventoryComponent, InventoryPlugin}, local_player::{ - death_event, handle_send_packet_event, GameProfileComponent, Hunger, InstanceHolder, - PermissionLevel, PlayerAbilities, SendPacketEvent, TabList, + death_event, GameProfileComponent, Hunger, InstanceHolder, PermissionLevel, + PlayerAbilities, TabList, }, mining::{self, MinePlugin}, movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin}, - packet_handling::PacketHandlerPlugin, + packet_handling::{ + game::{handle_send_packet_event, SendPacketEvent}, + login::{self, LoginSendPacketQueue}, + PacketHandlerPlugin, + }, player::retroactively_add_game_profile_component, raw_connection::RawConnection, respawn::RespawnPlugin, @@ -35,6 +39,7 @@ use azalea_protocol::{ packets::{ configuration::{ serverbound_client_information_packet::ClientInformation, + serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, ClientboundConfigurationPacket, ServerboundConfigurationPacket, }, game::ServerboundGamePacket, @@ -208,8 +213,29 @@ impl Client { resolved_address: &SocketAddr, run_schedule_sender: mpsc::UnboundedSender<()>, ) -> Result<(Self, mpsc::UnboundedReceiver), JoinError> { + // check if an entity with our uuid already exists in the ecs and if so then + // just use that + let entity = { + let mut ecs = ecs_lock.lock(); + + let entity_uuid_index = ecs.resource::(); + let uuid = account.uuid_or_offline(); + if let Some(entity) = entity_uuid_index.get(&account.uuid_or_offline()) { + debug!("Reusing entity {entity:?} for client"); + entity + } else { + let entity = ecs.spawn_empty().id(); + debug!("Created new entity {entity:?} for client"); + // add to the uuid index + let mut entity_uuid_index = ecs.resource_mut::(); + entity_uuid_index.insert(uuid, entity); + entity + } + }; + let conn = Connection::new(resolved_address).await?; - let (mut conn, game_profile) = Self::handshake(conn, account, address).await?; + let (mut conn, game_profile) = + Self::handshake(ecs_lock.clone(), entity, conn, account, address).await?; { // quickly send the brand here @@ -217,12 +243,13 @@ impl Client { // they don't have to know :) "vanilla".write_into(&mut brand_data).unwrap(); conn.write( - azalea_protocol::packets::configuration::serverbound_custom_payload_packet::ServerboundCustomPayloadPacket { + ServerboundCustomPayloadPacket { identifier: ResourceLocation::new("brand"), data: brand_data.into(), } .get(), - ).await?; + ) + .await?; } let (read_conn, write_conn) = conn.into_split(); @@ -234,22 +261,6 @@ impl Client { let mut ecs = ecs_lock.lock(); - // check if an entity with our uuid already exists in the ecs and if so then - // just use that - let entity = { - let entity_uuid_index = ecs.resource::(); - if let Some(entity) = entity_uuid_index.get(&game_profile.uuid) { - debug!("Reusing entity {entity:?} for client"); - entity - } else { - let entity = ecs.spawn_empty().id(); - debug!("Created new entity {entity:?} for client"); - // add to the uuid index - let mut entity_uuid_index = ecs.resource_mut::(); - entity_uuid_index.insert(game_profile.uuid, entity); - entity - } - }; // we got the ConfigurationConnection, so the client is now connected :) let client = Client::new( game_profile.clone(), @@ -284,6 +295,8 @@ impl Client { /// This will also automatically refresh the account's access token if /// it's expired. pub async fn handshake( + ecs_lock: Arc>, + entity: Entity, mut conn: Connection, account: &Account, address: &ServerAddress, @@ -307,6 +320,14 @@ impl Client { .await?; let mut conn = conn.login(); + // this makes it so plugins can send an `SendLoginPacketEvent` event to the ecs + // and we'll send it to the server + let (ecs_packets_tx, mut ecs_packets_rx) = mpsc::unbounded_channel(); + ecs_lock + .lock() + .entity_mut(entity) + .insert(LoginSendPacketQueue { tx: ecs_packets_tx }); + // login conn.write( ServerboundHelloPacket { @@ -320,7 +341,20 @@ impl Client { .await?; let (conn, profile) = loop { - let packet = conn.read().await?; + let packet = tokio::select! { + packet = conn.read() => packet?, + Some(packet) = ecs_packets_rx.recv() => { + // write this packet to the server + conn.write(packet).await?; + continue; + } + }; + + ecs_lock.lock().send_event(login::LoginPacketEvent { + entity, + packet: Arc::new(packet.clone()), + }); + match packet { ClientboundLoginPacket::Hello(p) => { debug!("Got encryption request"); @@ -655,7 +689,7 @@ impl Plugin for AzaleaPlugin { /// [`DefaultPlugins`]. #[doc(hidden)] pub fn start_ecs_runner( - mut app: App, + app: App, run_schedule_receiver: mpsc::UnboundedReceiver<()>, run_schedule_sender: mpsc::UnboundedSender<()>, ) -> Arc> { diff --git a/azalea-client/src/interact.rs b/azalea-client/src/interact.rs index bdb17827..c8332d20 100644 --- a/azalea-client/src/interact.rs +++ b/azalea-client/src/interact.rs @@ -34,10 +34,9 @@ use tracing::warn; use crate::{ attack::handle_attack_event, inventory::{InventoryComponent, InventorySet}, - local_player::{ - handle_send_packet_event, LocalGameMode, PermissionLevel, PlayerAbilities, SendPacketEvent, - }, + local_player::{LocalGameMode, PermissionLevel, PlayerAbilities}, movement::MoveEventsSet, + packet_handling::game::{handle_send_packet_event, SendPacketEvent}, respawn::perform_respawn, Client, }; diff --git a/azalea-client/src/inventory.rs b/azalea-client/src/inventory.rs index e1ac9dd4..7bbefbee 100644 --- a/azalea-client/src/inventory.rs +++ b/azalea-client/src/inventory.rs @@ -26,7 +26,8 @@ use bevy_ecs::{ use tracing::warn; use crate::{ - local_player::{handle_send_packet_event, PlayerAbilities, SendPacketEvent}, + local_player::PlayerAbilities, + packet_handling::game::{handle_send_packet_event, SendPacketEvent}, respawn::perform_respawn, Client, }; diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 9d016bde..cbbee2dc 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -36,7 +36,7 @@ pub use client::{ start_ecs_runner, Client, DefaultPlugins, JoinError, JoinedClientBundle, TickBroadcast, }; pub use events::Event; -pub use local_player::{GameProfileComponent, InstanceHolder, SendPacketEvent, TabList}; +pub use local_player::{GameProfileComponent, InstanceHolder, TabList}; pub use movement::{ PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection, }; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index bebe841e..cf1f7475 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -160,24 +160,3 @@ impl From> for HandlePacketError { HandlePacketError::Poison(e.to_string()) } } - -/// Event for sending a packet to the server. -#[derive(Event)] -pub struct SendPacketEvent { - pub entity: Entity, - pub packet: ServerboundGamePacket, -} - -pub fn handle_send_packet_event( - mut send_packet_events: EventReader, - mut query: Query<&mut RawConnection>, -) { - for event in send_packet_events.read() { - if let Ok(raw_connection) = query.get_mut(event.entity) { - // debug!("Sending packet: {:?}", event.packet); - if let Err(e) = raw_connection.write_packet(event.packet.clone()) { - error!("Failed to send packet: {e}"); - } - } - } -} diff --git a/azalea-client/src/mining.rs b/azalea-client/src/mining.rs index 84035143..4c160a9b 100644 --- a/azalea-client/src/mining.rs +++ b/azalea-client/src/mining.rs @@ -17,8 +17,9 @@ use crate::{ HitResultComponent, SwingArmEvent, }, inventory::{InventoryComponent, InventorySet}, - local_player::{LocalGameMode, PermissionLevel, PlayerAbilities, SendPacketEvent}, + local_player::{LocalGameMode, PermissionLevel, PlayerAbilities}, movement::MoveEventsSet, + packet_handling::game::SendPacketEvent, Client, }; diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index b636fbcb..58fe1ff8 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -1,5 +1,5 @@ use crate::client::Client; -use crate::local_player::SendPacketEvent; +use crate::packet_handling::game::SendPacketEvent; use azalea_core::position::Vec3; use azalea_entity::{metadata::Sprinting, Attributes, Jumping}; use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position}; diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index 0285884e..e7d3ffd8 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -22,7 +22,7 @@ use crate::packet_handling::game::KeepAliveEvent; use crate::raw_connection::RawConnection; #[derive(Event, Debug, Clone)] -pub struct PacketEvent { +pub struct ConfigurationPacketEvent { /// The client entity that received the packet. pub entity: Entity, /// The packet that was actually received. @@ -31,7 +31,7 @@ pub struct PacketEvent { pub fn send_packet_events( query: Query<(Entity, &RawConnection), With>, - mut packet_events: ResMut>, + mut packet_events: ResMut>, ) { // we manually clear and send the events at the beginning of each update // since otherwise it'd cause issues with events in process_packet_events @@ -51,7 +51,7 @@ pub fn send_packet_events( continue; } }; - packet_events.send(PacketEvent { + packet_events.send(ConfigurationPacketEvent { entity: player_entity, packet, }); @@ -64,9 +64,10 @@ pub fn send_packet_events( pub fn process_packet_events(ecs: &mut World) { let mut events_owned = Vec::new(); - let mut system_state: SystemState> = SystemState::new(ecs); + let mut system_state: SystemState> = + SystemState::new(ecs); let mut events = system_state.get_mut(ecs); - for PacketEvent { + for ConfigurationPacketEvent { entity: player_entity, packet, } in events.read() diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index cffa0d2b..20facadb 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -23,6 +23,7 @@ use azalea_protocol::{ serverbound_keep_alive_packet::ServerboundKeepAlivePacket, serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, serverbound_pong_packet::ServerboundPongPacket, ClientboundGamePacket, + ServerboundGamePacket, }, read::deserialize_packet, }; @@ -40,8 +41,7 @@ use crate::{ SetContainerContentEvent, }, local_player::{ - GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, - SendPacketEvent, TabList, + GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList, }, movement::{KnockbackEvent, KnockbackType}, raw_connection::RawConnection, @@ -1391,3 +1391,24 @@ pub fn process_packet_events(ecs: &mut World) { } } } + +/// An event for sending a packet to the server while we're in the `game` state. +#[derive(Event)] +pub struct SendPacketEvent { + pub entity: Entity, + pub packet: ServerboundGamePacket, +} + +pub fn handle_send_packet_event( + mut send_packet_events: EventReader, + mut query: Query<&mut RawConnection>, +) { + for event in send_packet_events.read() { + if let Ok(raw_connection) = query.get_mut(event.entity) { + // debug!("Sending packet: {:?}", event.packet); + if let Err(e) = raw_connection.write_packet(event.packet.clone()) { + error!("Failed to send packet: {e}"); + } + } + } +} diff --git a/azalea-client/src/packet_handling/login.rs b/azalea-client/src/packet_handling/login.rs new file mode 100644 index 00000000..83a6a98d --- /dev/null +++ b/azalea-client/src/packet_handling/login.rs @@ -0,0 +1,49 @@ +// login packets aren't actually handled here because compression/encryption +// would make packet handling a lot messier + +use std::sync::Arc; + +use azalea_protocol::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket}; +use bevy_ecs::prelude::*; +use tokio::sync::mpsc; +use tracing::error; + +use crate::raw_connection::RawConnection; + +use super::game::SendPacketEvent; + +// this struct is defined here anyways though so it's consistent with the other +// ones + +#[derive(Event, Debug, Clone)] +pub struct LoginPacketEvent { + /// The client entity that received the packet. + pub entity: Entity, + /// The packet that was actually received. + pub packet: Arc, +} + +/// Event for sending a login packet to the server. +#[derive(Event)] +pub struct SendLoginPacketEvent { + pub entity: Entity, + pub packet: ServerboundLoginPacket, +} + +#[derive(Component)] +pub struct LoginSendPacketQueue { + pub tx: mpsc::UnboundedSender, +} + +pub fn handle_send_packet_event( + mut send_packet_events: EventReader, + mut query: Query<&mut LoginSendPacketQueue>, +) { + for event in send_packet_events.read() { + if let Ok(queue) = query.get_mut(event.entity) { + let _ = queue.tx.send(event.packet.clone()); + } else { + error!("Sent SendPacketEvent for entity that doesn't have a LoginSendPacketQueue"); + } + } +} diff --git a/azalea-client/src/packet_handling/mod.rs b/azalea-client/src/packet_handling/mod.rs index 35bdfc04..7f797a5c 100644 --- a/azalea-client/src/packet_handling/mod.rs +++ b/azalea-client/src/packet_handling/mod.rs @@ -4,13 +4,17 @@ use bevy_ecs::prelude::*; use crate::{chat::ChatReceivedEvent, events::death_listener}; -use self::game::{ - AddPlayerEvent, DeathEvent, InstanceLoadedEvent, KeepAliveEvent, RemovePlayerEvent, - ResourcePackEvent, UpdatePlayerEvent, +use self::{ + game::{ + AddPlayerEvent, DeathEvent, InstanceLoadedEvent, KeepAliveEvent, RemovePlayerEvent, + ResourcePackEvent, UpdatePlayerEvent, + }, + login::{LoginPacketEvent, SendLoginPacketEvent}, }; pub mod configuration; pub mod game; +pub mod login; pub struct PacketHandlerPlugin; @@ -37,16 +41,17 @@ impl Plugin for PacketHandlerPlugin { .add_systems( PreUpdate, ( - game::process_packet_events, + game::process_packet_events + // we want to index and deindex right after + .before(EntityUpdateSet::Deindex), configuration::process_packet_events, - ) - // we want to index and deindex right after - .before(EntityUpdateSet::Deindex), + login::handle_send_packet_event, + ), ) .add_systems(Update, death_event_on_0_health.before(death_listener)) // we do this instead of add_event so we can handle the events ourselves .init_resource::>() - .init_resource::>() + .init_resource::>() .add_event::() .add_event::() .add_event::() @@ -54,6 +59,8 @@ impl Plugin for PacketHandlerPlugin { .add_event::() .add_event::() .add_event::() - .add_event::(); + .add_event::() + .add_event::() + .add_event::(); } } diff --git a/azalea-client/src/respawn.rs b/azalea-client/src/respawn.rs index cd9b434e..150b3591 100644 --- a/azalea-client/src/respawn.rs +++ b/azalea-client/src/respawn.rs @@ -4,7 +4,7 @@ use azalea_protocol::packets::game::serverbound_client_command_packet::{ use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; -use crate::local_player::{handle_send_packet_event, SendPacketEvent}; +use crate::packet_handling::game::{handle_send_packet_event, SendPacketEvent}; /// Tell the server that we're respawning. #[derive(Event, Debug, Clone)] diff --git a/azalea/src/accept_resource_packs.rs b/azalea/src/accept_resource_packs.rs index 6fdb40db..c99b1cac 100644 --- a/azalea/src/accept_resource_packs.rs +++ b/azalea/src/accept_resource_packs.rs @@ -1,9 +1,9 @@ use crate::app::{App, Plugin}; use azalea_client::chunks::handle_chunk_batch_finished_event; use azalea_client::inventory::InventorySet; +use azalea_client::packet_handling::game::SendPacketEvent; use azalea_client::packet_handling::{death_event_on_0_health, game::ResourcePackEvent}; use azalea_client::respawn::perform_respawn; -use azalea_client::SendPacketEvent; use azalea_protocol::packets::game::serverbound_resource_pack_packet::{ self, ServerboundResourcePackPacket, }; diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 0cffb416..c6fe096f 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -2,7 +2,9 @@ use std::{sync::Arc, time::Duration}; -use azalea_client::{inventory::InventoryComponent, PhysicsState}; +use azalea_client::{ + inventory::InventoryComponent, packet_handling::game::SendPacketEvent, PhysicsState, +}; use azalea_core::{position::Vec3, resource_location::ResourceLocation}; use azalea_entity::{ attributes::AttributeInstance, metadata::Sprinting, Attributes, EntityDimensions, Physics, @@ -77,7 +79,7 @@ impl Simulation { .cloned() .collect(), }) - .add_event::(); + .add_event::(); app.edit_schedule(bevy_app::Main, |schedule| { schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);