Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ package `in`.dragonbra.javasteam.steam.handlers.steamgameserver
* @param token Gets or sets the authentication token used to log in as a game server.
* @param appID Gets or sets the AppID this gameserver will serve.
*/
data class LogOnDetails(var token: String?, var appID: Int)
data class LogOnDetails(var token: String, var appID: Int)
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import `in`.dragonbra.javasteam.steam.handlers.steamgameserver.callback.TicketAu
import `in`.dragonbra.javasteam.steam.handlers.steamuser.callback.LoggedOnCallback
import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg
import `in`.dragonbra.javasteam.steam.steamclient.callbacks.DisconnectedCallback
import `in`.dragonbra.javasteam.types.AsyncJobSingle
import `in`.dragonbra.javasteam.types.SteamID
import `in`.dragonbra.javasteam.util.HardwareUtils
import `in`.dragonbra.javasteam.util.NetHelpers
import `in`.dragonbra.javasteam.util.NetHelpers.obfuscatePrivateIP
import `in`.dragonbra.javasteam.util.Utils
import `in`.dragonbra.javasteam.util.obfuscatePrivateIP
import java.net.Inet6Address

/**
Expand All @@ -34,26 +37,27 @@ class SteamGameServer : ClientMsgHandler() {
* Logs onto the Steam network as a persistent game server.
* The client should already have been connected at this point.
* Results are return in a [LoggedOnCallback].
*
* @param details The details to use for logging on.
* @return The Job ID of the request. This can be used to fine the appropriate [LoggedOnCallback]
* @throws IllegalArgumentException token is not set within [details].
*/
fun logOn(details: LogOnDetails) {
require(!details.token.isNullOrEmpty()) { "LogOn requires a game server token to be set in 'details'." }
@Throws(IllegalArgumentException::class)
fun logOn(details: LogOnDetails): AsyncJobSingle<LoggedOnCallback> {
require(!details.token.isBlank()) { "LogOn requires a game server token to be set in 'details'." }

val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogonGameServer)

if (!client.isConnected) {
LoggedOnCallback(EResult.NoConnection).also(client::postCallback)
return
client.postCallback(LoggedOnCallback(EResult.NoConnection, logon.sourceJobID))
return AsyncJobSingle(client, logon.sourceJobID)
}

val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogonGameServer)

val gsId = SteamID(0, 0, client.universe, EAccountType.GameServer)

logon.protoHeader.clientSessionid = 0
logon.protoHeader.steamid = gsId.convertToUInt64()

val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP!!)
logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp)
logon.body.obfuscatedPrivateIp = NetHelpers.getMsgIPAddress(client.localIP!!).obfuscatePrivateIP()

logon.body.protocolVersion = MsgClientLogon.CurrentProtocol

Expand All @@ -64,6 +68,8 @@ class SteamGameServer : ClientMsgHandler() {
logon.body.gameServerToken = details.token

client.send(logon)

return AsyncJobSingle(client, logon.sourceJobID)
}

/**
Expand All @@ -72,23 +78,23 @@ class SteamGameServer : ClientMsgHandler() {
* Results are return in a [LoggedOnCallback].
*
* @param appId The AppID served by this game server, or 0 for the default.
* @return The Job ID of the request. This can be used to fine the appropriate [LoggedOnCallback]
*/
@JvmOverloads
fun logOnAnonymous(appId: Int = 0) {
fun logOnAnonymous(appId: Int = 0): AsyncJobSingle<LoggedOnCallback> {
val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogonGameServer)

if (!client.isConnected) {
client.postCallback(LoggedOnCallback(EResult.NoConnection))
return
client.postCallback(LoggedOnCallback(EResult.NoConnection, logon.sourceJobID))
return AsyncJobSingle(client, logon.sourceJobID)
}

val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogonGameServer)

val gsId = SteamID(0, 0, client.universe, EAccountType.AnonGameServer)

logon.protoHeader.clientSessionid = 0
logon.protoHeader.steamid = gsId.convertToUInt64()

val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP!!)
logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp)
logon.body.obfuscatedPrivateIp = NetHelpers.getMsgIPAddress(client.localIP!!).obfuscatePrivateIP()

logon.body.protocolVersion = MsgClientLogon.CurrentProtocol

Expand All @@ -97,6 +103,8 @@ class SteamGameServer : ClientMsgHandler() {
logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID())

client.send(logon)

