Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Velocity Support #139

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .etc/example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ map_size = 1_000
cache_ttl = 60
# How big the cache can be in kb.
cache_capacity = 20_000

# Velocity configuration
[velocity]
enabled = false
# The key from forwarding.secret
secret = ""
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ thiserror = "2.0.3"
rand = "0.9.0-beta.0"
fnv = "1.0.7"
wyhash = "0.5.0"
sha2 = "=0.10.8"
hmac = "0.12.1"

# Encoding/Serialization
serde = { version = "1.0.210", features = ["derive"] }
Expand All @@ -136,6 +138,7 @@ serde_derive = "1.0.210"
base64 = "0.22.1"
bitcode = "0.6.3"
bitcode_derive = "0.6.3"
bitmask-enum = "2.2.5"

# Bit manipulation
byteorder = "1.5.0"
Expand Down
5 changes: 4 additions & 1 deletion src/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ ferrumc-macros = { workspace = true }
ferrumc-nbt = { workspace = true }
ferrumc-general-purpose = { workspace = true }
ferrumc-state = { workspace = true }
ferrumc-text = { workspace = true }

rand = { workspace = true }
ctor = { workspace = true }
parking_lot = { workspace = true }
tracing = { workspace = true }
Expand All @@ -36,7 +38,8 @@ futures = { workspace = true }
serde_json = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive"] }

hmac = { workspace = true }
sha2 = { workspace = true }

[[bin]]
name = "ferrumc"
Expand Down
21 changes: 21 additions & 0 deletions src/bin/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use ferrumc_core::identity::player_identity::*;
use ferrumc_macros::Event;
use ferrumc_ecs::entities::Entity;

/// This event is triggered when the player attempts to log on to the server.
///
/// Beware that not all components on the entity may be set yet this event is mostly for:
/// a custom handshaking protocol before the player logs in using login plugin messages/etc.
///
#[derive(Event, Clone)]
pub struct PlayerStartLoginEvent {
/// The entity that this event was fired for.
pub entity: Entity,

/// This profile can be changed and after the event is finished this will be the new profile.
///
/// Be warned that this event can be cancelled or this field can be overriden by other listeners and
/// this could mean your profile will never be used.
///
pub profile: PlayerIdentity,
}
27 changes: 27 additions & 0 deletions src/bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,42 @@ use ferrumc_world::World;
use std::sync::Arc;
use systems::definition;
use tracing::{error, info};
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket;
use ferrumc_net::{connection::StreamWriter, NetResult};
use ferrumc_net_codec::encode::NetEncodeOpts;

pub(crate) mod errors;
use crate::cli::{CLIArgs, Command, ImportArgs};
mod cli;
mod packet_handlers;
mod systems;
pub mod events;
mod velocity;

pub type Result<T> = std::result::Result<T, BinaryError>;

pub async fn send_login_success(state: Arc<ServerState>, conn_id: usize, identity: PlayerIdentity) -> NetResult<()> {
//Send a Login Success Response to further the login sequence
let mut writer = state
.universe
.get_mut::<StreamWriter>(conn_id)?;

writer
.send_packet(
&LoginSuccessPacket::new(identity.clone()),
&NetEncodeOpts::WithLength,
)
.await?;

state.universe.add_component::<PlayerIdentity>(
conn_id,
identity,
)?;

Ok(())
}

