diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSockets.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSockets.java new file mode 100644 index 0000000..dc83c1b --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSockets.java @@ -0,0 +1,436 @@ +package com.codedisaster.steamworks; + +import java.nio.ByteBuffer; + +public class SteamNetworkingSockets extends SteamInterface { + + public static final class Connection { + private final int handle; + + public Connection(int handle) { + this.handle = handle; + } + + public boolean isValid() { + return handle != 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Connection)) return false; + + Connection that = (Connection) o; + return handle == that.handle; + } + + @Override + public int hashCode() { + return handle; + } + + @Override + public String toString() { + return Integer.toHexString(handle); + } + } + + public static final class Socket { + private final int handle; + + public Socket(int handle) { + this.handle = handle; + } + + public boolean isValid() { + return handle != 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Connection)) return false; + + Connection that = (Connection) o; + return handle == that.handle; + } + + @Override + public int hashCode() { + return handle; + } + + @Override + public String toString() { + return Integer.toHexString(handle); + } + } + + public enum ConnectionState { + /** + * Dummy value used to indicate an error condition in the API. + * Specified connection doesn't exist or has already been closed. + */ + None(0), + + /** + * We are trying to establish whether peers can talk to each other, + * whether they WANT to talk to each other, perform basic auth, + * and exchange crypt keys. + * + * + * + * In either case, any unreliable packets sent now are almost certain + * to be dropped. Attempts to receive packets are guaranteed to fail. + * You may send messages if the send mode allows for them to be queued. + * but if you close the connection before the connection is actually + * established, any queued messages will be discarded immediately. + * (We will not attempt to flush the queue and confirm delivery to the + * remote host, which ordinarily happens when a connection is closed.) + */ + Connecting(1), + + /** + * Some connection types use a back channel or trusted 3rd party + * for earliest communication. If the server accepts the connection, + * then these connections switch into the rendezvous state. During this + * state, we still have not yet established an end-to-end route (through + * the relay network), and so if you send any messages unreliable, they + * are going to be discarded. + */ + FindingRoute(2), + + /** + * We've received communications from our peer (and we know + * who they are) and are all good. If you close the connection now, + * we will make our best effort to flush out any reliable sent data that + * has not been acknowledged by the peer. (But note that this happens + * from within the application process, so unlike a TCP connection, you are + * not totally handing it off to the operating system to deal with it.) + */ + Connected(3), + + /** + * Connection has been closed by our peer, but not closed locally. + * The connection still exists from an API perspective. You must close the + * handle to free up resources. If there are any messages in the inbound queue, + * you may retrieve them. Otherwise, nothing may be done with the connection + * except to close it. + *

+ * This state is similar to CLOSE_WAIT in the TCP state machine. + */ + ClosedByPeer(4), + + /** + * A disruption in the connection has been detected locally. (E.g. timeout, + * local internet connection disrupted, etc.) + *

+ * The connection still exists from an API perspective. You must close the + * handle to free up resources. + *

+ * Attempts to send further messages will fail. Any remaining received messages + * in the queue are available. + */ + ProblemDetectedLocally(5); + + private final int value; + private static final ConnectionState[] values = values(); + + ConnectionState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ConnectionState byValue(int value) { + for (ConnectionState state : values) { + if (state.value == value) { + return state; + } + } + return None; + } + } + + /** + * Flags used to set options for message sending. + * This is not an enum, because flags are naturally combined with the or operator. + */ + public interface SendFlags { + /** + * Send the message unreliably. Can be lost. Messages *can* be larger than a + * single MTU (UDP packet), but there is no retransmission, so if any piece + * of the message is lost, the entire message will be dropped. + *

+ * The sending API does have some knowledge of the underlying connection, so + * if there is no NAT-traversal accomplished or there is a recognized adjustment + * happening on the connection, the packet will be batched until the connection + * is open again. + *

+ * Migration note: This is not exactly the same as k_EP2PSendUnreliable! You + * probably want k_ESteamNetworkingSendType_UnreliableNoNagle + */ + int Unreliable = 0; + + /** + * Disable Nagle's algorithm. + * By default, Nagle's algorithm is applied to all outbound messages. This means + * that the message will NOT be sent immediately, in case further messages are + * sent soon after you send this, which can be grouped together. Any time there + * is enough buffered data to fill a packet, the packets will be pushed out immediately, + * but partially-full packets not be sent until the Nagle timer expires. See + * ISteamNetworkingSockets::FlushMessagesOnConnection, ISteamNetworkingMessages::FlushMessagesToUser + *

+ * NOTE: Don't just send every message without Nagle because you want packets to get there + * quicker. Make sure you understand the problem that Nagle is solving before disabling it. + * If you are sending small messages, often many at the same time, then it is very likely that + * it will be more efficient to leave Nagle enabled. A typical proper use of this flag is + * when you are sending what you know will be the last message sent for a while (e.g. the last + * in the server simulation tick to a particular client), and you use this flag to flush all + * messages. + */ + int NoNagle = 1; + + /** + * Send a message unreliably, bypassing Nagle's algorithm for this message and any messages + * currently pending on the Nagle timer. This is equivalent to using k_ESteamNetworkingSend_Unreliable + * and then immediately flushing the messages using ISteamNetworkingSockets::FlushMessagesOnConnection + * or ISteamNetworkingMessages::FlushMessagesToUser. (But using this flag is more efficient since you + * only make one API call.) + */ + int UnreliableNoNagle = Unreliable | NoNagle; + + /** + * If the message cannot be sent very soon (because the connection is still doing some initial + * handshaking, route negotiations, etc), then just drop it. This is only applicable for unreliable + * messages. Using this flag on reliable messages is invalid. + */ + int NoDelay = 4; + + /** + * Send an unreliable message, but if it cannot be sent relatively quickly, just drop it instead of queuing it. + * This is useful for messages that are not useful if they are excessively delayed, such as voice data. + * NOTE: The Nagle algorithm is not used, and if the message is not dropped, any messages waiting on the + * Nagle timer are immediately flushed. + *

+ * A message will be dropped under the following circumstances: + * - the connection is not fully connected. (E.g. the "Connecting" or "FindingRoute" states) + * - there is a sufficiently large number of messages queued up already such that the current message + * will not be placed on the wire in the next ~200ms or so. + *

+ * If a message is dropped for these reasons, k_EResultIgnored will be returned. + */ + int UnreliableNoDelay = Unreliable | NoDelay | NoNagle; + + /** + * Reliable message send. Can send up to k_cbMaxSteamNetworkingSocketsMessageSizeSend bytes in a single message. + * Does fragmentation/re-assembly of messages under the hood, as well as a sliding window for + * efficient sends of large chunks of data. + *

+ * The Nagle algorithm is used. See notes on k_ESteamNetworkingSendType_Unreliable for more details. + * See k_ESteamNetworkingSendType_ReliableNoNagle, ISteamNetworkingSockets::FlushMessagesOnConnection, + * ISteamNetworkingMessages::FlushMessagesToUser + *

+ * Migration note: This is NOT the same as k_EP2PSendReliable, it's more like k_EP2PSendReliableWithBuffering + */ + int Reliable = 8; + + /** + * Send a message reliably, but bypass Nagle's algorithm. + *

+ * Migration note: This is equivalent to k_EP2PSendReliable + */ + int ReliableNoNagle = Reliable | NoNagle; + + /** + * By default, message sending is queued, and the work of encryption and talking to + * the operating system sockets, etc is done on a service thread. This is usually a + * a performance win when messages are sent from the "main thread". However, if this + * flag is set, and data is ready to be sent immediately (either from this message + * or earlier queued data), then that work will be done in the current thread, before + * the current call returns. If data is not ready to be sent (due to rate limiting + * or Nagle), then this flag has no effect. + *

+ * This is an advanced flag used to control performance at a very low level. For + * most applications running on modern hardware with more than one CPU core, doing + * the work of sending on a service thread will yield the best performance. Only + * use this flag if you have a really good reason and understand what you are doing. + * Otherwise you will probably just make performance worse. + */ + int UseCurrentThread = 16; + + /** + * When sending a message using ISteamNetworkingMessages, automatically re-establish + * a broken session, without returning k_EResultNoConnection. Without this flag, + * if you attempt to send a message, and the session was proactively closed by the + * peer, or an error occurred that disrupted communications, then you must close the + * session using ISteamNetworkingMessages::CloseSessionWithUser before attempting to + * send another message. (Or you can simply add this flag and retry.) In this way, + * the disruption cannot go unnoticed, and a more clear order of events can be + * ascertained. This is especially important when reliable messages are used, since + * if the connection is disrupted, some of those messages will not have been delivered, + * and it is in general not possible to know which. Although a + * SteamNetworkingMessagesSessionFailed_t callback will be posted when an error occurs + * to notify you that a failure has happened, callbacks are asynchronous, so it is not + * possible to tell exactly when it happened. And because the primary purpose of + * ISteamNetworkingMessages is to be like UDP, there is no notification when a peer closes + * the session. + *

+ * If you are not using any reliable messages (e.g. you are using ISteamNetworkingMessages + * exactly as a transport replacement for UDP-style datagrams only), you may not need to + * know when an underlying connection fails, and so you may not need this notification. + */ + int AutoRestartBrokenSession = 32; + } + + public SteamNetworkingSockets(SteamNetworkingSocketsCallback callback) { + super(SteamNetworkingSocketsNative.createCallback(new SteamNetworkingSocketsCallbackAdapter(callback))); + } + + public Connection connectP2P(SteamID steamID, int virtualPort){ + int result = SteamNetworkingSocketsNative.connectP2P(steamID.handle, virtualPort); + return new Connection(result); + } + + public Socket createListenSocketP2P(int virtualPort){ + int result = SteamNetworkingSocketsNative.createListenSocketP2P(virtualPort); + return new Socket(result); + } + + /** + * Accepts an incoming connection request. + * + * @param connection The handle of the connection to be accepted. + * @return {@link SteamResult} indicating the result of the operation: + *

+ * This method communicates with the native SteamNetworkingSockets API to accept a connection. + */ + public SteamResult acceptConnection(Connection connection) { + int result = SteamNetworkingSocketsNative.acceptConnection(connection.handle); + return SteamResult.byValue(result); + } + + public boolean closeConnection(Connection connection, int reason, boolean linger){ + return SteamNetworkingSocketsNative.closeConnection(connection.handle, reason, linger); + } + + public boolean closeListenSocket(Socket socket){ + return SteamNetworkingSocketsNative.closeListenSocket(socket.handle); + } + + /** + * Sends a message to a specified connection. + * + * @param connection The handle of the connection to which the message is sent. + * @param data The byte buffer containing the message to be sent. + * @param sendFlags Flags controlling how the message is sent, see {@link SendFlags} + * @return {@link SteamResult} indicating the result of the operation. Possible values include: + * + * This method interfaces with the native SteamNetworkingSockets API for message transmission. + */ + public SteamResult sendMessageToConnection(Connection connection, ByteBuffer data, int sendFlags) throws SteamException { + if (!data.isDirect()) { + throw new SteamException("Direct buffer required!"); + } + + int result = SteamNetworkingSocketsNative.sendMessageToConnection(connection.handle, data, data.position(), data.remaining(), sendFlags); + return SteamResult.byValue(result); + } + + + /** + * Flushes messages for a specified connection. + * + * @param connection The handle of the connection to flush messages for. + * @return {@link SteamResult} indicating the result of the operation. Possible values include: + * + * This method communicates with the native SteamNetworkingSockets API to perform the operation. + */ + public SteamResult flushMessages(Connection connection) { + int result = SteamNetworkingSocketsNative.flushMessages(connection.handle); + return SteamResult.byValue(result); + } + + /** + * Attempts to receive a single pending message from a SteamNetworkingSockets connection. + *

+ * This method is non-blocking. If no message is currently available, it returns {@code 0}. + * At most one message is received per invocation. + *

+ * + *

+ * The message payload is copied into the provided {@link ByteBuffer} starting at the buffer’s + * current {@link ByteBuffer#position()}, and up to {@link ByteBuffer#remaining()} bytes. + * The buffer must be a {@linkplain ByteBuffer#isDirect() direct buffer}. + *

+ * + *

+ * If the buffer is too small to hold the incoming message, the message is dropped and a + * {@link SteamException} is thrown. The exception message includes the required buffer size. + *

+ * + *

+ * On success, the buffer position is advanced by the number of bytes written. + *

+ * + * @param connection The connection handle to receive data from. + * @param data A direct {@link ByteBuffer} into which the message payload will be written. + * + * @return The number of bytes received in the buffer, or {@code 0} if no message is currently available. + * + * @throws SteamException + * If {@code data} is not a direct buffer, or if the incoming message exceeds + * the buffer's remaining capacity. + * + * @implNote + * The underlying native message is always released before this method returns. + * To fully drain the receive queue, callers should invoke this method repeatedly + * until it returns {@code 0}, optionally enforcing a per-frame processing limit. + */ + public int receiveMessageOnConnection(Connection connection, ByteBuffer data) throws SteamException { + if (!data.isDirect()) { + throw new SteamException("Direct buffer required!"); + } + + int bytesWritten = SteamNetworkingSocketsNative.receiveMessageOnConnection(connection.handle, data, data.position(), data.remaining()); + if (bytesWritten < 0) { + throw new SteamException("Buffer Overflow, bytes received: " + (-bytesWritten) + " bytes remaining: " + data.remaining()); + } + + return bytesWritten; + } + + /** + * Helper method to globally enable k_ESteamNetworkingConfig_SymmetricConnect + * Useful to avoid split brain scenarios for P2P matchmaking + */ + public void enableSymmetricConnect() { + SteamNetworkingSocketsNative.enableSymmetricConnect(); + } +} diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallback.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallback.java new file mode 100644 index 0000000..33d1c28 --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallback.java @@ -0,0 +1,10 @@ +package com.codedisaster.steamworks; + +import com.codedisaster.steamworks.SteamNetworkingSockets.Connection; +import com.codedisaster.steamworks.SteamNetworkingSockets.ConnectionState; + +public interface SteamNetworkingSocketsCallback { + + void onConnectionStatusChanged(Connection connection, SteamID steamID, ConnectionState state, ConnectionState prevState); + +} diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallbackAdapter.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallbackAdapter.java new file mode 100644 index 0000000..b3723d1 --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallbackAdapter.java @@ -0,0 +1,15 @@ +package com.codedisaster.steamworks; + +import com.codedisaster.steamworks.SteamNetworkingSockets.Connection; +import com.codedisaster.steamworks.SteamNetworkingSockets.ConnectionState; + +class SteamNetworkingSocketsCallbackAdapter extends SteamCallbackAdapter { + + SteamNetworkingSocketsCallbackAdapter(SteamNetworkingSocketsCallback callback) { + super(callback); + } + + void onConnectionStatusChanged(int connectionHandle, long steamID, int state, int prevState) { + callback.onConnectionStatusChanged(new Connection(connectionHandle), new SteamID(steamID), ConnectionState.byValue(state), ConnectionState.byValue(prevState)); + } +} diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsNative.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsNative.java new file mode 100644 index 0000000..25e3571 --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsNative.java @@ -0,0 +1,89 @@ +package com.codedisaster.steamworks; + +import java.nio.ByteBuffer; + +final class SteamNetworkingSocketsNative { + + // @off + + /*JNI + #include + #include "SteamNetworkingSocketsCallback.h" + #include + */ + + static native long createCallback(SteamNetworkingSocketsCallbackAdapter javaCallback); /* + return (intp) new SteamNetworkingSocketsCallback(env, javaCallback); + */ + + public static native int connectP2P(long steamID, int virtualPort);/* + SteamNetworkingIdentity identity; + identity.m_eType = k_ESteamNetworkingIdentityType_SteamID; + identity.SetSteamID64(steamID); + + HSteamNetConnection connection = SteamNetworkingSockets()->ConnectP2P(identity, virtualPort, 0, NULL); + + return connection; + */ + + public static native int createListenSocketP2P(int virtualPort);/* + HSteamListenSocket socket = SteamNetworkingSockets()->CreateListenSocketP2P(virtualPort, 0, NULL); + + return socket; + */ + + public static native int acceptConnection(int netConnectionHandle);/* + return SteamNetworkingSockets()->AcceptConnection(netConnectionHandle); + */ + + public static native boolean closeConnection(int netConnectionHandle, int reason, boolean linger);/* + return SteamNetworkingSockets()->CloseConnection(netConnectionHandle, reason, NULL, linger); + */ + + public static native boolean closeListenSocket(int socketHandle);/* + return SteamNetworkingSockets()->CloseListenSocket(socketHandle); + */ + + public static native int sendMessageToConnection(int netConnectionHandle, ByteBuffer data, int offset, int size, int sendFlags);/* + return SteamNetworkingSockets()->SendMessageToConnection(netConnectionHandle, &data[offset], size, sendFlags, NULL); + */ + + public static native int receiveMessageOnConnection(int netConnectionHandle, ByteBuffer data, int offset, int size);/* + + SteamNetworkingMessage_t* messages[1]; + + int messagesReceived = SteamNetworkingSockets()->ReceiveMessagesOnConnection((HSteamNetConnection)netConnectionHandle, messages, 1); + if (messagesReceived <= 0 || !messages[0]) { + return 0; + } + + SteamNetworkingMessage_t* message = messages[0]; + if (message->m_cbSize > size) { + message->Release(); + return -message->m_cbSize; + } + + memcpy(&data[offset], message->m_pData, message->m_cbSize); + + int bytesWritten = message->m_cbSize; + + message->Release(); + + return bytesWritten; + */ + + public static native int flushMessages(int connectionHandle);/* + return SteamNetworkingSockets()->FlushMessagesOnConnection(connectionHandle); + */ + + public static native void enableSymmetricConnect();/* + int32_t v = 1; + SteamNetworkingUtils()->SetConfigValue( + k_ESteamNetworkingConfig_SymmetricConnect, + k_ESteamNetworkingConfig_Global, + 0, + k_ESteamNetworkingConfig_Int32, + &v + ); + */ +} diff --git a/java-wrapper/src/main/native/SteamNetworkingSocketsCallback.cpp b/java-wrapper/src/main/native/SteamNetworkingSocketsCallback.cpp new file mode 100644 index 0000000..655c8c6 --- /dev/null +++ b/java-wrapper/src/main/native/SteamNetworkingSocketsCallback.cpp @@ -0,0 +1,18 @@ +#include "SteamNetworkingSocketsCallback.h" + +SteamNetworkingSocketsCallback::SteamNetworkingSocketsCallback(JNIEnv* env, jobject callback) : + SteamCallbackAdapter(env, callback) { +} + +SteamNetworkingSocketsCallback::~SteamNetworkingSocketsCallback() { +} + +void SteamNetworkingSocketsCallback::onConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t* callback) { + invokeCallback({ + callVoidMethod(env, "onConnectionStatusChanged", "(IJII)V", + callback->m_hConn, + callback->m_info.m_identityRemote.GetSteamID64(), + callback->m_info.m_eState, + callback->m_eOldState); + }); +} \ No newline at end of file diff --git a/java-wrapper/src/main/native/SteamNetworkingSocketsCallback.h b/java-wrapper/src/main/native/SteamNetworkingSocketsCallback.h new file mode 100644 index 0000000..a70c30d --- /dev/null +++ b/java-wrapper/src/main/native/SteamNetworkingSocketsCallback.h @@ -0,0 +1,14 @@ +#pragma once + +#include "SteamCallbackAdapter.h" +#include + +class SteamNetworkingSocketsCallback : public SteamCallbackAdapter { + +public: + SteamNetworkingSocketsCallback(JNIEnv* env, jobject callback); + ~SteamNetworkingSocketsCallback(); + + STEAM_CALLBACK(SteamNetworkingSocketsCallback, onConnectionStatusChanged, SteamNetConnectionStatusChangedCallback_t); + +}; \ No newline at end of file diff --git a/java-wrapper/src/main/resources/libsteamworks4j.dylib b/java-wrapper/src/main/resources/libsteamworks4j.dylib index 80ade41..30a8aa1 100755 Binary files a/java-wrapper/src/main/resources/libsteamworks4j.dylib and b/java-wrapper/src/main/resources/libsteamworks4j.dylib differ diff --git a/java-wrapper/src/main/resources/libsteamworks4j.so b/java-wrapper/src/main/resources/libsteamworks4j.so index eaa1416..bfac36b 100755 Binary files a/java-wrapper/src/main/resources/libsteamworks4j.so and b/java-wrapper/src/main/resources/libsteamworks4j.so differ diff --git a/java-wrapper/src/main/resources/steamworks4j.dll b/java-wrapper/src/main/resources/steamworks4j.dll index 1201bb4..697e78f 100644 Binary files a/java-wrapper/src/main/resources/steamworks4j.dll and b/java-wrapper/src/main/resources/steamworks4j.dll differ diff --git a/java-wrapper/src/main/resources/steamworks4j64.dll b/java-wrapper/src/main/resources/steamworks4j64.dll index 3813d99..15fe649 100644 Binary files a/java-wrapper/src/main/resources/steamworks4j64.dll and b/java-wrapper/src/main/resources/steamworks4j64.dll differ diff --git a/server/src/main/resources/libsteamworks4j-encryptedappticket.dylib b/server/src/main/resources/libsteamworks4j-encryptedappticket.dylib index c7c0660..0c34820 100755 Binary files a/server/src/main/resources/libsteamworks4j-encryptedappticket.dylib and b/server/src/main/resources/libsteamworks4j-encryptedappticket.dylib differ diff --git a/server/src/main/resources/libsteamworks4j-server.dylib b/server/src/main/resources/libsteamworks4j-server.dylib index 2ec6bcf..6eb7586 100755 Binary files a/server/src/main/resources/libsteamworks4j-server.dylib and b/server/src/main/resources/libsteamworks4j-server.dylib differ diff --git a/server/src/main/resources/libsteamworks4j-server.so b/server/src/main/resources/libsteamworks4j-server.so index 2f6e76d..6104644 100755 Binary files a/server/src/main/resources/libsteamworks4j-server.so and b/server/src/main/resources/libsteamworks4j-server.so differ diff --git a/server/src/main/resources/steamworks4j-encryptedappticket.dll b/server/src/main/resources/steamworks4j-encryptedappticket.dll index 54d425a..84ed023 100644 Binary files a/server/src/main/resources/steamworks4j-encryptedappticket.dll and b/server/src/main/resources/steamworks4j-encryptedappticket.dll differ diff --git a/server/src/main/resources/steamworks4j-encryptedappticket64.dll b/server/src/main/resources/steamworks4j-encryptedappticket64.dll index cca9aff..81ebcfc 100644 Binary files a/server/src/main/resources/steamworks4j-encryptedappticket64.dll and b/server/src/main/resources/steamworks4j-encryptedappticket64.dll differ diff --git a/server/src/main/resources/steamworks4j-server.dll b/server/src/main/resources/steamworks4j-server.dll index b3d18d1..7e814c1 100644 Binary files a/server/src/main/resources/steamworks4j-server.dll and b/server/src/main/resources/steamworks4j-server.dll differ diff --git a/server/src/main/resources/steamworks4j-server64.dll b/server/src/main/resources/steamworks4j-server64.dll index f38ca00..2efd7ff 100644 Binary files a/server/src/main/resources/steamworks4j-server64.dll and b/server/src/main/resources/steamworks4j-server64.dll differ