From 6e830b40698a452cf2e88c612952636802192c4a Mon Sep 17 00:00:00 2001 From: SrdjanV Date: Fri, 9 May 2025 21:51:47 +0200 Subject: [PATCH 1/5] Add new ip methods in CMClient that are npe safe Add additional null checks --- .../dragonbra/javasteam/steam/CMClient.java | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index aa778935..dfb484ab 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -28,6 +28,7 @@ import in.dragonbra.javasteam.util.log.LogManager; import in.dragonbra.javasteam.util.log.Logger; import in.dragonbra.javasteam.util.stream.BinaryReader; +import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -51,10 +52,13 @@ public abstract class CMClient { private long sessionToken; + @Nullable private Integer cellID; + @Nullable private Integer sessionID; + @Nullable private SteamID steamID; private IDebugNetworkListener debugNetworkListener; @@ -64,6 +68,7 @@ public abstract class CMClient { // connection lock around the setup and tear down of the connection task private final Object connectionLock = new Object(); + @Nullable private Connection connection; private final ScheduledFunction heartBeatFunc; @@ -166,6 +171,12 @@ public void connect(ServerRecord cmServer) { cmServer = getServers().getNextServerCandidate(configuration.getProtocolTypes()); } + if (cmServer == null) { + logger.debug("No CM servers available to connect to"); + onClientDisconnected(false); + return; + } + connection = createConnection(cmServer.getProtocolTypes()); connection.getNetMsgReceived().addEventHandler(netMsgReceived); connection.getConnected().addEventHandler(connected); @@ -412,7 +423,10 @@ private void handleLogOnResponse(IPacketMsg packetMsg) { heartBeatFunc.setDelay(logonResp.getBody().getLegacyOutOfGameHeartbeatSeconds() * 1000L); heartBeatFunc.start(); } else if (logonResponse == EResult.TryAnotherCM || logonResponse == EResult.ServiceUnavailable) { - getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); + var connection = this.connection;// probably not needed + if (connection != null) { + getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); + } } } @@ -431,7 +445,10 @@ private void handleLoggedOff(IPacketMsg packetMsg) { logger.debug("handleLoggedOff got " + logoffResult); if (logoffResult == EResult.TryAnotherCM || logoffResult == EResult.ServiceUnavailable) { - getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); + var connection = this.connection;// probably not needed + if (connection != null) { + getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); + } } } else { logger.debug("handleLoggedOff got unexpected response: " + packetMsg.getMsgType()); @@ -468,20 +485,52 @@ public SmartCMServerList getServers() { * Returns the local IP of this client. * * @return The local IP. + * @throws NullPointerException if the client is not connected */ - public InetAddress getLocalIP() { + public InetAddress getLocalIP() throws NullPointerException { return connection.getLocalIP(); } + /** + * Safe version of {@link CMClient#getLocalIP()}. + * + * @return The current endpoint. + */ + //JavaSteam addition + public Optional getLocalIPOptional() { + var connection = this.connection; + if (connection == null) { + return Optional.empty(); + } else { + return Optional.ofNullable(connection.getLocalIP()); + } + } + /** * Returns the current endpoint this client is connected to. * * @return The current endpoint. + * @throws NullPointerException if the client is not connected */ - public InetSocketAddress getCurrentEndpoint() { + public InetSocketAddress getCurrentEndpoint() throws NullPointerException { return connection.getCurrentEndPoint(); } + /** + * Safe version of {@link CMClient#getCurrentEndpoint()}. + * + * @return The current endpoint. + */ + //JavaSteam addition + public Optional getCurrentEndpointOptional() { + var connection = this.connection; + if (connection == null) { + return Optional.empty(); + } else { + return Optional.ofNullable(connection.getCurrentEndPoint()); + } + } + /** * Gets the universe of this client. * @@ -522,7 +571,7 @@ public long getSessionToken() { * @return the Steam recommended Cell ID of this client. This value is assigned after a logon attempt has succeeded. * This value will be null if the client is logged off of Steam. */ - public Integer getCellID() { + public @Nullable Integer getCellID() { return cellID; } @@ -532,7 +581,7 @@ public Integer getCellID() { * * @return The session ID. */ - public Integer getSessionID() { + public @Nullable Integer getSessionID() { return sessionID; } @@ -542,7 +591,7 @@ public Integer getSessionID() { * * @return The SteamID. */ - public SteamID getSteamID() { + public @Nullable SteamID getSteamID() { return steamID; } From d6c91d3dc2760724fa186832d3d97689cdad8468 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Fri, 9 May 2025 15:47:14 -0500 Subject: [PATCH 2/5] Fixup for kotlin side of things --- .../javasteam/steam/handlers/steamcloud/SteamCloud.kt | 4 ++-- .../steam/handlers/steamfriends/SteamFriends.kt | 9 ++++++--- .../steam/handlers/steamfriends/cache/FriendCache.kt | 2 +- .../javasteam/steam/handlers/steamuser/SteamUser.kt | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamcloud/SteamCloud.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamcloud/SteamCloud.kt index 4b45d60e..c33bee9f 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamcloud/SteamCloud.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamcloud/SteamCloud.kt @@ -238,7 +238,7 @@ class SteamCloud : ClientMsgHandler() { timestamp: Date, filename: String, platformsToSync: Int = UInt.MAX_VALUE.toInt(), - cellId: Int = client.cellID, + cellId: Int = client.cellID!!, canEncrypt: Boolean = true, isSharedFile: Boolean = false, deprecatedRealm: Int? = null, @@ -492,7 +492,7 @@ class SteamCloud : ClientMsgHandler() { bytesExpected: Long, bytesActual: Long, durationMs: Int, - cellId: Int = client.cellID, + cellId: Int = client.cellID!!, proxied: Boolean, ipv6Local: Boolean, ipv6Remote: Boolean, diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/SteamFriends.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/SteamFriends.kt index fe7c555b..b6c70e84 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/SteamFriends.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/SteamFriends.kt @@ -422,10 +422,13 @@ class SteamFriends : ClientMsgHandler() { body.steamIdChat = chatID body.type = EChatInfoType.StateChange + // SteamID can be null if not connected - will be ultimately ignored in Client.Send. + val localSteamID = client.steamID?.convertToUInt64() ?: SteamID().convertToUInt64() + try { - writeLong(client.steamID.convertToUInt64()) // ChatterActedOn + writeLong(localSteamID) // ChatterActedOn writeInt(EChatMemberStateChange.Left.code()) // StateChange - writeLong(client.steamID.convertToUInt64()) // ChatterActedBy + writeLong(localSteamID) // ChatterActedBy } catch (e: IOException) { logger.debug(e) } @@ -473,7 +476,7 @@ class SteamFriends : ClientMsgHandler() { body.steamIdInvited = steamIdUser.convertToUInt64() // steamclient also sends the steamid of the user that did the invitation // we'll mimic that behavior - body.steamIdPatron = client.steamID.convertToUInt64() + body.steamIdPatron = client.steamID?.convertToUInt64() ?: SteamID().convertToUInt64() }.also(client::send) } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/cache/FriendCache.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/cache/FriendCache.kt index d03a510a..0f5d6d1f 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/cache/FriendCache.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/cache/FriendCache.kt @@ -62,5 +62,5 @@ class AccountCache { fun getUser(steamId: SteamID): User = if (isLocalUser(steamId)) localUser else users.getAccount(steamId) - fun isLocalUser(steamId: SteamID): Boolean = localUser.steamID == steamId + fun isLocalUser(steamId: SteamID?): Boolean = localUser.steamID == steamId } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/SteamUser.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/SteamUser.kt index d14e7031..1162ee73 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/SteamUser.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/SteamUser.kt @@ -34,7 +34,7 @@ import `in`.dragonbra.javasteam.util.NetHelpers */ class SteamUser : ClientMsgHandler() { - val steamID: SteamID + val steamID: SteamID? get() = client.steamID /** From 4f9b71ae7b5ee70151cc947144c956b2f643dce5 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Fri, 9 May 2025 16:19:45 -0500 Subject: [PATCH 3/5] Log null connections when trying to mark bad. Allow nullable Protocol types in trymark. Which will return false if a paramater is null. --- src/main/java/in/dragonbra/javasteam/steam/CMClient.java | 8 ++++++-- .../javasteam/steam/discovery/SmartCMServerList.kt | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index dfb484ab..024210e5 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -423,9 +423,11 @@ private void handleLogOnResponse(IPacketMsg packetMsg) { heartBeatFunc.setDelay(logonResp.getBody().getLegacyOutOfGameHeartbeatSeconds() * 1000L); heartBeatFunc.start(); } else if (logonResponse == EResult.TryAnotherCM || logonResponse == EResult.ServiceUnavailable) { - var connection = this.connection;// probably not needed + var connection = this.connection; if (connection != null) { getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); + } else { + logger.error("Connection was null trying to mark endpoint bad."); } } } @@ -445,9 +447,11 @@ private void handleLoggedOff(IPacketMsg packetMsg) { logger.debug("handleLoggedOff got " + logoffResult); if (logoffResult == EResult.TryAnotherCM || logoffResult == EResult.ServiceUnavailable) { - var connection = this.connection;// probably not needed + var connection = this.connection; if (connection != null) { getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); + } else { + logger.error("Connection was null trying to mark endpoint bad."); } } } else { diff --git a/src/main/java/in/dragonbra/javasteam/steam/discovery/SmartCMServerList.kt b/src/main/java/in/dragonbra/javasteam/steam/discovery/SmartCMServerList.kt index 9c8df9d7..f6d46ed9 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/discovery/SmartCMServerList.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/discovery/SmartCMServerList.kt @@ -193,11 +193,11 @@ class SmartCMServerList(private val configuration: SteamConfiguration) { } } - fun tryMark(endPoint: InetSocketAddress?, protocolTypes: ProtocolTypes, quality: ServerQuality): Boolean = - tryMark(endPoint, EnumSet.of(protocolTypes), quality) + fun tryMark(endPoint: InetSocketAddress?, protocolTypes: ProtocolTypes?, quality: ServerQuality): Boolean = + tryMark(endPoint, protocolTypes?.let { EnumSet.of(it) }, quality) - fun tryMark(endPoint: InetSocketAddress?, protocolTypes: EnumSet, quality: ServerQuality): Boolean { - if (endPoint == null) { + fun tryMark(endPoint: InetSocketAddress?, protocolTypes: EnumSet?, quality: ServerQuality): Boolean { + if (endPoint == null || protocolTypes == null) { logger.error("Couldn't mark an endpoint ${quality.name}, skipping it") return false } From 37aa9fcf4880870537c0f2db16a8267dad35d1b6 Mon Sep 17 00:00:00 2001 From: SrdjanV Date: Fri, 9 May 2025 23:34:56 +0200 Subject: [PATCH 4/5] Logg error when no CM servers are available to connect to --- src/main/java/in/dragonbra/javasteam/steam/CMClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index 024210e5..a29d7ee1 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -172,7 +172,7 @@ public void connect(ServerRecord cmServer) { } if (cmServer == null) { - logger.debug("No CM servers available to connect to"); + logger.error("No CM servers available to connect to"); onClientDisconnected(false); return; } From b21b1e7c2341bc8a220294e446caebf3496a3d85 Mon Sep 17 00:00:00 2001 From: SrdjanV Date: Sat, 10 May 2025 20:41:59 +0200 Subject: [PATCH 5/5] Remove Optional ip methods in CMClient and make the base methods return null instead of throwing a npe --- .../dragonbra/javasteam/steam/CMClient.java | 40 ++++--------------- .../steamgameserver/SteamGameServer.kt | 4 +- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index a29d7ee1..f45f97d2 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -488,51 +488,27 @@ public SmartCMServerList getServers() { /** * Returns the local IP of this client. * - * @return The local IP. - * @throws NullPointerException if the client is not connected + * @return The local IP or null if no connection is available. */ - public InetAddress getLocalIP() throws NullPointerException { - return connection.getLocalIP(); - } - - /** - * Safe version of {@link CMClient#getLocalIP()}. - * - * @return The current endpoint. - */ - //JavaSteam addition - public Optional getLocalIPOptional() { + public @Nullable InetAddress getLocalIP() { var connection = this.connection; if (connection == null) { - return Optional.empty(); - } else { - return Optional.ofNullable(connection.getLocalIP()); + return null; } + return connection.getLocalIP(); } /** * Returns the current endpoint this client is connected to. * - * @return The current endpoint. - * @throws NullPointerException if the client is not connected + * @return The current endpoint or null if no connection is available. */ - public InetSocketAddress getCurrentEndpoint() throws NullPointerException { - return connection.getCurrentEndPoint(); - } - - /** - * Safe version of {@link CMClient#getCurrentEndpoint()}. - * - * @return The current endpoint. - */ - //JavaSteam addition - public Optional getCurrentEndpointOptional() { + public @Nullable InetSocketAddress getCurrentEndpoint() { var connection = this.connection; if (connection == null) { - return Optional.empty(); - } else { - return Optional.ofNullable(connection.getCurrentEndPoint()); + return null; } + return connection.getCurrentEndPoint(); } /** diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgameserver/SteamGameServer.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgameserver/SteamGameServer.kt index af829db7..40760c45 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgameserver/SteamGameServer.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgameserver/SteamGameServer.kt @@ -52,7 +52,7 @@ class SteamGameServer : ClientMsgHandler() { logon.protoHeader.clientSessionid = 0 logon.protoHeader.steamid = gsId.convertToUInt64() - val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP) + val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP!!) logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) logon.body.protocolVersion = MsgClientLogon.CurrentProtocol @@ -87,7 +87,7 @@ class SteamGameServer : ClientMsgHandler() { logon.protoHeader.clientSessionid = 0 logon.protoHeader.steamid = gsId.convertToUInt64() - val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP) + val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP!!) logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) logon.body.protocolVersion = MsgClientLogon.CurrentProtocol