make it so plugins can send and receive packets during the login state

This commit is contained in:
mat 2023-12-03 02:41:09 -06:00
parent 0713223e12
commit 1f46ef8c11
22 changed files with 211 additions and 80 deletions

12
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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::*;

View file

@ -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
}

View file

@ -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() {}

View file

@ -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;

View file

@ -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.

View file

@ -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,
};

View file

@ -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<Event>), 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::<EntityUuidIndex>();
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::<EntityUuidIndex>();
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::<EntityUuidIndex>();
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::<EntityUuidIndex>();
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<Mutex<World>>,
entity: Entity,
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
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<Mutex<World>> {

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -160,24 +160,3 @@ impl<T> From<std::sync::PoisonError<T>> 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<SendPacketEvent>,
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}");
}
}
}
}

View file

@ -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,
};

View file

@ -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};

View file

@ -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<InConfigurationState>>,
mut packet_events: ResMut<Events<PacketEvent>>,
mut packet_events: ResMut<Events<ConfigurationPacketEvent>>,
) {
// 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<EventReader<PacketEvent>> = SystemState::new(ecs);
let mut system_state: SystemState<EventReader<ConfigurationPacketEvent>> =
SystemState::new(ecs);
let mut events = system_state.get_mut(ecs);
for PacketEvent {
for ConfigurationPacketEvent {
entity: player_entity,
packet,
} in events.read()

View file

@ -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<SendPacketEvent>,
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}");
}
}
}
}

View file

@ -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<ClientboundLoginPacket>,
}
/// 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<ServerboundLoginPacket>,
}
pub fn handle_send_packet_event(
mut send_packet_events: EventReader<SendLoginPacketEvent>,
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");
}
}
}

View file

@ -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::<Events<game::PacketEvent>>()
.init_resource::<Events<configuration::PacketEvent>>()
.init_resource::<Events<configuration::ConfigurationPacketEvent>>()
.add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>()
@ -54,6 +59,8 @@ impl Plugin for PacketHandlerPlugin {
.add_event::<DeathEvent>()
.add_event::<KeepAliveEvent>()
.add_event::<ResourcePackEvent>()
.add_event::<InstanceLoadedEvent>();
.add_event::<InstanceLoadedEvent>()
.add_event::<LoginPacketEvent>()
.add_event::<SendLoginPacketEvent>();
}
}

View file

@ -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)]

View file

@ -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,
};

View file

@ -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::<azalea_client::SendPacketEvent>();
.add_event::<SendPacketEvent>();
app.edit_schedule(bevy_app::Main, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);