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 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1358200
feat: Velocity Support
GStudiosX2 Dec 10, 2024
cfeec4d
fix PlayerIdentity documentation
GStudiosX2 Dec 10, 2024
f94bbe8
Implement Suggestions
GStudiosX2 Dec 10, 2024
13bf0b5
test
GStudiosX2 Dec 16, 2024
bea9da2
Spawn Player Entities (no sync) and Spawn Players on highest non air …
GStudiosX2 Dec 16, 2024
3446c04
Revert "test"
GStudiosX2 Dec 16, 2024
a2da495
Hopefully fixed all the conflicts and everything still works
GStudiosX2 Dec 30, 2024
22c5919
I think deadlocking on join fixed
GStudiosX2 Dec 30, 2024
361181d
Remove comment on PlayerStartLoginEvent#profile
GStudiosX2 Dec 30, 2024
3bcc5d6
Fixed a few clippy warnings
GStudiosX2 Dec 31, 2024
b2b333f
Merge branch 'master' into feat/velocity
GStudiosX2 Dec 31, 2024
c47dfc2
Fix merge conflicts
GStudiosX2 Jan 2, 2025
66ba4c9
Merge branch 'master' into feat/velocity
GStudiosX2 Jan 2, 2025
efc3018
Fix clippy errors
GStudiosX2 Jan 2, 2025
7748158
Forgot to removeextra parentheses
GStudiosX2 Jan 2, 2025
20e6054
Hopefully all clippy errors are fixed
GStudiosX2 Jan 2, 2025
d002329
Move a few things around
GStudiosX2 Jan 2, 2025
6e5db56
Fix code formatting
GStudiosX2 Jan 2, 2025
7fa503b
FFix merge conflicts, Fix crouching with geyser
GStudiosX2 Jan 2, 2025
0e1c148
Fixed merge conflicts
GStudiosX2 Jan 5, 2025
d11c03e
[no ci] Delete grep
GStudiosX2 Jan 5, 2025
ce37083
cancelable events
GStudiosX2 Jan 12, 2025
07a442b
Merge branch 'master' into feat/velocity
GStudiosX2 Jan 19, 2025
6afbf0c
fixed problem with quote! in profiling
Sweattypalms Jan 12, 2025
8f626da
cargo fmt and fixed macro expansion for profiler
Sweattypalms Jan 16, 2025
c8713ac
dashmap -> hashmap for chunk recv
Sweattypalms Jan 17, 2025
2ae8179
fmt
Sweattypalms Jan 17, 2025
a4ee2d4
oopsie daisies
Sweattypalms Jan 17, 2025
427bb86
Fix conflicts
GStudiosX2 Jan 21, 2025
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