return AsyncJobSingle(client, logon.sourceJobID)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import `in`.dragonbra.javasteam.steam.handlers.steamuser.callback.WalletInfoCall
import `in`.dragonbra.javasteam.steam.handlers.steamuser.callback.WebAPIUserNonceCallback
import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg
import `in`.dragonbra.javasteam.steam.steamclient.callbacks.DisconnectedCallback
import `in`.dragonbra.javasteam.types.AsyncJobSingle
import `in`.dragonbra.javasteam.types.SteamID
import `in`.dragonbra.javasteam.util.HardwareUtils
import `in`.dragonbra.javasteam.util.JavaSteamAddition
Expand Down Expand Up @@ -55,21 +56,23 @@ class SteamUser : ClientMsgHandler() {
* Logs the client into the Steam3 network.
* The client should already have been connected at this point.
* Results are returned in a [LoggedOnCallback].
*
* @param details The details to use for logging on.
* @return The Job ID of the request. This can be used to find the appropriate [LoggedOnCallback]/
* @throws IllegalArgumentException Username or password are not set within [details].
*/
fun logOn(details: LogOnDetails) {
@Throws(IllegalArgumentException::class)
fun logOn(details: LogOnDetails): AsyncJobSingle<LoggedOnCallback> {
if (details.username.isEmpty() || (details.password.isNullOrEmpty() && details.accessToken.isNullOrEmpty())) {
throw IllegalArgumentException("LogOn requires a username and password or access token to be set in 'details'.")
}

val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogon)

if (!client.isConnected) {
client.postCallback(LoggedOnCallback(EResult.NoConnection))
return
client.postCallback(LoggedOnCallback(EResult.NoConnection, logon.sourceJobID))
return AsyncJobSingle(client, logon.sourceJobID)
}

val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogon)

val steamID = SteamID(details.accountID, details.accountInstance, client.universe, EAccountType.Individual)

if (details.loginID != null) {
Expand Down Expand Up @@ -141,24 +144,26 @@ class SteamUser : ClientMsgHandler() {
}

client.send(logon)

return AsyncJobSingle(client, logon.sourceJobID)
}

/**
* Logs the client into the Steam3 network as an anonymous user.
* The client should already have been connected at this point.
* Results are returned in a [LoggedOnCallback].
*
* @param details The details to use for logging on.
* @return The Job ID of the request. This can be used to find the appropriate [LoggedOnCallback]
*/
@JvmOverloads
fun logOnAnonymous(details: AnonymousLogOnDetails = AnonymousLogOnDetails()) {
fun logOnAnonymous(details: AnonymousLogOnDetails = AnonymousLogOnDetails()): AsyncJobSingle<LoggedOnCallback> {
val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogon)

if (!client.isConnected) {
client.postCallback(LoggedOnCallback(EResult.NoConnection))
return
client.postCallback(LoggedOnCallback(EResult.NoConnection, logon.sourceJobID))
return AsyncJobSingle(client, logon.sourceJobID)
}

val logon = ClientMsgProtobuf<CMsgClientLogon.Builder>(CMsgClientLogon::class.java, EMsg.ClientLogon)

val auId = SteamID(0, 0, client.universe, EAccountType.AnonUser)

logon.protoHeader.clientSessionid = 0
Expand All @@ -172,6 +177,8 @@ class SteamUser : ClientMsgHandler() {
logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID())

client.send(logon)

return AsyncJobSingle(client, logon.sourceJobID)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ class LoggedOffCallback(packetMsg: IPacketMsg) : CallbackMsg() {
SteammessagesClientserverLogin.CMsgClientLoggedOff::class.java,
packetMsg
)
jobID = loggedOff.targetJobID
result = EResult.from(loggedOff.body.eresult)
} else {
val loggedOff = ClientMsg(MsgClientLoggedOff::class.java, packetMsg)
jobID = loggedOff.targetJobID
result = loggedOff.body.result
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesParentalObjec
import `in`.dragonbra.javasteam.steam.handlers.steamuser.LogOnDetails
import `in`.dragonbra.javasteam.steam.handlers.steamuser.SteamUser
import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg
import `in`.dragonbra.javasteam.types.JobID
import `in`.dragonbra.javasteam.types.SteamID
import `in`.dragonbra.javasteam.util.NetHelpers
import `in`.dragonbra.javasteam.util.log.LogManager
Expand Down Expand Up @@ -161,6 +162,7 @@ class LoggedOnCallback : CallbackMsg {
)
val resp = loginResp.body

jobID = loginResp.targetJobID
result = EResult.from(resp.eresult)
extendedResult = EResult.from(resp.eresultExtended)

Expand Down Expand Up @@ -203,14 +205,16 @@ class LoggedOnCallback : CallbackMsg {
clientInstanceId = resp.clientInstanceId
}

constructor(result: EResult) {
constructor(result: EResult, jobID: JobID) {
this.result = result
this.jobID = jobID
}

private fun handleNonProtoLogon(packetMsg: IPacketMsg) {
val loginResp = ClientMsg(MsgClientLogOnResponse::class.java, packetMsg)
val resp = loginResp.body

jobID = loginResp.targetJobID
result = resp.result

outOfGameSecsPerHeartbeat = resp.outOfGameHeartbeatRateSec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import `in`.dragonbra.javasteam.steam.steamclient.callbacks.DisconnectedCallback
import `in`.dragonbra.javasteam.steam.steamclient.configuration.SteamConfiguration
import `in`.dragonbra.javasteam.types.AsyncJob
import `in`.dragonbra.javasteam.types.JobID
import `in`.dragonbra.javasteam.util.JavaSteamAddition
import `in`.dragonbra.javasteam.util.log.LogManager
import `in`.dragonbra.javasteam.util.log.Logger
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -152,7 +153,6 @@ class SteamClient @JvmOverloads constructor(

/**
* Returns a registered handler.
*
* @param type The type of the handler to cast to. Must derive from ClientMsgHandler.
* @param T The type of the handler to cast to. Must derive from ClientMsgHandler.
* @return A registered handler on success, or null if the handler could not be found.
Expand All @@ -163,11 +163,32 @@ class SteamClient @JvmOverloads constructor(
/**
* Kotlin Helper:
* Returns a registered handler.
*
* @param T The type of the handler to cast to. Must derive from ClientMsgHandler.
* @return A registered handler on success, or null if the handler could not be found.
*/
@JavaSteamAddition
inline fun <reified T : ClientMsgHandler> getHandler(): T? = getHandler(T::class.java)

/**
* Returns a registered handler, throwing if not found.
* @param type The type of the handler to cast to. Must derive from ClientMsgHandler.
* @return A registered handler.
* @throws IllegalArgumentException No handler of type [T] is registered.
*/
@Throws(IllegalArgumentException::class)
fun <T : ClientMsgHandler> getRequiredHandler(type: Class<T>): T =
getHandler(type) ?: throw IllegalArgumentException("No handler found for type ${type.name}")

/**
* Kotlin Helper:
* Returns a registered handler, throwing if not found.
* @param T The type of the handler to cast to. Must derive from ClientMsgHandler.
* @return A registered handler.
* @throws IllegalArgumentException No handler of type [T] is registered.
*/
@JavaSteamAddition
@Throws(IllegalArgumentException::class)
inline fun <reified T : ClientMsgHandler> getRequiredHandler() = getRequiredHandler(T::class.java)
//endregion

//region Callbacks
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import java.net.Socket
import java.net.UnknownHostException
import java.nio.ByteBuffer

fun CMsgIPAddress.obfuscatePrivateIP() = NetHelpers.obfuscatePrivateIP(this)

/**
* @author lngtr
* @since 2018-02-22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ protected <C extends CallbackMsg> C verifyCallback() {
return (C) callback;
}

protected CallbackMsg getCallback() {
ArgumentCaptor<CallbackMsg> callbackCaptor = ArgumentCaptor.forClass(CallbackMsg.class);
verify(steamClient, atLeast(1)).postCallback(callbackCaptor.capture());
return callbackCaptor.getValue();
}

protected IPacketMsg getPacket(EMsg msgType, boolean isProto) {
return CMClient.getPacketMsg(TestPackets.getPacket(msgType, isProto));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package in.dragonbra.javasteam.steam.handlers.steamgameserver;

import in.dragonbra.javasteam.enums.EResult;
import in.dragonbra.javasteam.steam.handlers.HandlerTestBase;
import in.dragonbra.javasteam.steam.handlers.steamuser.callback.LoggedOnCallback;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

/**
* @author Lossy
* @since 2026-3-22
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class SteamGameServerTest extends HandlerTestBase<SteamGameServer> {

@Override
protected SteamGameServer createHandler() {
return new SteamGameServer();
}

@Test
public void logOnPostsLoggedOnCallbackWhenNoConnection() {
Mockito.when(steamClient.isConnected()).thenReturn(false);

var details = new LogOnDetails("SuperSecretToken", 0);
var asyncJob = handler.logOn(details);

var callback = getCallback();
Assertions.assertNotNull(callback);
Assertions.assertEquals(LoggedOnCallback.class, callback.getClass());

var loc = (LoggedOnCallback) callback;
Assertions.assertEquals(EResult.NoConnection, loc.getResult());
Assertions.assertEquals(asyncJob.getJobID(), loc.getJobID());
}

@Test
public void logOnAnonymousPostsLoggedOnCallbackWhenNoConnection() {
Mockito.when(steamClient.isConnected()).thenReturn(false);

var asyncJob = handler.logOnAnonymous();

var callback = getCallback();
Assertions.assertNotNull(callback);
Assertions.assertEquals(LoggedOnCallback.class, callback.getClass());

var loc = (LoggedOnCallback) callback;
Assertions.assertEquals(EResult.NoConnection, loc.getResult());
Assertions.assertEquals(asyncJob.getJobID(), loc.getJobID());
}
}
Loading
Loading