diff --git a/Cargo.lock b/Cargo.lock index 8aa28cd..3c04bbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.8.12" @@ -1508,6 +1518,7 @@ dependencies = [ "bevy_ecs", "bevy_time", "renet", + "renet_netcode", "renet_steam", ] @@ -2155,6 +2166,41 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -2443,6 +2489,15 @@ dependencies = [ "windows 0.54.0", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -2507,6 +2562,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + [[package]] name = "csv" version = "1.4.0" @@ -3197,6 +3263,16 @@ dependencies = [ "thread_local", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "1.1.0" @@ -3815,6 +3891,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "inventory" version = "0.3.21" @@ -4810,6 +4895,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "opener" version = "0.8.3" @@ -5054,6 +5145,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -5451,6 +5553,18 @@ dependencies = [ "octets", ] +[[package]] +name = "renet_netcode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d73ffa79c2081fe93286acac186a20d49657b93b8cfa4e0e8b79b1f3ee81241" +dependencies = [ + "bevy_ecs", + "log", + "renet", + "renetcode", +] + [[package]] name = "renet_steam" version = "2.1.0" @@ -5463,6 +5577,16 @@ dependencies = [ "steamworks", ] +[[package]] +name = "renetcode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "118d456f815f7fd5bd12713a9e69a0b0f8b45806bd515e05bb60146f1867310d" +dependencies = [ + "chacha20poly1305", + "log", +] + [[package]] name = "rmp" version = "0.8.14" @@ -5941,6 +6065,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.5" @@ -6400,6 +6530,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.5.7" @@ -7470,6 +7610,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 72932ba..cfeb99b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,10 +61,8 @@ bevy_pkv = { version = "0.14", default-features = false, features = [ "redb", ] } bevy_replicon = "0.37.1" -bevy_replicon_renet = { version = "0.13.0", default-features = false, features = [ - "server", - "renet_steam", -] } +# TODO: i dont think we need this in dedicated server mode +bevy_replicon_renet = { version = "0.13.0", features = ["renet_steam"] } bevy_sprite3d = "7.0.0" bevy_trenchbroom = { version = "0.10", default-features = false, features = [ "physics-integration", @@ -79,6 +77,7 @@ rand = "=0.8.5" ron = "0.8" serde = { version = "1.0.219", features = ["derive"] } shared = { path = "crates/shared" } +# TODO: i dont think we need this in dedicated server mode steamworks = "0.12" [profile.dev.package."*"] diff --git a/crates/hedz_reloaded/src/client/mod.rs b/crates/hedz_reloaded/src/client/mod.rs index b36d6cb..055a1dc 100644 --- a/crates/hedz_reloaded/src/client/mod.rs +++ b/crates/hedz_reloaded/src/client/mod.rs @@ -1,6 +1,6 @@ use crate::{ GameState, - config::NetworkingConfig, + config::NetConfig, protocol::{ ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, messages::DespawnTbMapEntity, }, @@ -18,11 +18,9 @@ use bevy_replicon::{ use bevy_replicon_renet::{ RenetChannelsExt, renet::{ConnectionConfig, RenetClient}, - steam::SteamClientTransport, }; use bevy_steamworks::Client; use bevy_trenchbroom::geometry::Brushes; -use steamworks::SteamId; pub mod audio; pub mod backpack; @@ -53,7 +51,7 @@ pub fn plugin(app: &mut App) { app.add_systems( OnEnter(GameState::Connecting), - connect_to_server.run_if(|config: Res| config.connect_to_host()), + connect_to_server.run_if(|config: Res| config.is_client()), ); app.add_systems(Update, despawn_absent_map_entities); app.add_systems( @@ -87,7 +85,7 @@ fn on_disconnect() { fn connect_to_server( mut commands: Commands, - config: Res, + config: Res, channels: Res, steam_client: Res, ) -> Result { @@ -100,14 +98,40 @@ fn connect_to_server( ..Default::default() }); - let steam_id: u64 = config.steam_id.clone().unwrap().parse().unwrap(); - let steam_id = SteamId::from_raw(steam_id); - - info!("attempting connection to {steam_id:?}"); - let transport = SteamClientTransport::new((**steam_client).clone(), &steam_id)?; - commands.insert_resource(client); - commands.insert_resource(transport); + + if let NetConfig::SteamClient(host_steam_id) = &*config { + info!("connecting to steam host: {host_steam_id:?}"); + let transport = bevy_replicon_renet::steam::SteamClientTransport::new( + (**steam_client).clone(), + host_steam_id, + )?; + + commands.insert_resource(transport); + } else if let NetConfig::NetcodeClient(host_addr) = &*config { + use std::time::SystemTime; + + info!("connecting to netcode host: {host_addr:?}"); + + let current_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + let client_id = current_time.as_millis() as u64; + let socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))?; + let authentication = bevy_replicon_renet::netcode::ClientAuthentication::Unsecure { + client_id, + protocol_id: 0, + server_addr: host_addr.clone(), + user_data: None, + }; + let transport = bevy_replicon_renet::netcode::NetcodeClientTransport::new( + current_time, + authentication, + socket, + )?; + + commands.insert_resource(transport); + } Ok(()) } diff --git a/crates/hedz_reloaded/src/config.rs b/crates/hedz_reloaded/src/config.rs index d1553f6..582f1f0 100644 --- a/crates/hedz_reloaded/src/config.rs +++ b/crates/hedz_reloaded/src/config.rs @@ -1,26 +1,92 @@ +use std::net::SocketAddr; + use bevy::prelude::*; use clap::Parser; +use steamworks::SteamId; pub fn plugin(app: &mut App) { let config = NetworkingConfig::parse(); + let config: NetConfig = config.into(); + + info!("net config: {:?}", config); + app.insert_resource(config); } -#[derive(Resource, Parser, Debug)] +#[derive(Parser, Debug)] #[command(version, about, long_about = None)] -pub struct NetworkingConfig { +struct NetworkingConfig { /// Steam id of the host to connect to #[arg(long)] - pub steam_id: Option, - /// Whether or not to open a port when opening the client, for other clients - /// to connect. Does nothing if `server` is set. + pub steam_host_id: Option, + + /// Act as steam host #[arg(long)] - pub host: bool, + pub steam_host: bool, + + /// Act as host using netcode, so we have to define our port + #[arg(long)] + pub netcode_host: Option>, + + /// Host address we connect to as a client + #[arg(long)] + pub netcode_client: Option>, } -impl NetworkingConfig { - pub fn connect_to_host(&self) -> bool { - self.steam_id.is_some() +#[derive(Resource, Debug)] +pub enum NetConfig { + Singleplayer, + SteamHost, + NetcodeHost { port: u16 }, + SteamClient(SteamId), + NetcodeClient(SocketAddr), +} + +impl NetConfig { + pub fn is_client(&self) -> bool { + matches!( + self, + NetConfig::SteamClient(_) | NetConfig::NetcodeClient(_) + ) + } + + pub fn is_host(&self) -> bool { + matches!(self, NetConfig::SteamHost | NetConfig::NetcodeHost { .. }) + } + + pub fn is_singleplayer(&self) -> bool { + !self.is_client() && !self.is_host() } } + +impl From for NetConfig { + fn from(config: NetworkingConfig) -> Self { + match ( + config.steam_host, + config.steam_host_id, + config.netcode_host, + config.netcode_client, + ) { + (false, None, None, None) => Self::Singleplayer, + (true, None, None, None) => Self::SteamHost, + (false, Some(id), None, None) => Self::SteamClient(parse_steam_id(id)), + (false, None, Some(port), None) => Self::NetcodeHost { + port: port.unwrap_or(31111), + }, + (false, None, None, Some(addr)) => Self::NetcodeClient(parse_addr(addr)), + _ => panic!("Invalid configuration"), + } + } +} + +fn parse_addr(addr: Option) -> SocketAddr { + addr.map(|addr| addr.parse().ok()) + .flatten() + .unwrap_or_else(|| "127.0.0.1:31111".parse().unwrap()) +} + +fn parse_steam_id(id: String) -> SteamId { + let id: u64 = id.parse().unwrap(); + SteamId::from_raw(id) +} diff --git a/crates/hedz_reloaded/src/lib.rs b/crates/hedz_reloaded/src/lib.rs index 6858885..133b43e 100644 --- a/crates/hedz_reloaded/src/lib.rs +++ b/crates/hedz_reloaded/src/lib.rs @@ -34,7 +34,7 @@ pub mod utils; pub mod water; use crate::{ - config::NetworkingConfig, + config::NetConfig, heads_database::{HeadDatabaseAsset, HeadsDatabase}, protocol::{PlayerIdCounter, messages::AssignClientPlayer}, tb_entities::SpawnPoint, @@ -52,7 +52,6 @@ use bevy_trenchbroom::{ TrenchBroomPlugins, config::TrenchBroomConfig, prelude::TrenchBroomPhysicsPlugin, }; use bevy_trenchbroom_avian::AvianPhysicsBackend; -use steamworks::{CallbackResult, P2PSessionRequest}; use utils::{billboards, squish_animation}; pub const HEDZ_GREEN: Srgba = Srgba::rgb(0.0, 1.0, 0.0); @@ -150,23 +149,21 @@ pub fn plugin(app: &mut App) { if cfg!(feature = "client") { app.add_systems( OnEnter(GameState::Waiting), - start_solo_client - .run_if(|config: Res| !config.connect_to_host() && !config.host), + start_solo_client.run_if(|config: Res| config.is_singleplayer()), ); app.add_systems( OnEnter(GameState::Waiting), - start_listen_server - .run_if(|config: Res| !config.connect_to_host() && config.host), + start_listen_server.run_if(|config: Res| config.is_host()), ); app.add_systems( OnEnter(GameState::Waiting), - start_client.run_if(|config: Res| config.connect_to_host()), + start_client.run_if(|config: Res| config.is_client()), ); } else { app.add_systems(OnEnter(GameState::Waiting), start_dedicated_server); } - app.add_systems(Update, accept_p2p_sessions); + app.add_systems(Update, log_steam_events); } #[derive(Resource, Reflect, Debug)] @@ -196,20 +193,14 @@ pub enum GameState { Playing, } -fn accept_p2p_sessions( - mut events: MessageReader, - steam_client: Res, -) { +fn log_steam_events(events: Option>) { + let Some(mut events) = events else { + return; + }; + for event in events.read() { - if let SteamworksEvent::CallbackResult(CallbackResult::P2PSessionRequest( - P2PSessionRequest { remote }, - )) = event - { - info!("Accepting P2P session from: {:?}", remote); - steam_client.networking().accept_p2p_session(*remote); - } else { - info!("steamworks event: {:?}", event); - } + let SteamworksEvent::CallbackResult(result) = event; + info!("steam: {:?}", result); } } diff --git a/crates/hedz_reloaded/src/server.rs b/crates/hedz_reloaded/src/server.rs index 3ed6d6b..edccb1d 100644 --- a/crates/hedz_reloaded/src/server.rs +++ b/crates/hedz_reloaded/src/server.rs @@ -1,5 +1,7 @@ use crate::{ - GameState, global_observer, + GameState, + config::NetConfig, + global_observer, heads_database::HeadsDatabase, player::ClientPlayerId, protocol::{ClientEnteredPlaying, PlayerIdCounter, SetGameTick, messages::AssignClientPlayer}, @@ -55,7 +57,8 @@ fn open_renet_server( mut commands: Commands, channels: Res, mut next: ResMut>, - steam_client: Res, + steam_client: Option>, + config: Res, ) -> Result<(), BevyError> { info!("opening server"); @@ -68,18 +71,46 @@ fn open_renet_server( ..Default::default() }); - let steam_config = bevy_replicon_renet::steam::SteamServerConfig { - access_permission: bevy_replicon_renet::steam::AccessPermission::FriendsOnly, - max_clients: 16, - }; + if let NetConfig::SteamHost = *config { + let Some(steam_client) = steam_client else { + return Err("Steam client not found".into()); + }; - let client = (**steam_client).clone(); - let transport = SteamServerTransport::new(client, steam_config)?; + let steam_config = bevy_replicon_renet::steam::SteamServerConfig { + access_permission: bevy_replicon_renet::steam::AccessPermission::FriendsOnly, + max_clients: 16, + }; - commands.queue(|w: &mut World| { - w.insert_resource(server); - w.insert_non_send_resource(transport); - }); + let client = (**steam_client).clone(); + let transport = SteamServerTransport::new(client, steam_config)?; + + commands.queue(|w: &mut World| { + w.insert_resource(server); + w.insert_non_send_resource(transport); + }); + + info!("hosting server: steam"); + } else if let NetConfig::NetcodeHost { port } = *config { + use std::time::SystemTime; + + let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; + let socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, port))?; + + let server_config = bevy_replicon_renet::netcode::ServerConfig { + current_time, + max_clients: 1, + protocol_id: 0, + authentication: bevy_replicon_renet::netcode::ServerAuthentication::Unsecure, + public_addresses: Default::default(), + }; + let transport = + bevy_replicon_renet::netcode::NetcodeServerTransport::new(server_config, socket)?; + + commands.insert_resource(server); + commands.insert_resource(transport); + + info!("hosting server: netcode on port {port}"); + } next.set(GameState::Playing); diff --git a/justfile b/justfile index 2e0a885..a11cf16 100644 --- a/justfile +++ b/justfile @@ -12,8 +12,8 @@ server_args := "--bin hedz_reloaded_server --no-default-features" run *args: RUST_BACKTRACE=1 cargo r {{ client_args }} -- {{ args }} -server: - RUST_BACKTRACE=1 cargo r {{ server_args }} +server *args: + RUST_BACKTRACE=1 cargo r {{ server_args }} -- {{ args }} dbg *args: RUST_BACKTRACE=1 cargo r {{ client_args }} --features dbg -- {{ args }} @@ -22,7 +22,7 @@ dbg-server: RUST_BACKTRACE=1 cargo r {{ server_args }} --features dbg sort: - cargo sort --check --workspace + cargo sort --workspace check: cargo sort --check --workspace