From 681692ddc3cf1cd111d3516e004491e8ec980b7b Mon Sep 17 00:00:00 2001 From: raymondtri Date: Mon, 24 Feb 2025 17:49:29 -0500 Subject: [PATCH 01/13] actually built --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- client.d.ts | 3 +++ index.js | 8 ++++---- src/api/mod.rs | 1 + src/api/networking_sockets.rs | 37 +++++++++++++++++++++++++++++++++++ 6 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 src/api/networking_sockets.rs diff --git a/Cargo.lock b/Cargo.lock index bd7252f..2621741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -320,7 +320,7 @@ dependencies = [ [[package]] name = "steamworks" version = "0.11.0" -source = "git+https://github.com/Noxime/steamworks-rs/?rev=fbb79635b06b4feea8261e5ca3e8ea3ef42facf9#fbb79635b06b4feea8261e5ca3e8ea3ef42facf9" +source = "git+https://github.com/Noxime/steamworks-rs/?rev=d05eaa60db328156aeee172b0b157f3f69c70b30#d05eaa60db328156aeee172b0b157f3f69c70b30" dependencies = [ "bitflags 1.3.2", "lazy_static", @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "steamworks-sys" version = "0.11.0" -source = "git+https://github.com/Noxime/steamworks-rs/?rev=fbb79635b06b4feea8261e5ca3e8ea3ef42facf9#fbb79635b06b4feea8261e5ca3e8ea3ef42facf9" +source = "git+https://github.com/Noxime/steamworks-rs/?rev=d05eaa60db328156aeee172b0b157f3f69c70b30#d05eaa60db328156aeee172b0b157f3f69c70b30" [[package]] name = "steamworksjs" diff --git a/Cargo.toml b/Cargo.toml index c6d4428..78ccd30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ napi = { version = "2.16.8", features = ["tokio_rt", "napi6", "serde-json"] } napi-derive = "2.16.9" lazy_static = "1" tokio = { version = "1", features = ["sync", "time"] } -steamworks = { git = "https://github.com/Noxime/steamworks-rs/", rev = "fbb79635b06b4feea8261e5ca3e8ea3ef42facf9", features = ["serde"] } +steamworks = { git = "https://github.com/Noxime/steamworks-rs/", rev = "d05eaa60db328156aeee172b0b157f3f69c70b30", features = ["serde"] } serde = "1" serde_json = "1" diff --git a/client.d.ts b/client.d.ts index c2fbb50..276661f 100644 --- a/client.d.ts +++ b/client.d.ts @@ -188,6 +188,9 @@ export declare namespace networking { export function readP2PPacket(size: number): P2PPacket export function acceptP2PSession(steamId64: bigint): void } +export declare namespace networking_sockets { + export function createListenSocketIp(localAddress: number): boolean +} export declare namespace overlay { export const enum Dialog { Friends = 0, diff --git a/index.js b/index.js index bc9e54d..a3f6917 100644 --- a/index.js +++ b/index.js @@ -5,14 +5,14 @@ const { platform, arch } = process let nativeBinding = undefined if (platform === 'win32' && arch === 'x64') { - nativeBinding = require('./dist/win64/steamworksjs.win32-x64-msvc.node') + nativeBinding = require('steamworks.js/dist/win64/steamworksjs.win32-x64-msvc.node') } else if (platform === 'linux' && arch === 'x64') { - nativeBinding = require('./dist/linux64/steamworksjs.linux-x64-gnu.node') + nativeBinding = require('steamworks.js/dist/linux64/steamworksjs.linux-x64-gnu.node') } else if (platform === 'darwin') { if (arch === 'x64') { - nativeBinding = require('./dist/osx/steamworksjs.darwin-x64.node') + nativeBinding = require('steamworks.js/dist/osx/steamworksjs.darwin-x64.node') } else if (arch === 'arm64') { - nativeBinding = require('./dist/osx/steamworksjs.darwin-arm64.node') + nativeBinding = require('steamworks.js/dist/osx/steamworksjs.darwin-arm64.node') } } else { throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) diff --git a/src/api/mod.rs b/src/api/mod.rs index 22161ba..256e510 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -7,6 +7,7 @@ pub mod input; pub mod localplayer; pub mod matchmaking; pub mod networking; +pub mod networking_sockets; pub mod overlay; pub mod stats; pub mod utils; diff --git a/src/api/networking_sockets.rs b/src/api/networking_sockets.rs new file mode 100644 index 0000000..75c4d54 --- /dev/null +++ b/src/api/networking_sockets.rs @@ -0,0 +1,37 @@ +use napi_derive::napi; + +#[napi] +pub mod networking_sockets { + use napi::{ + bindgen_prelude::{Array, BigInt, Buffer, Object}, + Error, JsObject, + }; + + use std::net::SocketAddr; + use std::iter; + + use steamworks::{ + networking_sockets::{ + ListenSocket + }, + networking_types::{ + NetworkingConfigEntry + } + }; + + #[napi] + pub fn create_listen_socket_ip( + local_address: u16 + ) -> Result + { + let local_address = SocketAddr::from(([0, 0, 0, 0], local_address)); + + let client = crate::client::get_client(); + let handle = client.networking_sockets().create_listen_socket_ip( + local_address, + iter::empty::() + ); + + Ok(handle.is_ok()) + } +} \ No newline at end of file From 1bd67b48de8b51b24d921672c7eb436568e23a36 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Mon, 24 Feb 2025 20:08:57 -0500 Subject: [PATCH 02/13] Added in more robust support for network messaging, untested but compiling. No support for callbacks yet because that's a nightmare. --- Cargo.lock | 4 +- Cargo.toml | 2 +- client.d.ts | 34 +++++++++++- src/api/callback.rs | 25 +++++++-- src/api/mod.rs | 2 +- src/api/networking_messages.rs | 98 ++++++++++++++++++++++++++++++++++ src/api/networking_sockets.rs | 37 ------------- 7 files changed, 154 insertions(+), 48 deletions(-) create mode 100644 src/api/networking_messages.rs delete mode 100644 src/api/networking_sockets.rs diff --git a/Cargo.lock b/Cargo.lock index 2621741..f2f2358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,7 +320,7 @@ dependencies = [ [[package]] name = "steamworks" version = "0.11.0" -source = "git+https://github.com/Noxime/steamworks-rs/?rev=d05eaa60db328156aeee172b0b157f3f69c70b30#d05eaa60db328156aeee172b0b157f3f69c70b30" +source = "git+https://github.com/Noxime/steamworks-rs/?rev=5fc8ef13d52e82068f031535446d786cf0bd60a8#5fc8ef13d52e82068f031535446d786cf0bd60a8" dependencies = [ "bitflags 1.3.2", "lazy_static", @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "steamworks-sys" version = "0.11.0" -source = "git+https://github.com/Noxime/steamworks-rs/?rev=d05eaa60db328156aeee172b0b157f3f69c70b30#d05eaa60db328156aeee172b0b157f3f69c70b30" +source = "git+https://github.com/Noxime/steamworks-rs/?rev=5fc8ef13d52e82068f031535446d786cf0bd60a8#5fc8ef13d52e82068f031535446d786cf0bd60a8" [[package]] name = "steamworksjs" diff --git a/Cargo.toml b/Cargo.toml index 78ccd30..0857e58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ napi = { version = "2.16.8", features = ["tokio_rt", "napi6", "serde-json"] } napi-derive = "2.16.9" lazy_static = "1" tokio = { version = "1", features = ["sync", "time"] } -steamworks = { git = "https://github.com/Noxime/steamworks-rs/", rev = "d05eaa60db328156aeee172b0b157f3f69c70b30", features = ["serde"] } +steamworks = { git = "https://github.com/Noxime/steamworks-rs/", rev = "5fc8ef13d52e82068f031535446d786cf0bd60a8", features = ["serde"] } serde = "1" serde_json = "1" diff --git a/client.d.ts b/client.d.ts index 276661f..e86ad6d 100644 --- a/client.d.ts +++ b/client.d.ts @@ -188,8 +188,38 @@ export declare namespace networking { export function readP2PPacket(size: number): P2PPacket export function acceptP2PSession(steamId64: bigint): void } -export declare namespace networking_sockets { - export function createListenSocketIp(localAddress: number): boolean +export declare namespace networking_messages { + /** The method used to send a packet */ + export const enum SendType { + /** + * Send the packet directly over udp. + * + * Can't be larger than 1200 bytes + */ + Unreliable = 0, + /** + * Like `Unreliable` but doesn't buffer packets + * sent before the connection has started. + */ + UnreliableNoDelay = 1, + /** + * Reliable packet sending. + * + * Can't be larger than 1 megabyte. + */ + Reliable = 2, + /** + * Like `Reliable` but applies the nagle + * algorithm to packets being sent + */ + ReliableWithBuffering = 3 + } + export function sendMessageToUser(steamId64: bigint, sendType: SendType, data: Buffer, channel?: number | undefined | null): void + export interface Message { + data: Buffer + steamId?: PlayerSteamId + } + export function receiveMessagesOnChannel(channel: number, batchSize?: number | undefined | null): Array } export declare namespace overlay { export const enum Dialog { diff --git a/src/api/callback.rs b/src/api/callback.rs index 3978088..d97c1be 100644 --- a/src/api/callback.rs +++ b/src/api/callback.rs @@ -32,6 +32,8 @@ pub mod callback { LobbyChatUpdate, P2PSessionRequest, P2PSessionConnectFail, + // NetworkingMessagesSessionRequest, + //NetworkingMessagesSessionFailed, GameLobbyJoinRequested, MicroTxnAuthorizationResponse, } @@ -71,6 +73,19 @@ pub mod callback { SteamCallback::P2PSessionConnectFail => { register_callback::(threadsafe_handler) } + // this one fails because the request can't leave the rust context or it auto denies + // not serializable at the crate level + /* + SteamCallback::NetworkingMessagesSessionRequest => { + register_callback::(threadsafe_handler) + } + */ + // This is still a private struct in the steamworks crate + /* + SteamCallback::NetworkingMessagesSessionFailed => { + register_callback::(threadsafe_handler) + } + */ SteamCallback::GameLobbyJoinRequested => { register_callback::(threadsafe_handler) } @@ -90,10 +105,10 @@ pub mod callback { where C: steamworks::Callback + serde::Serialize, { - let client = crate::client::get_client(); - client.register_callback(move |value: C| { - let value = serde_json::to_value(&value).unwrap(); - threadsafe_handler.call(value, ThreadsafeFunctionCallMode::Blocking); - }) + let client = crate::client::get_client(); + client.register_callback(move |value: C| { + let value = serde_json::to_value(&value).unwrap(); + threadsafe_handler.call(value, ThreadsafeFunctionCallMode::Blocking); + }) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 256e510..1613c9e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -7,7 +7,7 @@ pub mod input; pub mod localplayer; pub mod matchmaking; pub mod networking; -pub mod networking_sockets; +pub mod networking_messages; pub mod overlay; pub mod stats; pub mod utils; diff --git a/src/api/networking_messages.rs b/src/api/networking_messages.rs new file mode 100644 index 0000000..f9837c0 --- /dev/null +++ b/src/api/networking_messages.rs @@ -0,0 +1,98 @@ +use napi_derive::napi; + +#[napi] +pub mod networking_messages { + use napi::{ + bindgen_prelude::{BigInt, Buffer}, + Error + }; + use steamworks::{ + SteamId, + SteamError, + ClientManager, + networking_types::{ + SendFlags, + NetworkingIdentity, + NetworkingMessage, + }, + networking_messages::SessionRequest, + }; + + use crate::api::localplayer::PlayerSteamId; + + fn err(steam_err:SteamError) -> Result<(), Error> { + Err(Error::new( + napi::Status::GenericFailure, + steam_err.to_string() + )) + } + + #[napi] + /// The method used to send a packet + pub enum SendType { + /// Send the packet directly over udp. + /// + /// Can't be larger than 1200 bytes + Unreliable, + /// Like `Unreliable` but doesn't buffer packets + /// sent before the connection has started. + UnreliableNoDelay, + /// Reliable packet sending. + /// + /// Can't be larger than 1 megabyte. + Reliable, + /// Like `Reliable` but applies the nagle + /// algorithm to packets being sent + ReliableWithBuffering, + } + + #[napi] + pub fn send_message_to_user( + steam_id64: BigInt, + send_type: SendType, + data: Buffer, + channel: Option + ) -> Result<(), Error> { + let client = crate::client::get_client(); + let steam_id = SteamId::from_raw(steam_id64.get_u64().1); + let identity = NetworkingIdentity::new_steam_id(steam_id); + + client.networking_messages().send_message_to_user( + identity, + match send_type { + SendType::Unreliable => SendFlags::UNRELIABLE, + SendType::UnreliableNoDelay => SendFlags::UNRELIABLE_NO_DELAY, + SendType::ReliableWithBuffering => SendFlags::RELIABLE, // nagle is the new default + SendType::Reliable => SendFlags::RELIABLE_NO_NAGLE, + } & SendFlags::AUTO_RESTART_BROKEN_SESSION, + &data, + channel.unwrap_or(0) + ).or_else(err) + } + + #[napi(object)] + pub struct Message { + pub data: Buffer, + pub steam_id: Option, + } + + #[napi] + pub fn receive_messages_on_channel( + channel: u32, + batch_size: Option + ) -> Vec { + let client = crate::client::get_client(); + + client + .networking_messages() + .receive_messages_on_channel(channel, batch_size.unwrap_or(10).try_into().unwrap()) + .iter().map(|m:&NetworkingMessage| { + let steam_id = m.identity_peer().steam_id(); + + return Message { + data: m.data().into(), + steam_id: steam_id.map(|id| PlayerSteamId::from_steamid(id)) + } + }).collect() + } +} \ No newline at end of file diff --git a/src/api/networking_sockets.rs b/src/api/networking_sockets.rs deleted file mode 100644 index 75c4d54..0000000 --- a/src/api/networking_sockets.rs +++ /dev/null @@ -1,37 +0,0 @@ -use napi_derive::napi; - -#[napi] -pub mod networking_sockets { - use napi::{ - bindgen_prelude::{Array, BigInt, Buffer, Object}, - Error, JsObject, - }; - - use std::net::SocketAddr; - use std::iter; - - use steamworks::{ - networking_sockets::{ - ListenSocket - }, - networking_types::{ - NetworkingConfigEntry - } - }; - - #[napi] - pub fn create_listen_socket_ip( - local_address: u16 - ) -> Result - { - let local_address = SocketAddr::from(([0, 0, 0, 0], local_address)); - - let client = crate::client::get_client(); - let handle = client.networking_sockets().create_listen_socket_ip( - local_address, - iter::empty::() - ); - - Ok(handle.is_ok()) - } -} \ No newline at end of file From b2eba69e0203e159e02a6f2a48a09ce8bb3bca87 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 08:34:42 -0500 Subject: [PATCH 03/13] created a function that allows the user to toggle approving or denying networking requests from node --- client.d.ts | 1 + src/api/networking_messages.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/client.d.ts b/client.d.ts index e86ad6d..2afa823 100644 --- a/client.d.ts +++ b/client.d.ts @@ -220,6 +220,7 @@ export declare namespace networking_messages { steamId?: PlayerSteamId } export function receiveMessagesOnChannel(channel: number, batchSize?: number | undefined | null): Array + export function allowJoinRequest(state: boolean): void } export declare namespace overlay { export const enum Dialog { diff --git a/src/api/networking_messages.rs b/src/api/networking_messages.rs index f9837c0..3e27280 100644 --- a/src/api/networking_messages.rs +++ b/src/api/networking_messages.rs @@ -95,4 +95,18 @@ pub mod networking_messages { } }).collect() } + + #[napi] + pub fn allow_join_request(state: bool) { + let client = crate::client::get_client(); + let msgs = client.networking_messages(); + // from steamworks-rs -- /// Calling this function more than once will replace the previous callback. + msgs.session_request_callback(move |req| { + if state { + req.accept(); + } else { + req.reject(); + } + }); + } } \ No newline at end of file From 6173060beda176c6e38f1b77d5643ab8d217136b Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 08:35:55 -0500 Subject: [PATCH 04/13] added credit --- src/api/networking_messages.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/networking_messages.rs b/src/api/networking_messages.rs index 3e27280..ffd8670 100644 --- a/src/api/networking_messages.rs +++ b/src/api/networking_messages.rs @@ -1,5 +1,7 @@ use napi_derive::napi; +// some credit here goes to https://github.com/apxapob as this was heavily inspired by his work + #[napi] pub mod networking_messages { use napi::{ From 3c10607a9cd56a509fbf0d17764b18526304bb05 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 12:43:46 -0500 Subject: [PATCH 05/13] It's compiling now but still completely failing once initiated in node, it just times out, never to receive the request on the server --- client.d.ts | 3 +-- index.d.ts | 2 +- index.js | 5 +++-- src/api/callback.rs | 15 --------------- src/api/networking_messages.rs | 14 -------------- src/lib.rs | 17 ++++++++++++++++- 6 files changed, 21 insertions(+), 35 deletions(-) diff --git a/client.d.ts b/client.d.ts index 2afa823..2255ad8 100644 --- a/client.d.ts +++ b/client.d.ts @@ -1,4 +1,4 @@ -export declare function init(appId?: number | undefined | null): void +export declare function init(appId?: number | undefined | null, networking?: boolean | undefined | null): void export declare function restartAppIfNecessary(appId: number): boolean export declare function runCallbacks(): void export interface PlayerSteamId { @@ -220,7 +220,6 @@ export declare namespace networking_messages { steamId?: PlayerSteamId } export function receiveMessagesOnChannel(channel: number, batchSize?: number | undefined | null): Array - export function allowJoinRequest(state: boolean): void } export declare namespace overlay { export const enum Dialog { diff --git a/index.d.ts b/index.d.ts index e249a46..3b97ea5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -export function init(appId?: number): Omit; +export function init(appId?: number, networking?: boolean): Omit; export function restartAppIfNecessary(appId: number): boolean; export function electronEnableSteamOverlay(disableEachFrameInvalidation?: boolean): void; export type Client = typeof import("./client.d"); diff --git a/index.js b/index.js index a3f6917..315370e 100644 --- a/index.js +++ b/index.js @@ -23,12 +23,13 @@ let runCallbacksInterval = undefined /** * Initialize the steam client or throw an error if it fails * @param {number} [appId] - App ID of the game to load, if undefined, will search for a steam_appid.txt file + * @param {boolean} [networking] - Enable networking * @returns {Omit} */ -module.exports.init = (appId) => { +module.exports.init = (appId, networking) => { const { init: internalInit, runCallbacks, restartAppIfNecessary, ...api } = nativeBinding - internalInit(appId) + internalInit(appId, networking) clearInterval(runCallbacksInterval) runCallbacksInterval = setInterval(runCallbacks, 1000 / 30) diff --git a/src/api/callback.rs b/src/api/callback.rs index d97c1be..6a8d179 100644 --- a/src/api/callback.rs +++ b/src/api/callback.rs @@ -32,8 +32,6 @@ pub mod callback { LobbyChatUpdate, P2PSessionRequest, P2PSessionConnectFail, - // NetworkingMessagesSessionRequest, - //NetworkingMessagesSessionFailed, GameLobbyJoinRequested, MicroTxnAuthorizationResponse, } @@ -73,19 +71,6 @@ pub mod callback { SteamCallback::P2PSessionConnectFail => { register_callback::(threadsafe_handler) } - // this one fails because the request can't leave the rust context or it auto denies - // not serializable at the crate level - /* - SteamCallback::NetworkingMessagesSessionRequest => { - register_callback::(threadsafe_handler) - } - */ - // This is still a private struct in the steamworks crate - /* - SteamCallback::NetworkingMessagesSessionFailed => { - register_callback::(threadsafe_handler) - } - */ SteamCallback::GameLobbyJoinRequested => { register_callback::(threadsafe_handler) } diff --git a/src/api/networking_messages.rs b/src/api/networking_messages.rs index ffd8670..957138c 100644 --- a/src/api/networking_messages.rs +++ b/src/api/networking_messages.rs @@ -97,18 +97,4 @@ pub mod networking_messages { } }).collect() } - - #[napi] - pub fn allow_join_request(state: bool) { - let client = crate::client::get_client(); - let msgs = client.networking_messages(); - // from steamworks-rs -- /// Calling this function more than once will replace the previous callback. - msgs.session_request_callback(move |req| { - if state { - req.accept(); - } else { - req.reject(); - } - }); - } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f637028..e8ee293 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ pub mod client; extern crate lazy_static; #[napi] -pub fn init(app_id: Option) -> Result<(), Error> { +pub fn init(app_id: Option, networking: Option) -> Result<(), Error> { if client::has_client() { client::drop_client(); } @@ -26,6 +26,21 @@ pub fn init(app_id: Option) -> Result<(), Error> { steam_client.user_stats().request_current_stats(); + // https://github.com/Noxime/steamworks-rs/blob/master/examples/networking-messages/src/main.rs + // networking message acceptance is handled differently than regular callbacks and it cannot be serialized + // at most we allow you to control accepting or rejecting a session request at the rust level + + steam_client.networking_messages().session_request_callback(move |req| { + println!("Accepting session request from {:?}", req.remote()); + req.accept(); + // assert!(req.accept()); + // mimicing the assert! causes it to crash + }); + + steam_client.networking_messages().session_failed_callback(|info| { + println!("Session failed: {:?}", info); + }); + client::set_client(steam_client); Ok(()) } From bf93c1371d69294dfb86037998439bc8e5eb63f2 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 14:35:19 -0500 Subject: [PATCH 06/13] Saving and restarting --- client.d.ts | 4 ++++ src/api/mod.rs | 1 + src/api/networking_utils.rs | 22 ++++++++++++++++++++++ src/lib.rs | 3 ++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/api/networking_utils.rs diff --git a/client.d.ts b/client.d.ts index 2255ad8..b36eaa9 100644 --- a/client.d.ts +++ b/client.d.ts @@ -221,6 +221,10 @@ export declare namespace networking_messages { } export function receiveMessagesOnChannel(channel: number, batchSize?: number | undefined | null): Array } +export declare namespace networking_utils { + export function initRelayNetworkAccess(): void + export function detailedRelayNetworkStatus(): string +} export declare namespace overlay { export const enum Dialog { Friends = 0, diff --git a/src/api/mod.rs b/src/api/mod.rs index 1613c9e..0bf040d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -8,6 +8,7 @@ pub mod localplayer; pub mod matchmaking; pub mod networking; pub mod networking_messages; +pub mod networking_utils; pub mod overlay; pub mod stats; pub mod utils; diff --git a/src/api/networking_utils.rs b/src/api/networking_utils.rs new file mode 100644 index 0000000..1b26525 --- /dev/null +++ b/src/api/networking_utils.rs @@ -0,0 +1,22 @@ +use napi_derive::napi; + +#[napi] +pub mod networking_utils { + use napi::{ + bindgen_prelude::{BigInt, Buffer}, + Error + }; + + #[napi] + pub fn init_relay_network_access() { + let client = crate::client::get_client(); + client.networking_utils().init_relay_network_access() + } + + #[napi] + pub fn detailed_relay_network_status() -> Result { + let client = crate::client::get_client(); + let status = client.networking_utils().detailed_relay_network_status(); + Ok(status.debugging_message().to_string()) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e8ee293..261dc18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,14 +32,15 @@ pub fn init(app_id: Option, networking: Option) -> Result<(), Error> steam_client.networking_messages().session_request_callback(move |req| { println!("Accepting session request from {:?}", req.remote()); - req.accept(); // assert!(req.accept()); + req.accept(); // mimicing the assert! causes it to crash }); steam_client.networking_messages().session_failed_callback(|info| { println!("Session failed: {:?}", info); }); + client::set_client(steam_client); Ok(()) From 2d1c8e1d55bfcb3000917da3a1d1742e2616ac2e Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 14:53:55 -0500 Subject: [PATCH 07/13] reproducible failures --- test/networking_messages.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/networking_messages.js diff --git a/test/networking_messages.js b/test/networking_messages.js new file mode 100644 index 0000000..dc7acb7 --- /dev/null +++ b/test/networking_messages.js @@ -0,0 +1,30 @@ +const { init } = require('../index.js') + +const client = init(480) + +client.networking_utils.initRelayNetworkAccess(); + +const mySteamId = client.localplayer.getSteamId().steamId64; + +setInterval(() => { + let messages = [] + try { + do { + messages = client.networking_messages.receiveMessagesOnChannel(0); + + while(messages.length > 0){ + const message = messages.shift(); + console.log("Received message") + console.log(message?.steamId) + console.log(message?.data.toString()); + } + } while(messages.length > 0) + } catch (e) { + if(messages.length > 0){ + console.error("Error receiving messages") + console.error(e) + } + } +}, 1000 / 60) + +client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); \ No newline at end of file From 9ee6df04d6b6471fe5fd920d471c969bf7935d41 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 14:56:16 -0500 Subject: [PATCH 08/13] modified the index.js script to account for testing --- index.js | 38 ++++++++++++++++++++++++++----------- test/networking_messages.js | 2 ++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 315370e..2b93cb6 100644 --- a/index.js +++ b/index.js @@ -3,21 +3,37 @@ const { platform, arch } = process /** @typedef {typeof import('./client.d')} Client */ /** @type {Client} */ let nativeBinding = undefined - -if (platform === 'win32' && arch === 'x64') { - nativeBinding = require('steamworks.js/dist/win64/steamworksjs.win32-x64-msvc.node') -} else if (platform === 'linux' && arch === 'x64') { - nativeBinding = require('steamworks.js/dist/linux64/steamworksjs.linux-x64-gnu.node') -} else if (platform === 'darwin') { - if (arch === 'x64') { - nativeBinding = require('steamworks.js/dist/osx/steamworksjs.darwin-x64.node') - } else if (arch === 'arm64') { - nativeBinding = require('steamworks.js/dist/osx/steamworksjs.darwin-arm64.node') +if(process.env.IS_TESTING){ + if (platform === 'win32' && arch === 'x64') { + nativeBinding = require('./dist/win64/steamworksjs.win32-x64-msvc.node') + } else if (platform === 'linux' && arch === 'x64') { + nativeBinding = require('./dist/linux64/steamworksjs.linux-x64-gnu.node') + } else if (platform === 'darwin') { + if (arch === 'x64') { + nativeBinding = require('./dist/osx/steamworksjs.darwin-x64.node') + } else if (arch === 'arm64') { + nativeBinding = require('./dist/osx/steamworksjs.darwin-arm64.node') + } + } else { + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) } } else { - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + if (platform === 'win32' && arch === 'x64') { + nativeBinding = require('steamworks.js/dist/win64/steamworksjs.win32-x64-msvc.node') + } else if (platform === 'linux' && arch === 'x64') { + nativeBinding = require('steamworks.js/dist/linux64/steamworksjs.linux-x64-gnu.node') + } else if (platform === 'darwin') { + if (arch === 'x64') { + nativeBinding = require('steamworks.js/dist/osx/steamworksjs.darwin-x64.node') + } else if (arch === 'arm64') { + nativeBinding = require('steamworks.js/dist/osx/steamworksjs.darwin-arm64.node') + } + } else { + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + } } + let runCallbacksInterval = undefined /** diff --git a/test/networking_messages.js b/test/networking_messages.js index dc7acb7..3e42705 100644 --- a/test/networking_messages.js +++ b/test/networking_messages.js @@ -1,3 +1,5 @@ +process.env.IS_TESTING = true + const { init } = require('../index.js') const client = init(480) From ef6ecc16eb338ce02b7cf8a21e00160aa6357a17 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 15:44:10 -0500 Subject: [PATCH 09/13] reproducible --- index.js | 2 ++ src/client.rs | 2 +- src/lib.rs | 34 ++++++++++++++++++---------------- test/networking_messages.js | 32 +++++++++++++++++--------------- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/index.js b/index.js index 2b93cb6..5a3df7f 100644 --- a/index.js +++ b/index.js @@ -47,8 +47,10 @@ module.exports.init = (appId, networking) => { internalInit(appId, networking) + /* clearInterval(runCallbacksInterval) runCallbacksInterval = setInterval(runCallbacks, 1000 / 30) + */ return api } diff --git a/src/client.rs b/src/client.rs index 78af348..0f952dd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -22,4 +22,4 @@ pub fn set_client(client: Client) { pub fn drop_client() { let mut client_ref = STEAM_CLIENT.lock().unwrap(); *client_ref = None; -} +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 261dc18..7ff2698 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,34 +26,36 @@ pub fn init(app_id: Option, networking: Option) -> Result<(), Error> steam_client.user_stats().request_current_stats(); + client::set_client(steam_client); + + Ok(()) +} + +#[napi] +pub fn restart_app_if_necessary(app_id: u32) -> bool { + steamworks::restart_app_if_necessary(AppId(app_id)) +} + +#[napi] +pub fn run_callbacks() { + let c = client::get_client(); + // https://github.com/Noxime/steamworks-rs/blob/master/examples/networking-messages/src/main.rs // networking message acceptance is handled differently than regular callbacks and it cannot be serialized // at most we allow you to control accepting or rejecting a session request at the rust level - steam_client.networking_messages().session_request_callback(move |req| { + c.networking_messages().session_request_callback(move |req| { println!("Accepting session request from {:?}", req.remote()); // assert!(req.accept()); - req.accept(); + assert!(req.accept()); // mimicing the assert! causes it to crash }); - steam_client.networking_messages().session_failed_callback(|info| { + c.networking_messages().session_failed_callback(|info| { println!("Session failed: {:?}", info); }); - - - client::set_client(steam_client); - Ok(()) -} - -#[napi] -pub fn restart_app_if_necessary(app_id: u32) -> bool { - steamworks::restart_app_if_necessary(AppId(app_id)) -} -#[napi] -pub fn run_callbacks() { - client::get_client().run_callbacks(); + c.run_callbacks(); } pub mod api; diff --git a/test/networking_messages.js b/test/networking_messages.js index 3e42705..a1e614b 100644 --- a/test/networking_messages.js +++ b/test/networking_messages.js @@ -11,22 +11,24 @@ const mySteamId = client.localplayer.getSteamId().steamId64; setInterval(() => { let messages = [] try { - do { - messages = client.networking_messages.receiveMessagesOnChannel(0); - - while(messages.length > 0){ - const message = messages.shift(); - console.log("Received message") - console.log(message?.steamId) - console.log(message?.data.toString()); - } - } while(messages.length > 0) - } catch (e) { - if(messages.length > 0){ - console.error("Error receiving messages") - console.error(e) + messages = client.networking_messages.receiveMessagesOnChannel(0); + + while(messages.length > 0){ + const message = messages.shift(); + console.log("Received message") + console.log(message?.steamId) + console.log(message?.data.toString()); + + client.networking_messages.sendMessageToUser(message.steamId.steamId64, 1, Buffer.from("Hello, world!"), 0); + console.log("Sent message") } + + } catch (e) { + console.error(e) } }, 1000 / 60) -client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); \ No newline at end of file +//client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); +client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 0); +// client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, world!"), 0); +//client.networking_messages.sendMessageToUser(mySteamId, 3, Buffer.from("Hello, world!"), 0); \ No newline at end of file From 48d9283f4374aff4e7b12d7f2fe285138bc9b4b6 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 16:47:46 -0500 Subject: [PATCH 10/13] maybe it is a networking issue? --- index.js | 2 -- src/api/networking_messages.rs | 6 +++- src/lib.rs | 32 +++++++++++++------ ...ages.js => networking_messages_receive.js} | 8 +++-- test/networking_messages_send.js | 25 +++++++++++++++ 5 files changed, 58 insertions(+), 15 deletions(-) rename test/{networking_messages.js => networking_messages_receive.js} (72%) create mode 100644 test/networking_messages_send.js diff --git a/index.js b/index.js index 5a3df7f..2b93cb6 100644 --- a/index.js +++ b/index.js @@ -47,10 +47,8 @@ module.exports.init = (appId, networking) => { internalInit(appId, networking) - /* clearInterval(runCallbacksInterval) runCallbacksInterval = setInterval(runCallbacks, 1000 / 30) - */ return api } diff --git a/src/api/networking_messages.rs b/src/api/networking_messages.rs index 957138c..6ad5fc8 100644 --- a/src/api/networking_messages.rs +++ b/src/api/networking_messages.rs @@ -1,5 +1,5 @@ use napi_derive::napi; - +use std::process; // some credit here goes to https://github.com/apxapob as this was heavily inspired by his work #[napi] @@ -59,6 +59,8 @@ pub mod networking_messages { let steam_id = SteamId::from_raw(steam_id64.get_u64().1); let identity = NetworkingIdentity::new_steam_id(steam_id); + println!("sending message in thread {}", std::process::id()); + client.networking_messages().send_message_to_user( identity, match send_type { @@ -85,6 +87,8 @@ pub mod networking_messages { ) -> Vec { let client = crate::client::get_client(); + println!("receiving messages in thread {}", std::process::id()); + client .networking_messages() .receive_messages_on_channel(channel, batch_size.unwrap_or(10).try_into().unwrap()) diff --git a/src/lib.rs b/src/lib.rs index 7ff2698..e7b7b3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ use napi_derive::napi; use steamworks::AppId; use steamworks::Client; use steamworks::SteamAPIInitError; +use std::process; + pub mod client; @@ -26,8 +28,24 @@ pub fn init(app_id: Option, networking: Option) -> Result<(), Error> steam_client.user_stats().request_current_stats(); - client::set_client(steam_client); + // https://github.com/Noxime/steamworks-rs/blob/master/examples/networking-messages/src/main.rs + // networking message acceptance is handled differently than regular callbacks and it cannot be serialized + // at most we allow you to control accepting or rejecting a session request at the rust level + /* + steam_client.networking_messages().session_request_callback(move |req| { + println!("Accepting session request from {:?}", req.remote()); + // assert!(req.accept()); + req.accept(); + // mimicing the assert! causes it to crash + }); + + steam_client.networking_messages().session_failed_callback(|info| { + println!("Session failed: {:?}", info); + }); + */ + + client::set_client(steam_client); Ok(()) } @@ -39,22 +57,18 @@ pub fn restart_app_if_necessary(app_id: u32) -> bool { #[napi] pub fn run_callbacks() { let c = client::get_client(); - - // https://github.com/Noxime/steamworks-rs/blob/master/examples/networking-messages/src/main.rs - // networking message acceptance is handled differently than regular callbacks and it cannot be serialized - // at most we allow you to control accepting or rejecting a session request at the rust level - + println!("running callbacks in thread {}", std::process::id()); c.networking_messages().session_request_callback(move |req| { - println!("Accepting session request from {:?}", req.remote()); + // assert!(req.accept()); - assert!(req.accept()); + req.accept(); // mimicing the assert! causes it to crash + println!("Accepting session request"); }); c.networking_messages().session_failed_callback(|info| { println!("Session failed: {:?}", info); }); - c.run_callbacks(); } diff --git a/test/networking_messages.js b/test/networking_messages_receive.js similarity index 72% rename from test/networking_messages.js rename to test/networking_messages_receive.js index a1e614b..cef00dd 100644 --- a/test/networking_messages.js +++ b/test/networking_messages_receive.js @@ -6,7 +6,9 @@ const client = init(480) client.networking_utils.initRelayNetworkAccess(); -const mySteamId = client.localplayer.getSteamId().steamId64; +setInterval(() => { + console.log(client.networking_utils.detailedRelayNetworkStatus()) +}, 1000) setInterval(() => { let messages = [] @@ -19,7 +21,7 @@ setInterval(() => { console.log(message?.steamId) console.log(message?.data.toString()); - client.networking_messages.sendMessageToUser(message.steamId.steamId64, 1, Buffer.from("Hello, world!"), 0); + // client.networking_messages.sendMessageToUser(message.steamId.steamId64, 1, Buffer.from("Hello, world!"), 0); console.log("Sent message") } @@ -29,6 +31,6 @@ setInterval(() => { }, 1000 / 60) //client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); -client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 0); +// client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 0); // client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, world!"), 0); //client.networking_messages.sendMessageToUser(mySteamId, 3, Buffer.from("Hello, world!"), 0); \ No newline at end of file diff --git a/test/networking_messages_send.js b/test/networking_messages_send.js new file mode 100644 index 0000000..8b4e937 --- /dev/null +++ b/test/networking_messages_send.js @@ -0,0 +1,25 @@ +process.env.IS_TESTING = true + +const { init } = require('../index.js') + +const client = init(480) + +client.networking_utils.initRelayNetworkAccess(); + +setInterval(() => { + console.log(client.networking_utils.detailedRelayNetworkStatus()) +}, 1000) + + +const mySteamId = client.localplayer.getSteamId().steamId64; +setTimeout(() => { + setInterval(() => { + client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 0); + }, 1000) +}, 5000) + + +//client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); + +// client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, world!"), 0); +//client.networking_messages.sendMessageToUser(mySteamId, 3, Buffer.from("Hello, world!"), 0); \ No newline at end of file From 06faca20263e65c16d41b881d4c31c417dc92b03 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 17:05:14 -0500 Subject: [PATCH 11/13] guh --- src/lib.rs | 4 ++-- test/networking_messages_receive.js | 33 ++++++++++++++++++++++++----- test/networking_messages_send.js | 22 ++++++++++++++++++- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e7b7b3a..d363c5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ pub fn init(app_id: Option, networking: Option) -> Result<(), Error> // networking message acceptance is handled differently than regular callbacks and it cannot be serialized // at most we allow you to control accepting or rejecting a session request at the rust level - /* steam_client.networking_messages().session_request_callback(move |req| { println!("Accepting session request from {:?}", req.remote()); // assert!(req.accept()); @@ -43,7 +42,6 @@ pub fn init(app_id: Option, networking: Option) -> Result<(), Error> steam_client.networking_messages().session_failed_callback(|info| { println!("Session failed: {:?}", info); }); - */ client::set_client(steam_client); Ok(()) @@ -57,6 +55,7 @@ pub fn restart_app_if_necessary(app_id: u32) -> bool { #[napi] pub fn run_callbacks() { let c = client::get_client(); + /* println!("running callbacks in thread {}", std::process::id()); c.networking_messages().session_request_callback(move |req| { @@ -69,6 +68,7 @@ pub fn run_callbacks() { c.networking_messages().session_failed_callback(|info| { println!("Session failed: {:?}", info); }); + */ c.run_callbacks(); } diff --git a/test/networking_messages_receive.js b/test/networking_messages_receive.js index cef00dd..5ba1dfc 100644 --- a/test/networking_messages_receive.js +++ b/test/networking_messages_receive.js @@ -10,6 +10,29 @@ setInterval(() => { console.log(client.networking_utils.detailedRelayNetworkStatus()) }, 1000) +const mySteamId = client.localplayer.getSteamId().steamId64; + +setInterval(() => { + let messages = [] + try { + messages = client.networking_messages.receiveMessagesOnChannel(1); + + while(messages.length > 0){ + const message = messages.shift(); + console.log("Received message") + console.log(message?.steamId) + console.log(message?.data.toString()); + + client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello from server!"), 0); + console.log("Sent message") + } + + } catch (e) { + console.error(e) + } +}, 1000 / 60) + + setInterval(() => { let messages = [] try { @@ -21,7 +44,7 @@ setInterval(() => { console.log(message?.steamId) console.log(message?.data.toString()); - // client.networking_messages.sendMessageToUser(message.steamId.steamId64, 1, Buffer.from("Hello, world!"), 0); + client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 1); console.log("Sent message") } @@ -30,7 +53,7 @@ setInterval(() => { } }, 1000 / 60) -//client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); -// client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 0); -// client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, world!"), 0); -//client.networking_messages.sendMessageToUser(mySteamId, 3, Buffer.from("Hello, world!"), 0); \ No newline at end of file + +setTimeout(() => { + client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 1); +}, 5000) diff --git a/test/networking_messages_send.js b/test/networking_messages_send.js index 8b4e937..042db69 100644 --- a/test/networking_messages_send.js +++ b/test/networking_messages_send.js @@ -14,10 +14,30 @@ setInterval(() => { const mySteamId = client.localplayer.getSteamId().steamId64; setTimeout(() => { setInterval(() => { - client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 0); + client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 1); }, 1000) }, 5000) +setInterval(() => { + let messages = [] + try { + messages = client.networking_messages.receiveMessagesOnChannel(0); + + while(messages.length > 0){ + const message = messages.shift(); + console.log("Received message") + console.log(message?.steamId) + console.log(message?.data.toString()); + + client.networking_messages.sendMessageToUser(message.steamId.steamId64, 1, Buffer.from("Hello, world!"), 1); + console.log("Sent message") + } + + } catch (e) { + console.error(e) + } +}, 1000 / 60) + //client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); From d1f093c40a047ae93e632054008183fd1779816e Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 19:16:14 -0500 Subject: [PATCH 12/13] ok, it is not possible to send p2p connections to yourself. I will have to implement this. --- client.d.ts | 37 ++++++ src/api/mod.rs | 1 + src/api/networking_messages.rs | 5 - src/api/networking_sockets.rs | 174 ++++++++++++++++++++++++++++ test/networking_messages_receive.js | 32 +---- test/networking_messages_send.js | 5 +- test/networking_sockets_receive.js | 26 +++++ test/networking_sockets_send.js | 27 +++++ 8 files changed, 272 insertions(+), 35 deletions(-) create mode 100644 src/api/networking_sockets.rs create mode 100644 test/networking_sockets_receive.js create mode 100644 test/networking_sockets_send.js diff --git a/client.d.ts b/client.d.ts index b36eaa9..3b1ee5e 100644 --- a/client.d.ts +++ b/client.d.ts @@ -221,6 +221,43 @@ export declare namespace networking_messages { } export function receiveMessagesOnChannel(channel: number, batchSize?: number | undefined | null): Array } +export declare namespace networking_sockets { + export function createListenSocketP2P(localVirtualPort?: number | undefined | null): boolean + export function setAcceptNewP2PRequests(accept: boolean): void + export function connectP2P(steamId64: bigint, remoteVirtualPort: number): boolean + export function processListenP2PEvents(): void + export interface P2PPacket { + data: Buffer + steamId: bigint + } + export function receiveP2PMessages(batchSize?: number | undefined | null): Array + /** The method used to send a packet */ + export const enum SendType { + /** + * Send the packet directly over udp. + * + * Can't be larger than 1200 bytes + */ + Unreliable = 0, + /** + * Like `Unreliable` but doesn't buffer packets + * sent before the connection has started. + */ + UnreliableNoDelay = 1, + /** + * Reliable packet sending. + * + * Can't be larger than 1 megabyte. + */ + Reliable = 2, + /** + * Like `Reliable` but applies the nagle + * algorithm to packets being sent + */ + ReliableWithBuffering = 3 + } + export function sendP2PMessage(steamId64: bigint, data: Buffer, sendType: SendType): boolean +} export declare namespace networking_utils { export function initRelayNetworkAccess(): void export function detailedRelayNetworkStatus(): string diff --git a/src/api/mod.rs b/src/api/mod.rs index 0bf040d..28c5f1d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -8,6 +8,7 @@ pub mod localplayer; pub mod matchmaking; pub mod networking; pub mod networking_messages; +pub mod networking_sockets; pub mod networking_utils; pub mod overlay; pub mod stats; diff --git a/src/api/networking_messages.rs b/src/api/networking_messages.rs index 6ad5fc8..9f94489 100644 --- a/src/api/networking_messages.rs +++ b/src/api/networking_messages.rs @@ -1,5 +1,4 @@ use napi_derive::napi; -use std::process; // some credit here goes to https://github.com/apxapob as this was heavily inspired by his work #[napi] @@ -59,8 +58,6 @@ pub mod networking_messages { let steam_id = SteamId::from_raw(steam_id64.get_u64().1); let identity = NetworkingIdentity::new_steam_id(steam_id); - println!("sending message in thread {}", std::process::id()); - client.networking_messages().send_message_to_user( identity, match send_type { @@ -87,8 +84,6 @@ pub mod networking_messages { ) -> Vec { let client = crate::client::get_client(); - println!("receiving messages in thread {}", std::process::id()); - client .networking_messages() .receive_messages_on_channel(channel, batch_size.unwrap_or(10).try_into().unwrap()) diff --git a/src/api/networking_sockets.rs b/src/api/networking_sockets.rs new file mode 100644 index 0000000..b69ccf4 --- /dev/null +++ b/src/api/networking_sockets.rs @@ -0,0 +1,174 @@ +use napi_derive::napi; + +#[napi] +pub mod networking_sockets { + use napi::{bindgen_prelude::{BigInt, Buffer}, Error}; + use std::collections::HashMap; + use std::sync::Mutex; + use steamworks::{ + ClientManager as Manager, + SteamId, + networking_types::{ + SendFlags, + NetworkingIdentity, + ListenSocketEvent + }, + networking_sockets::{ + ListenSocket, NetConnection, + }, + }; + + lazy_static! { + static ref LISTEN_P2P: Mutex>> = Mutex::new(None); + static ref CONNECTIONS: Mutex>> = Mutex::new(HashMap::new()); + static ref ACCEPT_NEW_REQUESTS: Mutex = Mutex::new(true); + } + + // used to wait for new connections + #[napi] + pub fn create_listen_socket_p2p(local_virtual_port: Option) -> Result { + let client = crate::client::get_client(); + let port = local_virtual_port.unwrap_or(0); + let handle = client.networking_sockets().create_listen_socket_p2p(port, None); + + match handle { + Ok(socket) => { + let mut listen_p2p = LISTEN_P2P.lock().unwrap(); + *listen_p2p = Some(socket); + Ok(true) + } + Err(_) => Err(Error::from_reason("Failed to create listen socket")), + } + } + + // used to allow or reject new connections + #[napi] + pub fn set_accept_new_p2p_requests(accept: bool) { + *ACCEPT_NEW_REQUESTS.lock().unwrap() = accept; + } + + // used to initiate connection + #[napi] + pub fn connect_p2p(steam_id64: BigInt, remote_virtual_port: i32) -> Result { + let client = crate::client::get_client(); + let steam_id = SteamId::from_raw(steam_id64.get_u64().1); + let identity = NetworkingIdentity::new_steam_id(steam_id); + let handle = client.networking_sockets().connect_p2p(identity, remote_virtual_port, None); + match handle { + Ok(connection) => { + CONNECTIONS.lock().unwrap().insert(steam_id, connection); + Ok(true) + } + Err(e) => { + eprintln!("Failed to connect P2P: {:?}", e); + Err(Error::from_reason("Failed to connect P2P")) + } + } + } + + // used to accept incoming connections + #[napi] + pub fn process_listen_p2p_events() { + // Get the socket if it exists + let guard = LISTEN_P2P.lock().unwrap(); + let socket = if let Some(socket) = guard.as_ref() { + socket + } else { + return; + }; + + // Process all available events for this socket + while let Some(event) = socket.try_receive_event() { + match event { + ListenSocketEvent::Connecting(request) => { + // Check if we should accept the connection request + if *ACCEPT_NEW_REQUESTS.lock().unwrap() { + // Attempt to accept the connection request + if let Err(e) = request.accept() { + eprintln!("Failed to accept connection: {:?}", e); + } + } + } + ListenSocketEvent::Connected(connected) => { + // Grab the steam id of the connected user + let steam_id = connected.remote().steam_id().unwrap(); + // Insert the connection into the CONNECTIONS map + CONNECTIONS.lock().unwrap().insert(steam_id, connected.take_connection()); + } + _ => { + // Ignore other event types for now + } + } + } + } + + // now we need a way to receive all mesages + #[napi(object)] + pub struct P2PPacket { + pub data: Buffer, + pub steam_id: BigInt, + } + + #[napi] + pub fn receive_p2p_messages( + batch_size: Option + ) -> Vec { + let mut messages = Vec::new(); + let mut connections = CONNECTIONS.lock().unwrap(); + + for (steam_id, connection) in connections.iter_mut() { + if let Ok(received_messages) = connection.receive_messages(batch_size.unwrap_or(10) as usize) { + for message in received_messages { + messages.push(P2PPacket { + steam_id: BigInt::from(steam_id.raw()), + data: Buffer::from(message.data()), + }); + } + } + } + messages + } + + #[napi] + /// The method used to send a packet + pub enum SendType { + /// Send the packet directly over udp. + /// + /// Can't be larger than 1200 bytes + Unreliable, + /// Like `Unreliable` but doesn't buffer packets + /// sent before the connection has started. + UnreliableNoDelay, + /// Reliable packet sending. + /// + /// Can't be larger than 1 megabyte. + Reliable, + /// Like `Reliable` but applies the nagle + /// algorithm to packets being sent + ReliableWithBuffering, + } + + // and a way to send messages + #[napi] + pub fn send_p2p_message( + steam_id64: BigInt, + data: Buffer, + send_type: SendType + ) -> Result { + let steam_id = SteamId::from_raw(steam_id64.get_u64().1); + let mut connections = CONNECTIONS.lock().unwrap(); + if let Some(connection) = connections.get_mut(&steam_id) { + let result = connection.send_message( + &data, + match send_type { + SendType::Unreliable => SendFlags::UNRELIABLE, + SendType::UnreliableNoDelay => SendFlags::UNRELIABLE_NO_DELAY, + SendType::ReliableWithBuffering => SendFlags::RELIABLE, // nagle is the new default + SendType::Reliable => SendFlags::RELIABLE_NO_NAGLE, + } & SendFlags::AUTO_RESTART_BROKEN_SESSION, + ); + return Ok(result.is_ok()); + } + Err(Error::from_reason("Failed to send message")) + } +} \ No newline at end of file diff --git a/test/networking_messages_receive.js b/test/networking_messages_receive.js index 5ba1dfc..108f0ec 100644 --- a/test/networking_messages_receive.js +++ b/test/networking_messages_receive.js @@ -2,7 +2,7 @@ process.env.IS_TESTING = true const { init } = require('../index.js') -const client = init(480) +const client = init(480, true) client.networking_utils.initRelayNetworkAccess(); @@ -23,37 +23,11 @@ setInterval(() => { console.log(message?.steamId) console.log(message?.data.toString()); - client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello from server!"), 0); - console.log("Sent message") + // client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello from server!"), 0); + // console.log("Sent message") } } catch (e) { console.error(e) } }, 1000 / 60) - - -setInterval(() => { - let messages = [] - try { - messages = client.networking_messages.receiveMessagesOnChannel(0); - - while(messages.length > 0){ - const message = messages.shift(); - console.log("Received message") - console.log(message?.steamId) - console.log(message?.data.toString()); - - client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 1); - console.log("Sent message") - } - - } catch (e) { - console.error(e) - } -}, 1000 / 60) - - -setTimeout(() => { - client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 1); -}, 5000) diff --git a/test/networking_messages_send.js b/test/networking_messages_send.js index 042db69..c1db5ff 100644 --- a/test/networking_messages_send.js +++ b/test/networking_messages_send.js @@ -2,7 +2,7 @@ process.env.IS_TESTING = true const { init } = require('../index.js') -const client = init(480) +const client = init(480, false) client.networking_utils.initRelayNetworkAccess(); @@ -12,12 +12,14 @@ setInterval(() => { const mySteamId = client.localplayer.getSteamId().steamId64; +console.log(mySteamId) setTimeout(() => { setInterval(() => { client.networking_messages.sendMessageToUser(mySteamId, 1, Buffer.from("Hello, from client!"), 1); }, 1000) }, 5000) +/* setInterval(() => { let messages = [] try { @@ -37,6 +39,7 @@ setInterval(() => { console.error(e) } }, 1000 / 60) +*/ //client.networking_messages.sendMessageToUser(mySteamId, 0, Buffer.from("Hello, world!"), 0); diff --git a/test/networking_sockets_receive.js b/test/networking_sockets_receive.js new file mode 100644 index 0000000..c81f06b --- /dev/null +++ b/test/networking_sockets_receive.js @@ -0,0 +1,26 @@ +process.env.IS_TESTING = true + +const { init } = require('../index.js') + +const client = init(480, true) + +client.networking_utils.initRelayNetworkAccess(); + +const socket = 0; + +client.networking_sockets.createListenSocketP2P(socket); + +// enable p2p connections +client.networking_sockets.setAcceptNewP2PRequests(true); +// set up one listener just to process the listen p2p events +setInterval(() => { + console.log("Processing listen p2p events"); + client.networking_sockets.processListenP2PEvents(); +}, 1000 / 60) + + +// now actually listen for new messages +setInterval(() => { + let messages = client.networking_sockets.receiveP2PMessages(10); // 10 _from each_ connection + messages.forEach(message => console.log(message)); +}, 1000 / 60); \ No newline at end of file diff --git a/test/networking_sockets_send.js b/test/networking_sockets_send.js new file mode 100644 index 0000000..4bbc22c --- /dev/null +++ b/test/networking_sockets_send.js @@ -0,0 +1,27 @@ +process.env.IS_TESTING = true + +const { init } = require('../index.js') + +const client = init(480, true) + +client.networking_utils.initRelayNetworkAccess(); + +const socket = 96; + +// we shouldn't need to listen for p2p requests since we're sending it + +const mySteamId = client.localplayer.getSteamId().steamId64; +console.log(mySteamId); + +// now actually listen for new messages +/* +setInterval(() => { + let messages = client.networking_sockets.receiveP2PMessages(10); // 10 _from each_ connection + messages.forEach(message => console.log(message)); +}, 1000 / 60); +*/ + +// now let's send a connection request to the server +client.networking_sockets.connectP2P(mySteamId, 0); + +client.networking_sockets.sendP2PMessage(mySteamId, Buffer.from("Hello, from client!"), 1); \ No newline at end of file From ede25cc2151ac6482639ea5365359f4a9d56fad6 Mon Sep 17 00:00:00 2001 From: raymondtri Date: Tue, 25 Feb 2025 20:03:53 -0500 Subject: [PATCH 13/13] will she build --- client.d.ts | 3 + src/api/networking_sockets.rs | 96 ++++++++++++++++++++++++++++++ test/networking_sockets_receive.js | 12 +++- test/networking_sockets_send.js | 2 - 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/client.d.ts b/client.d.ts index 3b1ee5e..d7b98e4 100644 --- a/client.d.ts +++ b/client.d.ts @@ -223,9 +223,12 @@ export declare namespace networking_messages { } export declare namespace networking_sockets { export function createListenSocketP2P(localVirtualPort?: number | undefined | null): boolean + export function createListenSocketIp(localVirtualPort?: number | undefined | null): boolean + export function setAmIServer(isServer: boolean): void export function setAcceptNewP2PRequests(accept: boolean): void export function connectP2P(steamId64: bigint, remoteVirtualPort: number): boolean export function processListenP2PEvents(): void + export function processListenIpEvents(): void export interface P2PPacket { data: Buffer steamId: bigint diff --git a/src/api/networking_sockets.rs b/src/api/networking_sockets.rs index b69ccf4..3932b64 100644 --- a/src/api/networking_sockets.rs +++ b/src/api/networking_sockets.rs @@ -5,6 +5,9 @@ pub mod networking_sockets { use napi::{bindgen_prelude::{BigInt, Buffer}, Error}; use std::collections::HashMap; use std::sync::Mutex; + use std::net::{ + Ipv4Addr, SocketAddr + }; use steamworks::{ ClientManager as Manager, SteamId, @@ -17,11 +20,14 @@ pub mod networking_sockets { ListenSocket, NetConnection, }, }; + use crate::api::localplayer::PlayerSteamId; lazy_static! { static ref LISTEN_P2P: Mutex>> = Mutex::new(None); + static ref LISTEN_IP: Mutex>> = Mutex::new(None); static ref CONNECTIONS: Mutex>> = Mutex::new(HashMap::new()); static ref ACCEPT_NEW_REQUESTS: Mutex = Mutex::new(true); + static ref AM_I_SERVER: Mutex = Mutex::new(false); } // used to wait for new connections @@ -29,6 +35,7 @@ pub mod networking_sockets { pub fn create_listen_socket_p2p(local_virtual_port: Option) -> Result { let client = crate::client::get_client(); let port = local_virtual_port.unwrap_or(0); + let handle = client.networking_sockets().create_listen_socket_p2p(port, None); match handle { @@ -41,6 +48,33 @@ pub mod networking_sockets { } } + // allow for ip as well -- we need this for the local client loopback + #[napi] + pub fn create_listen_socket_ip(local_virtual_port: Option) -> Result { + let client = crate::client::get_client(); + let port = local_virtual_port.unwrap_or(0); + + let handle = client.networking_sockets().create_listen_socket_ip( + SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), port.try_into().unwrap()), + vec![], + ); + + match handle { + Ok(socket) => { + let mut listen_ip = LISTEN_IP.lock().unwrap(); + *listen_ip = Some(socket); + Ok(true) + } + Err(_) => Err(Error::from_reason("Failed to create listen socket")), + } + } + + // used to toggle whether locally it is server functionality + #[napi] + pub fn set_am_i_server(is_server: bool) { + *AM_I_SERVER.lock().unwrap() = is_server; + } + // used to allow or reject new connections #[napi] pub fn set_accept_new_p2p_requests(accept: bool) { @@ -50,8 +84,32 @@ pub mod networking_sockets { // used to initiate connection #[napi] pub fn connect_p2p(steam_id64: BigInt, remote_virtual_port: i32) -> Result { + // first check if I am server and does the steam_id64 belong to me + // if it does, we can just skip this step and return true let client = crate::client::get_client(); let steam_id = SteamId::from_raw(steam_id64.get_u64().1); + + let local_steam_id = PlayerSteamId::from_steamid(client.user().steam_id()); + if local_steam_id.steam_id64.get_u64().1 == steam_id64.get_u64().1 { + // then we need to actually hijack and hit the local server via ip + + let handle = client.networking_sockets().connect_by_ip_address( + SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), remote_virtual_port.try_into().unwrap()), + None + ); + + match handle { + Ok(connection) => { + CONNECTIONS.lock().unwrap().insert(steam_id, connection); + return Ok(true) + } + Err(e) => { + eprintln!("Failed to connect P2P(by IP): {:?}", e); + return Err(Error::from_reason("Failed to connect P2P(by IP)")) + } + } + } + let identity = NetworkingIdentity::new_steam_id(steam_id); let handle = client.networking_sockets().connect_p2p(identity, remote_virtual_port, None); match handle { @@ -102,6 +160,42 @@ pub mod networking_sockets { } } + // have to accept the connection from own ip, gross but whatever + #[napi] + pub fn process_listen_ip_events() { + // Get the socket if it exists + let guard = LISTEN_IP.lock().unwrap(); + let socket = if let Some(socket) = guard.as_ref() { + socket + } else { + return; + }; + + // Process all available events for this socket + while let Some(event) = socket.try_receive_event() { + match event { + ListenSocketEvent::Connecting(request) => { + // Check if we should accept the connection request + if *ACCEPT_NEW_REQUESTS.lock().unwrap() { + // Attempt to accept the connection request + if let Err(e) = request.accept() { + eprintln!("Failed to accept connection: {:?}", e); + } + } + } + ListenSocketEvent::Connected(connected) => { + // Grab the steam id of the connected user + let steam_id = connected.remote().steam_id().unwrap(); + // Insert the connection into the CONNECTIONS map + CONNECTIONS.lock().unwrap().insert(steam_id, connected.take_connection()); + } + _ => { + // Ignore other event types for now + } + } + } + } + // now we need a way to receive all mesages #[napi(object)] pub struct P2PPacket { @@ -126,6 +220,7 @@ pub mod networking_sockets { } } } + messages } @@ -156,6 +251,7 @@ pub mod networking_sockets { send_type: SendType ) -> Result { let steam_id = SteamId::from_raw(steam_id64.get_u64().1); + let mut connections = CONNECTIONS.lock().unwrap(); if let Some(connection) = connections.get_mut(&steam_id) { let result = connection.send_message( diff --git a/test/networking_sockets_receive.js b/test/networking_sockets_receive.js index c81f06b..453ec0b 100644 --- a/test/networking_sockets_receive.js +++ b/test/networking_sockets_receive.js @@ -7,20 +7,30 @@ const client = init(480, true) client.networking_utils.initRelayNetworkAccess(); const socket = 0; +const localSocket = 6969; client.networking_sockets.createListenSocketP2P(socket); +client.networking_sockets.createListenSocketIP(localSocket); // enable p2p connections -client.networking_sockets.setAcceptNewP2PRequests(true); +client.networking_sockets.setAmIServer(true); + // set up one listener just to process the listen p2p events setInterval(() => { console.log("Processing listen p2p events"); client.networking_sockets.processListenP2PEvents(); }, 1000 / 60) +// and the local ip guh +setInterval(() => { + console.log("Processing listen ip events"); + client.networking_sockets.processListenIPEvents(); +}, 1000 / 60) + // now actually listen for new messages setInterval(() => { + console.log("processing p2p messages"); let messages = client.networking_sockets.receiveP2PMessages(10); // 10 _from each_ connection messages.forEach(message => console.log(message)); }, 1000 / 60); \ No newline at end of file diff --git a/test/networking_sockets_send.js b/test/networking_sockets_send.js index 4bbc22c..2e5a2a3 100644 --- a/test/networking_sockets_send.js +++ b/test/networking_sockets_send.js @@ -6,8 +6,6 @@ const client = init(480, true) client.networking_utils.initRelayNetworkAccess(); -const socket = 96; - // we shouldn't need to listen for p2p requests since we're sending it const mySteamId = client.localplayer.getSteamId().steamId64;