#[tokio::main]
async fn main() {
let cli_args = CLIArgs::parse();
Expand Down
45 changes: 26 additions & 19 deletions src/bin/src/packet_handlers/login_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use ferrumc_net::packets::outgoing::finish_configuration::FinishConfigurationPac
use ferrumc_net::packets::outgoing::game_event::GameEventPacket;
use ferrumc_net::packets::outgoing::keep_alive::OutgoingKeepAlivePacket;
use ferrumc_net::packets::outgoing::login_play::LoginPlayPacket;
use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket;
use ferrumc_net::packets::outgoing::registry_data::get_registry_packets;
use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk;
use ferrumc_net::packets::outgoing::set_default_spawn_position::SetDefaultSpawnPositionPacket;
Expand All @@ -25,6 +24,10 @@ use ferrumc_net::packets::outgoing::synchronize_player_position::SynchronizePlay
use ferrumc_net_codec::encode::NetEncodeOpts;
use ferrumc_state::GlobalState;
use tracing::{debug, trace};
use ferrumc_net::packets::outgoing::player_info_update::{PlayerInfoUpdatePacket, PlayerInfo};
use crate::events::PlayerStartLoginEvent;
use ferrumc_events::errors::EventsError;
use ferrumc_events::infrastructure::Event;

#[event_handler]
async fn handle_login_start(
Expand All @@ -38,24 +41,20 @@ async fn handle_login_start(
debug!("Received login start from user with username {}", username);

// Add the player identity component to the ECS for the entity.
state.universe.add_component::<PlayerIdentity>(
login_start_event.conn_id,
PlayerIdentity::new(username.to_string(), uuid),
)?;

//Send a Login Success Response to further the login sequence
let mut writer = state
.universe
.get_mut::<StreamWriter>(login_start_event.conn_id)?;

writer
.send_packet(
&LoginSuccessPacket::new(uuid, username),
&NetEncodeOpts::WithLength,
)
.await?;

Ok(login_start_event)
let event = PlayerStartLoginEvent {
entity: login_start_event.conn_id,
profile: PlayerIdentity::new(username.to_string(), uuid),
};

match PlayerStartLoginEvent::trigger(event, state.clone()).await {
Err(NetError::Kick(msg)) => Err(NetError::Kick(msg)),
Err(NetError::EventsError(EventsError::Cancelled)) => Ok(login_start_event),
Ok(event) => {
crate::send_login_success(state, login_start_event.conn_id, event.profile).await?;
Ok(login_start_event)
},
e => e.map(|_| login_start_event),
}
}

#[event_handler]
Expand Down Expand Up @@ -168,6 +167,14 @@ async fn handle_ack_finish_configuration(
&NetEncodeOpts::WithLength,
)
.await?;

let profile = state
.universe
.get::<PlayerIdentity>(ack_finish_configuration_event.conn_id)?;
writer.send_packet(&PlayerInfoUpdatePacket::new(vec![
PlayerInfo::from(&profile)
]), &NetEncodeOpts::WithLength).await?;

send_keep_alive(conn_id, state, &mut writer).await?;

Ok(ack_finish_configuration_event)
Expand Down
4 changes: 2 additions & 2 deletions src/bin/src/systems/ticking_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ impl System for TickingSystem {
let required_end = Instant::now() + Duration::from_millis(50);
// TODO handle error
let res = TickEvent::trigger(TickEvent::new(tick), state.clone()).await;
if res.is_err() {
debug!("error handling tick event: {:?}", res);
if let Err(e) = res {
debug!("error handling tick event: {:?}", e);
}
let now = Instant::now();
if required_end > now {
Expand Down
102 changes: 102 additions & 0 deletions src/bin/src/velocity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use std::io::Cursor;
use crate::events::*;
use ferrumc_state::GlobalState;
use ferrumc_net::packets::outgoing::client_bound_plugin_message::*;
use ferrumc_net::packets::incoming::server_bound_plugin_message::*;
use ferrumc_net::{connection::StreamWriter, errors::NetError, NetResult};
use ferrumc_events::errors::EventsError;
use ferrumc_macros::event_handler;
use ferrumc_net_codec::decode::NetDecode;
use ferrumc_text::*;
use ferrumc_net::utils::ecs_helpers::EntityExt;
use ferrumc_net_codec::{encode::NetEncodeOpts, decode::NetDecodeOpts, net_types::var_int::VarInt};
use ferrumc_config::statics::get_global_config;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use tokio::io::AsyncReadExt;
use sha2::Sha256;
use hmac::{Hmac, Mac};

type HmacSha256 = Hmac<Sha256>;

struct VelocityMessageId(u32);

#[event_handler]
async fn handle_login_start(
event: PlayerStartLoginEvent,
state: GlobalState,
) -> NetResult<PlayerStartLoginEvent> {
if get_global_config().velocity.enabled {
let id = rand::random::<u32>();
let mut writer = event.entity
.get_mut::<StreamWriter>(&state.clone())?;
writer.send_packet(&LoginPluginMessagePacket::<()>::new(id, String::from("velocity:player_info"), ()), &NetEncodeOpts::WithLength).await?;
state.universe.add_component(event.entity, VelocityMessageId(id))?;

// this stops the packet handler from doing login success
Err(NetError::EventsError(EventsError::Cancelled))
} else {
Ok(event)
}
}

#[event_handler]
async fn handle_velocity_response(
event: LoginPluginResponseEvent,
state: GlobalState,
) -> NetResult<LoginPluginResponseEvent> {
let message = &event.packet;
if message.message_id.val as u32 == event.entity.get::<VelocityMessageId>(&state.clone())?.0 {
state.universe.remove_component::<VelocityMessageId>(event.entity)?;

let len = message.data.len();

let mut signature = vec![0u8; 32];
let mut data = Vec::with_capacity(256);
let mut buf = Cursor::new(&message.data);

if len > 0 && message.success {
buf.read_exact(&mut signature).await?;

let index = buf.position();
buf.read_to_end(&mut data).await?;
buf.set_position(index);

let version = VarInt::decode(&mut buf, &NetDecodeOpts::None)?;
let _addr = String::decode(&mut buf, &NetDecodeOpts::None)?;

if version != 1 {
return Err(NetError::kick(TextComponentBuilder::new("[FerrumC]")
.color(NamedColor::Blue)
.space()
+ ComponentBuilder::text("This velocity modern forwarding version is not supported!")
.color(NamedColor::Red)
.build()));
}
} else {
return Err(NetError::kick(ComponentBuilder::text("[FerrumC]")
.color(NamedColor::Blue)
.space()
+ ComponentBuilder::text("The velocity proxy did not send forwarding information!")
.color(NamedColor::Red)
.build()));
}

let mut key = HmacSha256::new_from_slice(get_global_config().velocity.secret.as_bytes())
.expect("Failed to create HmacSha256 for velocity secret");
key.update(&data);

if key.verify_slice(&signature[..]).is_ok() {
crate::send_login_success(
state.clone(),
event.entity,
PlayerIdentity::decode(&mut buf, &NetDecodeOpts::None)?,
).await?;

Ok(event)
} else {
Err(NetError::kick("Invalid proxy response!".to_string()))
}
} else {
Ok(event)
}
}
8 changes: 7 additions & 1 deletion src/lib/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ edition = "2021"
[dependencies]
thiserror = { workspace = true }
tokio = { workspace = true}
ferrumc-ecs = {workspace = true}
ferrumc-ecs = { workspace = true }

# this could cause issues
ferrumc-net-codec = { workspace = true }
ferrumc-macros = { workspace = true }
ferrumc-config = { workspace = true }
flate2 = { workspace = true }
Loading