From 9f3bbed95afead6f0b8d52e1323bf32f0ffa0f7c Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Wed, 16 Oct 2024 22:52:57 -0500 Subject: [PATCH 01/21] Add more logging for some incoming messages --- .../javasteamsamples/_020_friends/SampleFriends.java | 11 ++++++++++- .../java/in/dragonbra/javasteam/steam/CMClient.java | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_020_friends/SampleFriends.java b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_020_friends/SampleFriends.java index 7b9b2e74..136e827f 100644 --- a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_020_friends/SampleFriends.java +++ b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_020_friends/SampleFriends.java @@ -122,7 +122,16 @@ private void onConnected(ConnectedCallback callback) { private void onDisconnected(DisconnectedCallback callback) { System.out.println("Disconnected from Steam"); - isRunning = false; + if (callback.isUserInitiated()) { + isRunning = false; + } else { + try { + Thread.sleep(2000L); + steamClient.connect(); + } catch (InterruptedException e) { + System.err.println("An Interrupted exception occurred. " + e.getMessage()); + } + } } private void onLoggedOn(LoggedOnCallback callback) { diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index e7ca2707..706716e4 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -386,6 +386,7 @@ private void handleLogOnResponse(IPacketMsg packetMsg) { ClientMsgProtobuf logonResp = new ClientMsgProtobuf<>(CMsgClientLogonResponse.class, packetMsg); EResult logonResponse = EResult.from(logonResp.getBody().getEresult()); + logger.debug("handleLogOnResponse got response: " + logonResponse); if (logonResponse == EResult.OK) { sessionID = logonResp.getProtoHeader().getClientSessionid(); @@ -414,9 +415,13 @@ private void handleLoggedOff(IPacketMsg packetMsg) { ClientMsgProtobuf logoffMsg = new ClientMsgProtobuf<>(CMsgClientLoggedOff.class, packetMsg); EResult logoffResult = EResult.from(logoffMsg.getBody().getEresult()); + logger.debug("handleLoggedOff got " + logoffResult); + if (logoffResult == EResult.TryAnotherCM || logoffResult == EResult.ServiceUnavailable) { getServers().tryMark(connection.getCurrentEndPoint(), connection.getProtocolTypes(), ServerQuality.BAD); } + } else { + logger.debug("handleLoggedOff got unexpected response: " + packetMsg.getMsgType()); } } From d5e8436037bd1ac88e2ee6ce0f9fa10538d01efb Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:03:32 -0500 Subject: [PATCH 02/21] Add macOS 15 --- src/main/java/in/dragonbra/javasteam/util/Utils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/dragonbra/javasteam/util/Utils.java b/src/main/java/in/dragonbra/javasteam/util/Utils.java index 6b0c1ed6..3906ba41 100644 --- a/src/main/java/in/dragonbra/javasteam/util/Utils.java +++ b/src/main/java/in/dragonbra/javasteam/util/Utils.java @@ -58,7 +58,8 @@ public class Utils { OSX_OS_MAP.put(SystemUtils.IS_OS_MAC_OSX_BIG_SUR, EOSType.MacOS11); OSX_OS_MAP.put(SystemUtils.IS_OS_MAC_OSX_MONTEREY, EOSType.MacOS12); OSX_OS_MAP.put(SystemUtils.IS_OS_MAC_OSX_VENTURA, EOSType.MacOS13); - OSX_OS_MAP.put(checkOS("Mac OS X", "14"), EOSType.MacOS14); + OSX_OS_MAP.put(SystemUtils.IS_OS_MAC_OSX_SONOMA, EOSType.MacOS14); + OSX_OS_MAP.put(checkOS("Mac OS X", "15"), EOSType.MacOS15); LINUX_OS_MAP.put("2.2", EOSType.Linux22); LINUX_OS_MAP.put("2.4", EOSType.Linux24); From 757b4857996395323a6b971e777defde73ce5cc8 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:06:53 -0500 Subject: [PATCH 03/21] Add another test in WebHelpersTest --- .../dragonbra/javasteam/util/WebHelpersTest.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/test/java/in/dragonbra/javasteam/util/WebHelpersTest.java b/src/test/java/in/dragonbra/javasteam/util/WebHelpersTest.java index 0056cf89..e440649d 100644 --- a/src/test/java/in/dragonbra/javasteam/util/WebHelpersTest.java +++ b/src/test/java/in/dragonbra/javasteam/util/WebHelpersTest.java @@ -1,9 +1,9 @@ package in.dragonbra.javasteam.util; import in.dragonbra.javasteam.TestBase; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author lngtr @@ -12,8 +12,15 @@ public class WebHelpersTest extends TestBase { @Test - public void urlEncode() { + public void urlEncodeWithString() { String result = WebHelpers.urlEncode("encrypt THIS sTrInG1234 \10 \11 \12"); - assertEquals("encrypt+THIS+sTrInG1234+%08+%09+%0A", result); + Assertions.assertEquals("encrypt+THIS+sTrInG1234+%08+%09+%0A", result); } -} \ No newline at end of file + + @Test + public void urlEncodeWithByteArray() { + var input = "encrypt THIS sTrInG1234 \10 \11 \12".getBytes(); + String result = WebHelpers.urlEncode(input); + Assertions.assertEquals("encrypt+THIS+sTrInG1234+%08+%09+%0A", result); + } +} From 90f4a5a30b3c22e15f0a6c5f37a8828a7196b10d Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:11:48 -0500 Subject: [PATCH 04/21] Add tests in UtilsTest --- .../dragonbra/javasteam/util/UtilsTest.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java b/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java index eb39853d..4e8ee4ed 100644 --- a/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java +++ b/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java @@ -1,9 +1,8 @@ package in.dragonbra.javasteam.util; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * @author lngtr * @since 2019-01-15 @@ -17,6 +16,27 @@ public class UtilsTest { @Test public void crc32() { long result = Utils.crc32("test_string"); - assertEquals(0x0967B587, result); + Assertions.assertEquals(0x0967B587, result); + } + + @Test + public void toHex() { + byte[] byteArray = {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; + Assertions.assertEquals("DEADBEEF", Strings.toHex(byteArray)); + } + @Test + public void decodeHex() { + byte[] byteArray = {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; + Assertions.assertArrayEquals(byteArray, Strings.decodeHex("deadbeef")); + } + @Test + public void invalid_decodeHex() { + Assertions.assertThrows(StringIndexOutOfBoundsException.class, () -> Strings.decodeHex("odd")); + } + @Test + public void isNullOrEmpty() { + Assertions.assertTrue(Strings.isNullOrEmpty(null)); + Assertions.assertTrue(Strings.isNullOrEmpty("")); + Assertions.assertFalse(Strings.isNullOrEmpty("Hello World!")); } } From 4ef1350fbd317f9328b2bdb1a3b2289502948826 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:15:05 -0500 Subject: [PATCH 05/21] Add tests in PassableTest --- .../javasteam/util/PassableTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/java/in/dragonbra/javasteam/util/PassableTest.java diff --git a/src/test/java/in/dragonbra/javasteam/util/PassableTest.java b/src/test/java/in/dragonbra/javasteam/util/PassableTest.java new file mode 100644 index 00000000..e6b7e791 --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/PassableTest.java @@ -0,0 +1,33 @@ +package in.dragonbra.javasteam.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class PassableTest { + + @Test + public void BooleanPassable() { + var passableValue = new Passable<>(false); + + Assertions.assertFalse(passableValue.getValue()); + + passableValue.setValue(true); + Assertions.assertTrue(passableValue.getValue()); + + passableValue.setValue(null); + Assertions.assertNull(passableValue.getValue()); + } + + @Test + public void IntegerPassable() { + var passableValue = new Passable(null); + + Assertions.assertNull(passableValue.getValue()); + + passableValue.setValue(1); + Assertions.assertEquals(1, passableValue.getValue()); + + passableValue.setValue(2); + Assertions.assertEquals(2, passableValue.getValue()); + } +} From fd27b9ccfd634bb10444dc81f25263aa8c031e17 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:19:49 -0500 Subject: [PATCH 06/21] Add tests in CollectionsTest and StringsTest. Remove string tests from UtilsTest --- .../javasteam/util/CollectionsTest.java | 35 +++++++++++++++++++ .../dragonbra/javasteam/util/StringsTest.java | 31 ++++++++++++++++ .../dragonbra/javasteam/util/UtilsTest.java | 21 ----------- 3 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 src/test/java/in/dragonbra/javasteam/util/CollectionsTest.java create mode 100644 src/test/java/in/dragonbra/javasteam/util/StringsTest.java diff --git a/src/test/java/in/dragonbra/javasteam/util/CollectionsTest.java b/src/test/java/in/dragonbra/javasteam/util/CollectionsTest.java new file mode 100644 index 00000000..ca415f9a --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/CollectionsTest.java @@ -0,0 +1,35 @@ +package in.dragonbra.javasteam.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; + +public class CollectionsTest { + + private HashMap values; + + @BeforeEach + public void setUp() { + values = new HashMap<>(); + values.put(1, "Some Value"); + values.put(2, "A Value"); + values.put(3, "All the values"); + } + + @Test + public void getKeyByValueExistingValue() { + var result = CollectionUtils.getKeyByValue(values, "A Value"); + Assertions.assertEquals(2, result); + } + + @Test + public void getKeyByValueNullValue() { + var nullResult = CollectionUtils.getKeyByValue(values, "Null Value"); + Assertions.assertNull(nullResult); + + var nullResult2 = CollectionUtils.getKeyByValue(values, null); + Assertions.assertNull(nullResult2); + } +} diff --git a/src/test/java/in/dragonbra/javasteam/util/StringsTest.java b/src/test/java/in/dragonbra/javasteam/util/StringsTest.java new file mode 100644 index 00000000..6cfae69b --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/StringsTest.java @@ -0,0 +1,31 @@ +package in.dragonbra.javasteam.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class StringsTest { + + @Test + public void toHex() { + byte[] byteArray = {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; + Assertions.assertEquals("DEADBEEF", Strings.toHex(byteArray)); + } + + @Test + public void decodeHex() { + byte[] byteArray = {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; + Assertions.assertArrayEquals(byteArray, Strings.decodeHex("deadbeef")); + } + + @Test + public void invalid_decodeHex() { + Assertions.assertThrows(StringIndexOutOfBoundsException.class, () -> Strings.decodeHex("odd")); + } + + @Test + public void isNullOrEmpty() { + Assertions.assertTrue(Strings.isNullOrEmpty(null)); + Assertions.assertTrue(Strings.isNullOrEmpty("")); + Assertions.assertFalse(Strings.isNullOrEmpty("Hello World!")); + } +} diff --git a/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java b/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java index 4e8ee4ed..3fdf40cc 100644 --- a/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java +++ b/src/test/java/in/dragonbra/javasteam/util/UtilsTest.java @@ -18,25 +18,4 @@ public void crc32() { long result = Utils.crc32("test_string"); Assertions.assertEquals(0x0967B587, result); } - - @Test - public void toHex() { - byte[] byteArray = {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; - Assertions.assertEquals("DEADBEEF", Strings.toHex(byteArray)); - } - @Test - public void decodeHex() { - byte[] byteArray = {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; - Assertions.assertArrayEquals(byteArray, Strings.decodeHex("deadbeef")); - } - @Test - public void invalid_decodeHex() { - Assertions.assertThrows(StringIndexOutOfBoundsException.class, () -> Strings.decodeHex("odd")); - } - @Test - public void isNullOrEmpty() { - Assertions.assertTrue(Strings.isNullOrEmpty(null)); - Assertions.assertTrue(Strings.isNullOrEmpty("")); - Assertions.assertFalse(Strings.isNullOrEmpty("Hello World!")); - } } From 504450aee7650cbafd7b4aede46f9a1be6feafe8 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:20:22 -0500 Subject: [PATCH 07/21] Remove EResultDeserializer --- .../javasteam/util/EResultDeserializer.java | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/main/java/in/dragonbra/javasteam/util/EResultDeserializer.java diff --git a/src/main/java/in/dragonbra/javasteam/util/EResultDeserializer.java b/src/main/java/in/dragonbra/javasteam/util/EResultDeserializer.java deleted file mode 100644 index d8ea59df..00000000 --- a/src/main/java/in/dragonbra/javasteam/util/EResultDeserializer.java +++ /dev/null @@ -1,22 +0,0 @@ -package in.dragonbra.javasteam.util; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import in.dragonbra.javasteam.enums.EResult; - -import java.lang.reflect.Type; - -/** - * @author lngtr - * @since 2018-02-20 - */ -public class EResultDeserializer implements JsonDeserializer { - - @Override - public EResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - int code = json.getAsInt(); - return EResult.from(code); - } -} From 465c01188210629796f84aaddefabdb031b97b86 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 00:26:03 -0500 Subject: [PATCH 08/21] Add more tests for the utils package --- .../dragonbra/javasteam/util/MsgUtilTest.java | 38 +++++++++++++++ .../javasteam/util/compat/ConsumerTest.java | 21 +++++++++ .../javasteam/util/crypto/RSACryptoTest.java | 37 +++++++++++++++ .../javasteam/util/event/EventTest.java | 47 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 src/test/java/in/dragonbra/javasteam/util/MsgUtilTest.java create mode 100644 src/test/java/in/dragonbra/javasteam/util/compat/ConsumerTest.java create mode 100644 src/test/java/in/dragonbra/javasteam/util/crypto/RSACryptoTest.java create mode 100644 src/test/java/in/dragonbra/javasteam/util/event/EventTest.java diff --git a/src/test/java/in/dragonbra/javasteam/util/MsgUtilTest.java b/src/test/java/in/dragonbra/javasteam/util/MsgUtilTest.java new file mode 100644 index 00000000..8014cc29 --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/MsgUtilTest.java @@ -0,0 +1,38 @@ +package in.dragonbra.javasteam.util; + +import in.dragonbra.javasteam.enums.EMsg; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class MsgUtilTest { + + @Test + public void isProtoBuf() { + var result = MsgUtil.isProtoBuf(-2147482868); // ClientLicenseList + Assertions.assertTrue(result); + } + + @Test + public void isNotProtoBuf() { + var result = MsgUtil.isProtoBuf(798); // ClientUpdateGuestPassesList + Assertions.assertFalse(result); + } + + @Test + public void getMsgAsServiceMethodResponse() { + var result = MsgUtil.getMsg(-2147483501); // ServiceMethodResponse + Assertions.assertEquals(EMsg.ServiceMethodResponse, result); + } + + @Test + public void getMsgAsClientUpdateGuestPassesList() { + var result = MsgUtil.getMsg(798); // ClientUpdateGuestPassesList + Assertions.assertEquals(EMsg.ClientUpdateGuestPassesList, result); + } + + @Test + public void getMsgAsWrongMsg() { + var result = MsgUtil.getMsg(-2147483501); // ServiceMethodResponse + Assertions.assertNotEquals(EMsg.ClientUpdateGuestPassesList, result); + } +} diff --git a/src/test/java/in/dragonbra/javasteam/util/compat/ConsumerTest.java b/src/test/java/in/dragonbra/javasteam/util/compat/ConsumerTest.java new file mode 100644 index 00000000..e0983aaa --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/compat/ConsumerTest.java @@ -0,0 +1,21 @@ +package in.dragonbra.javasteam.util.compat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ConsumerTest { + + @Test + void testConsumerString() { + Consumer consumer = s -> Assertions.assertEquals("Test String", s); + + consumer.accept("Test String"); + } + + @Test + void testConsumerBoolean() { + Consumer consumer = b -> Assertions.assertEquals(true, b); + + consumer.accept(true); + } +} diff --git a/src/test/java/in/dragonbra/javasteam/util/crypto/RSACryptoTest.java b/src/test/java/in/dragonbra/javasteam/util/crypto/RSACryptoTest.java new file mode 100644 index 00000000..a226fd17 --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/crypto/RSACryptoTest.java @@ -0,0 +1,37 @@ +package in.dragonbra.javasteam.util.crypto; + +import in.dragonbra.javasteam.enums.EUniverse; +import in.dragonbra.javasteam.util.KeyDictionary; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +public class RSACryptoTest { + + private RSACrypto rsaCrypto; + + @BeforeEach + public void setUp() { + var pubKey = KeyDictionary.getPublicKey(EUniverse.Public); + rsaCrypto = new RSACrypto(pubKey); + } + + @Test + public void encrypt() { + var input = CryptoHelper.generateRandomBlock(32); + var encrypted = rsaCrypto.encrypt(input); + + Assertions.assertNotNull(encrypted); + } + + @Test + public void cipherInstance() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { + var cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", CryptoHelper.SEC_PROV); + Assertions.assertNotNull(cipher); + } +} diff --git a/src/test/java/in/dragonbra/javasteam/util/event/EventTest.java b/src/test/java/in/dragonbra/javasteam/util/event/EventTest.java new file mode 100644 index 00000000..bb066c62 --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/event/EventTest.java @@ -0,0 +1,47 @@ +package in.dragonbra.javasteam.util.event; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class EventTest { + + static class TestEventHandler implements EventHandler { + boolean eventHandled = false; + Object sender = null; + EventArgs eventArgs = null; + + @Override + public void handleEvent(Object sender, EventArgs e) { + this.eventHandled = true; + this.sender = sender; + this.eventArgs = e; + } + } + + @Test + void addEventHandlerAndHandleEvent() { + var event = new Event<>(); + var handler = new TestEventHandler(); + + event.addEventHandler(handler); + + event.handleEvent(this, EventArgs.EMPTY); + + Assertions.assertTrue(handler.eventHandled); + Assertions.assertEquals(this, handler.sender); + Assertions.assertEquals(EventArgs.EMPTY, handler.eventArgs); + } + + @Test + void removeEventHandler() { + var event = new Event<>(); + var handler = new TestEventHandler(); + + event.addEventHandler(handler); + event.removeEventHandler(handler); + + event.handleEvent(this, EventArgs.EMPTY); + + Assertions.assertFalse(handler.eventHandled); + } +} From 91d531caca892af0534ccf9d9990bd7803bcb4b3 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 23:41:18 -0500 Subject: [PATCH 09/21] Make UTF8 the standard charset. --- .../javasteam/base/AbstractMsgBase.java | 7 +- .../dragonbra/javasteam/types/KeyValue.java | 1 + .../javasteam/util/stream/BinaryReader.java | 30 +++- .../util/stream/BinaryReaderTest.java | 160 ++++++++++++++++++ .../util/stream/BinaryWriterTest.java | 92 ++++++++++ 5 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java create mode 100644 src/test/java/in/dragonbra/javasteam/util/stream/BinaryWriterTest.java diff --git a/src/main/java/in/dragonbra/javasteam/base/AbstractMsgBase.java b/src/main/java/in/dragonbra/javasteam/base/AbstractMsgBase.java index b17a7528..69a8c957 100644 --- a/src/main/java/in/dragonbra/javasteam/base/AbstractMsgBase.java +++ b/src/main/java/in/dragonbra/javasteam/base/AbstractMsgBase.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * This class provides a payload backing to client messages. @@ -107,7 +108,7 @@ public double readDouble() throws IOException { } public void writeString(String data) throws IOException { - writeString(data, Charset.defaultCharset()); + writeString(data, StandardCharsets.UTF_8); } public void writeString(String data, Charset charset) throws IOException { @@ -123,7 +124,7 @@ public void writeString(String data, Charset charset) throws IOException { } public void writeNullTermString(String data) throws IOException { - writeNullTermString(data, Charset.defaultCharset()); + writeNullTermString(data, StandardCharsets.UTF_8); } public void writeNullTermString(String data, Charset charset) throws IOException { @@ -132,7 +133,7 @@ public void writeNullTermString(String data, Charset charset) throws IOException } public String readNullTermString() throws IOException { - return readNullTermString(Charset.defaultCharset()); + return readNullTermString(StandardCharsets.UTF_8); } public String readNullTermString(Charset charset) throws IOException { diff --git a/src/main/java/in/dragonbra/javasteam/types/KeyValue.java b/src/main/java/in/dragonbra/javasteam/types/KeyValue.java index 91709bf0..da11ecc5 100644 --- a/src/main/java/in/dragonbra/javasteam/types/KeyValue.java +++ b/src/main/java/in/dragonbra/javasteam/types/KeyValue.java @@ -442,6 +442,7 @@ private static KeyValue loadFromFile(String path, boolean asBinary) { return null; } + // TODO charsets? try (FileInputStream fis = new FileInputStream(file)) { // Massage the incoming file to be encoded as UTF-8. String fisString = IOUtils.toString(fis, Charset.defaultCharset()); diff --git a/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java b/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java index 1421c54c..b5fa2d57 100644 --- a/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java +++ b/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java @@ -2,6 +2,7 @@ import java.io.*; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Basically DataInputStream, but the bytes are parsed in reverse order @@ -99,8 +100,9 @@ public boolean readBoolean() throws IOException { position += 1; return ch != 0; } + public String readNullTermString() throws IOException { - return readNullTermString(Charset.defaultCharset()); + return readNullTermString(StandardCharsets.UTF_8); } public String readNullTermString(Charset charset) throws IOException { @@ -108,6 +110,10 @@ public String readNullTermString(Charset charset) throws IOException { throw new IOException("charset is null"); } + if (charset.equals(StandardCharsets.UTF_8)) { + return readNullTermUtf8String(); + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(0); BinaryWriter bw = new BinaryWriter(buffer); @@ -126,7 +132,27 @@ public String readNullTermString(Charset charset) throws IOException { return new String(bytes, charset); } + private String readNullTermUtf8String() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int b; + + while ((b = in.read()) > 0) { + baos.write(b); + position += 1; + } + + if (b == -1) { + throw new EOFException(); + } + + if (b == 0) { + position++; + } + + return baos.toString(StandardCharsets.UTF_8); + } + public int getPosition() { return position; } -} \ No newline at end of file +} diff --git a/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java b/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java new file mode 100644 index 00000000..cf8c58cf --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java @@ -0,0 +1,160 @@ +package in.dragonbra.javasteam.util.stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class BinaryReaderTest { + + private BinaryReader binaryReader; + + @Test + void testReadInt() throws IOException { + byte[] data = {1, 0, 0, 0}; // 1 in little-endian + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readInt(); + + Assertions.assertEquals(1, result); + Assertions.assertEquals(4, binaryReader.getPosition()); + } + + @Test + void testReadBytes() throws IOException { + byte[] data = {0x01, 0x02, 0x03, 0x04}; + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readBytes(4); + + Assertions.assertArrayEquals(data, result); + Assertions.assertEquals(4, binaryReader.getPosition()); + } + + @Test + void testReadShort() throws IOException { + byte[] data = {0x01, 0x00}; // 1 in little-endian + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readShort(); + + Assertions.assertEquals(1, result); + Assertions.assertEquals(2, binaryReader.getPosition()); + } + + @Test + void testReadLong() throws IOException { + byte[] data = {1, 0, 0, 0, 0, 0, 0, 0}; // 1 in little-endian + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readLong(); + + Assertions.assertEquals(1L, result); + Assertions.assertEquals(8, binaryReader.getPosition()); + } + + @Test + void testReadChar() throws IOException { + byte[] data = {65}; // ASCII 'A' + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readChar(); + + Assertions.assertEquals('A', result); + Assertions.assertEquals(1, binaryReader.getPosition()); + } + + @Test + void testReadBoolean() throws IOException { + byte[] data = {1}; // true + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readBoolean(); + + Assertions.assertTrue(result); + Assertions.assertEquals(1, binaryReader.getPosition()); + } + + @Test + void testReadNullTermUtf8String() throws IOException { + var string = "Hello\0"; + var data = string.getBytes(StandardCharsets.UTF_8); + + binaryReader = new BinaryReader(new MemoryStream(data)); + + Assertions.assertEquals("Hello", binaryReader.readNullTermString()); + Assertions.assertEquals(6, binaryReader.getPosition()); + } + + @Test + void testReadNullTermUtf8StringWithSpaces() throws IOException { + var string = "Hello World With Spaces\0"; + var data = string.getBytes(StandardCharsets.UTF_8); + + binaryReader = new BinaryReader(new MemoryStream(data)); + + Assertions.assertEquals("Hello World With Spaces", binaryReader.readNullTermString()); + Assertions.assertEquals(24, binaryReader.getPosition()); + } + + @Test + void testReadFloat() throws IOException { + int floatAsInt = Float.floatToIntBits(3.14f); + byte[] data = { + (byte) (floatAsInt & 0xff), + (byte) ((floatAsInt >> 8) & 0xff), + (byte) ((floatAsInt >> 16) & 0xff), + (byte) ((floatAsInt >> 24) & 0xff) + }; + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readFloat(); + + Assertions.assertEquals(3.14f, result, 0.001); + Assertions.assertEquals(4, binaryReader.getPosition()); + } + + @Test + void testReadDouble() throws IOException { + var doubleAsLong = Double.doubleToLongBits(3.14159); + byte[] data = { + (byte) (doubleAsLong & 0xff), + (byte) ((doubleAsLong >> 8) & 0xff), + (byte) ((doubleAsLong >> 16) & 0xff), + (byte) ((doubleAsLong >> 24) & 0xff), + (byte) ((doubleAsLong >> 32) & 0xff), + (byte) ((doubleAsLong >> 40) & 0xff), + (byte) ((doubleAsLong >> 48) & 0xff), + (byte) ((doubleAsLong >> 56) & 0xff) + }; + + binaryReader = new BinaryReader(new MemoryStream(data)); + + var result = binaryReader.readDouble(); + + Assertions.assertEquals(3.14159, result, 0.00001); + Assertions.assertEquals(8, binaryReader.getPosition()); + } + + @Test + void testEOFException() { + byte[] data = {}; + binaryReader = new BinaryReader(new MemoryStream(data)); + + Assertions.assertThrows(EOFException.class, () -> binaryReader.readBoolean()); + Assertions.assertThrows(EOFException.class, () -> binaryReader.readByte()); + Assertions.assertThrows(EOFException.class, () -> binaryReader.readChar()); + Assertions.assertThrows(EOFException.class, () -> binaryReader.readInt()); + Assertions.assertThrows(EOFException.class, () -> binaryReader.readNullTermString()); + Assertions.assertThrows(EOFException.class, () -> binaryReader.readShort()); + } +} diff --git a/src/test/java/in/dragonbra/javasteam/util/stream/BinaryWriterTest.java b/src/test/java/in/dragonbra/javasteam/util/stream/BinaryWriterTest.java new file mode 100644 index 00000000..2028b77e --- /dev/null +++ b/src/test/java/in/dragonbra/javasteam/util/stream/BinaryWriterTest.java @@ -0,0 +1,92 @@ +package in.dragonbra.javasteam.util.stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +public class BinaryWriterTest { + + private MemoryStream memoryStream; + private BinaryWriter binaryWriter; + + @BeforeEach + void setUp() { + memoryStream = new MemoryStream(); + binaryWriter = new BinaryWriter(memoryStream.asOutputStream()); + } + + @Test + void testWriteInt() throws IOException { + binaryWriter.writeInt(1); // 1 should be written as [01 00 00 00] + + byte[] expected = {0x01, 0x00, 0x00, 0x00}; + + Assertions.assertArrayEquals(expected, memoryStream.toByteArray()); + } + + @Test + void testWriteShort() throws IOException { + binaryWriter.writeShort((short) 1); // 1 should be written as [01 00] + + byte[] expected = {0x01, 0x00}; + + Assertions.assertArrayEquals(expected, memoryStream.toByteArray()); + } + + @Test + void testWriteLong() throws IOException { + binaryWriter.writeLong(1L); // 1 should be written as [01 00 00 00 00 00 00 00] + + byte[] expected = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + Assertions.assertArrayEquals(expected, memoryStream.toByteArray()); + } + + @Test + void testWriteFloat() throws IOException { + binaryWriter.writeFloat(3.14f); + + var ms = new MemoryStream(memoryStream.toByteArray()); + var br = new BinaryReader(ms); + + Assertions.assertEquals(3.14f, br.readFloat()); + } + + @Test + void testWriteDouble() throws IOException { + binaryWriter.writeDouble(3.14159); + + var ms = new MemoryStream(memoryStream.toByteArray()); + var br = new BinaryReader(ms); + + Assertions.assertEquals(3.14159, br.readDouble(), 0.001); + } + + @Test + void testWriteBoolean() throws IOException { + binaryWriter.writeBoolean(true); + + Assertions.assertArrayEquals(new byte[]{1}, memoryStream.toByteArray()); + + memoryStream.reset(); // Reset stream for next test + binaryWriter.writeBoolean(false); + + Assertions.assertArrayEquals(new byte[]{0}, memoryStream.toByteArray()); + } + + @Test + void testWriteByte() throws IOException { + binaryWriter.writeByte((byte) 0xAB); + + Assertions.assertArrayEquals(new byte[]{(byte) 0xAB}, memoryStream.toByteArray()); + } + + @Test + void testWriteChar() throws IOException { + binaryWriter.writeChar('A'); // ASCII value of 'A' is 65 + + Assertions.assertArrayEquals(new byte[]{65}, memoryStream.toByteArray()); + } +} From feed35c3b90660317f22ced2e8636e70850ae616 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 23:43:00 -0500 Subject: [PATCH 10/21] Add PICS sample to verify encoding output --- .../_022_pics/SamplePics.java | 226 ++++++++++++++++++ .../steam/webapi/SteamDirectoryTest.java | 2 +- 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_022_pics/SamplePics.java diff --git a/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_022_pics/SamplePics.java b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_022_pics/SamplePics.java new file mode 100644 index 00000000..8a544e02 --- /dev/null +++ b/javasteam-samples/src/main/java/in/dragonbra/javasteamsamples/_022_pics/SamplePics.java @@ -0,0 +1,226 @@ +package in.dragonbra.javasteamsamples._022_pics; + +import in.dragonbra.javasteam.enums.EResult; +import in.dragonbra.javasteam.steam.authentication.*; +import in.dragonbra.javasteam.steam.handlers.steamapps.PICSRequest; +import in.dragonbra.javasteam.steam.handlers.steamapps.SteamApps; +import in.dragonbra.javasteam.steam.handlers.steamapps.callback.PICSChangesCallback; +import in.dragonbra.javasteam.steam.handlers.steamapps.callback.PICSProductInfoCallback; +import in.dragonbra.javasteam.steam.handlers.steamuser.LogOnDetails; +import in.dragonbra.javasteam.steam.handlers.steamuser.SteamUser; +import in.dragonbra.javasteam.steam.handlers.steamuser.callback.LoggedOffCallback; +import in.dragonbra.javasteam.steam.handlers.steamuser.callback.LoggedOnCallback; +import in.dragonbra.javasteam.steam.steamclient.SteamClient; +import in.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackManager; +import in.dragonbra.javasteam.steam.steamclient.callbacks.ConnectedCallback; +import in.dragonbra.javasteam.steam.steamclient.callbacks.DisconnectedCallback; +import in.dragonbra.javasteam.types.KeyValue; +import in.dragonbra.javasteam.util.log.DefaultLogListener; +import in.dragonbra.javasteam.util.log.LogManager; + +import java.util.concurrent.CancellationException; + +@SuppressWarnings("FieldCanBeLocal") +public class SamplePics implements Runnable { + + private SteamClient steamClient; + + private CallbackManager manager; + + private SteamApps steamApps; + + private SteamUser steamUser; + + private boolean isRunning; + + private final String user; + + private final String pass; + + private String previouslyStoredGuardData; // For the sake of this sample, we do not persist guard data + + public SamplePics(String user, String pass) { + this.user = user; + this.pass = pass; + } + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Sample1: No username and password specified!"); + return; + } + + LogManager.addListener(new DefaultLogListener()); + + new SamplePics(args[0], args[1]).run(); + } + + @Override + public void run() { + + // // If any configuration needs to be set; such as connection protocol api key, etc., you can configure it like so. + // var configuration = SteamConfiguration.create(config -> { + // config.withProtocolTypes(ProtocolTypes.WEB_SOCKET); + // }); + // // create our steamclient instance with custom configuration. + // steamClient = new SteamClient(configuration); + + // create our steamclient instance using default configuration + steamClient = new SteamClient(); + + // create the callback manager which will route callbacks to function calls + manager = new CallbackManager(steamClient); + + // get the steamuser handler, which is used for logging on after successfully connecting + steamUser = steamClient.getHandler(SteamUser.class); + + // get the steamuser handler, which is used for interacting with apps and packages. + steamApps = steamClient.getHandler(SteamApps.class); + + // register a few callbacks we're interested in + // these are registered upon creation to a callback manager, which will then route the callbacks + // to the functions specified + manager.subscribe(ConnectedCallback.class, this::onConnected); + manager.subscribe(DisconnectedCallback.class, this::onDisconnected); + + manager.subscribe(LoggedOnCallback.class, this::onLoggedOn); + manager.subscribe(LoggedOffCallback.class, this::onLoggedOff); + + manager.subscribe(PICSChangesCallback.class, this::onPicsChanges); + manager.subscribe(PICSProductInfoCallback.class, this::onPicsProduct); + + isRunning = true; + + System.out.println("Connecting to steam..."); + + // initiate the connection + steamClient.connect(); + + // create our callback handling loop + while (isRunning) { + // in order for the callbacks to get routed, they need to be handled by the manager + manager.runWaitCallbacks(1000L); + } + } + + @SuppressWarnings("DanglingJavadoc") + private void onConnected(ConnectedCallback callback) { + System.out.println("Connected to Steam! Logging in " + user + "..."); + + var shouldRememberPassword = false; + + AuthSessionDetails authDetails = new AuthSessionDetails(); + authDetails.username = user; + authDetails.password = pass; + authDetails.persistentSession = shouldRememberPassword; + + // See NewGuardData comment below. + authDetails.guardData = previouslyStoredGuardData; + + /** + * {@link UserConsoleAuthenticator} is the default authenticator implementation provided by JavaSteam + * for ease of use which blocks the thread and asks for user input to enter the code. + * However, if you require special handling (e.g. you have the TOTP secret and can generate codes on the fly), + * you can implement your own {@link IAuthenticator}. + */ + authDetails.authenticator = new UserConsoleAuthenticator(); + + try { + // Begin authenticating via credentials. + var authSession = steamClient.getAuthentication().beginAuthSessionViaCredentials(authDetails); + + // Note: This is blocking, it would be up to you to make it non-blocking for Java. + // Note: Kotlin uses should use ".pollingWaitForResult()" as its a suspending function. + AuthPollResult pollResponse = authSession.pollingWaitForResultCompat().get(); + + if (pollResponse.getNewGuardData() != null) { + // When using certain two factor methods (such as email 2fa), guard data may be provided by Steam + // for use in future authentication sessions to avoid triggering 2FA again (this works similarly to the old sentry file system). + // Do note that this guard data is also a JWT token and has an expiration date. + previouslyStoredGuardData = pollResponse.getNewGuardData(); + } + + // Logon to Steam with the access token we have received + // Note that we are using RefreshToken for logging on here + LogOnDetails details = new LogOnDetails(); + details.setUsername(pollResponse.getAccountName()); + details.setAccessToken(pollResponse.getRefreshToken()); + + // Set LoginID to a non-zero value if you have another client connected using the same account, + // the same private ip, and same public ip. + details.setLoginID(149); + + steamUser.logOn(details); + } catch (Exception e) { + // List a couple of exceptions that could be important to handle. + if (e instanceof AuthenticationException) { + System.err.println("An Authentication error has occurred. " + e.getMessage()); + } else if (e instanceof CancellationException) { + System.err.println("An Cancellation exception was raised. Usually means a timeout occurred. " + e.getMessage()); + } else { + System.err.println("An error occurred:" + e.getMessage()); + } + + steamUser.logOff(); + } + } + + private void onDisconnected(DisconnectedCallback callback) { + System.out.println("Disconnected from Steam. User initialized: " + callback.isUserInitiated()); + + // If the disconnection was not user initiated, we will retry connecting to steam again after a short delay. + if (callback.isUserInitiated()) { + isRunning = false; + } else { + try { + Thread.sleep(2000L); + steamClient.connect(); + } catch (InterruptedException e) { + System.err.println("An Interrupted exception occurred. " + e.getMessage()); + } + } + } + + private void onLoggedOn(LoggedOnCallback callback) { + if (callback.getResult() != EResult.OK) { + System.out.println("Unable to logon to Steam: " + callback.getResult() + " / " + callback.getExtendedResult()); + + isRunning = false; + return; + } + + System.out.println("Successfully logged on!"); + + // at this point, we'd be able to perform actions on Steam + steamApps.picsGetProductInfo(new PICSRequest(553850), null); // We'll use HellDivers 2 since it has "tm" in the title + + // steamUser.logOff(); + } + + private void onLoggedOff(LoggedOffCallback callback) { + System.out.println("Logged off of Steam: " + callback.getResult()); + + isRunning = false; + } + + private void onPicsChanges(PICSChangesCallback callback) { + System.out.println("TODO"); + } + + private void onPicsProduct(PICSProductInfoCallback callback) { + System.out.println("[PICSProductInfoCallback] apps: "); + for (var app : callback.getApps().entrySet()) { + printKeyValue(app.getValue().getKeyValues(), 1); + } + } + + private static void printKeyValue(KeyValue keyvalue, int depth) { + if (keyvalue.getChildren().isEmpty()) + System.out.println("[PICSProductInfoCallback] " + " ".repeat(depth * 4) + " " + keyvalue.getName() + ": " + keyvalue.getValue()); + else { + System.out.println("[PICSProductInfoCallback] " + " ".repeat(depth * 4) + " " + keyvalue.getName() + ":"); + for (KeyValue child : keyvalue.getChildren()) + printKeyValue(child, depth + 1); + } + } +} diff --git a/src/test/java/in/dragonbra/javasteam/steam/webapi/SteamDirectoryTest.java b/src/test/java/in/dragonbra/javasteam/steam/webapi/SteamDirectoryTest.java index aabdecd7..07912113 100644 --- a/src/test/java/in/dragonbra/javasteam/steam/webapi/SteamDirectoryTest.java +++ b/src/test/java/in/dragonbra/javasteam/steam/webapi/SteamDirectoryTest.java @@ -54,7 +54,7 @@ public void load() { server.shutdown(); } catch (Exception e) { - fail(e.getMessage()); + fail(e); } } } From b12d23fa10a1545090f6395f9d7c078b5e9d8ef3 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 23:45:12 -0500 Subject: [PATCH 11/21] Rename .java to .kt --- .../javasteam/types/{KVTextReader.java => KVTextReader.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/in/dragonbra/javasteam/types/{KVTextReader.java => KVTextReader.kt} (100%) diff --git a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.java b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt similarity index 100% rename from src/main/java/in/dragonbra/javasteam/types/KVTextReader.java rename to src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt From cca2263c7503400cc19aa9898f376aada853c3b1 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 17 Oct 2024 23:45:12 -0500 Subject: [PATCH 12/21] Fix encoding issues with PICSProductInfo. Port KVTextReader to kotlin while replacing PushbackInputStream with InputStreamReader --- .../handlers/steamapps/PICSProductInfo.kt | 12 +- .../dragonbra/javasteam/types/KVTextReader.kt | 249 ++++++++++-------- 2 files changed, 136 insertions(+), 125 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt index 53e3696f..d14d74aa 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt @@ -9,8 +9,6 @@ import `in`.dragonbra.javasteam.util.stream.MemoryStream import java.io.ByteArrayInputStream import java.io.IOException import java.net.URI -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets import java.util.* /** @@ -89,14 +87,10 @@ class PICSProductInfo : CallbackMsg { if (appInfo.hasBuffer() && !appInfo.buffer.isEmpty) { try { - // get the buffer as a string using the jvm's default charset. - // note: IDK why, but we have to encode this using the default charset - val bufferString = appInfo.buffer.toString(Charset.defaultCharset()) - // get the buffer as a byte array using utf-8 as a supported charset - val byteBuffer = bufferString.toByteArray(StandardCharsets.UTF_8) // we don't want to read the trailing null byte - val ms = MemoryStream(byteBuffer, 0, byteBuffer.size - 1) - keyValues.readAsText(ms) + MemoryStream(appInfo.buffer.toByteArray(), 0, appInfo.buffer.size() - 1).use { ms -> + keyValues.readAsText(ms) + } } catch (e: IOException) { throw IllegalArgumentException("failed to read buffer", e) } diff --git a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt index 05e0deaf..e5a8cd51 100644 --- a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt +++ b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt @@ -1,91 +1,123 @@ -package in.dragonbra.javasteam.types; +package `in`.dragonbra.javasteam.types -import in.dragonbra.javasteam.util.Passable; -import in.dragonbra.javasteam.util.Strings; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; +import `in`.dragonbra.javasteam.util.Passable +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.lang.IllegalStateException +import java.lang.StringBuilder +import java.nio.charset.StandardCharsets /** * @author lngtr * @since 2018-02-26 */ -public class KVTextReader extends PushbackInputStream { - - public static final Map ESCAPED_MAPPING; +class KVTextReader +internal constructor( + kv: KeyValue, + inputStream: InputStream, +) : InputStreamReader(inputStream, StandardCharsets.UTF_8) { + + companion object { + @JvmField + val ESCAPED_MAPPING = mapOf( + '\\' to '\\', + 'n' to '\n', + 'r' to '\r', + 't' to '\t', + // todo: any others? + ) + } - private final StringBuilder sb = new StringBuilder(128); + private val sb = StringBuilder(128) - static { - Map escapedMapping = new TreeMap<>(); + private var peekedChar: Int? = null - escapedMapping.put('n', '\n'); - escapedMapping.put('r', '\r'); - escapedMapping.put('t', '\t'); - escapedMapping.put('\\', '\\'); + // Mimics C# StreamReader 'Peek()' + val peek: Int + get() { + if (peekedChar == null) { + peekedChar = read() + } + return peekedChar ?: -1 + } - ESCAPED_MAPPING = Collections.unmodifiableMap(escapedMapping); - } + // Mimics C# StreamReader 'EndOfStream' + val endOfStream: Boolean + get() { + return try { + peek == -1 + } catch (_: IOException) { + true + } + } - KVTextReader(KeyValue kv, InputStream is) throws IOException { - super(is); - Passable wasQuoted = new Passable<>(false); - Passable wasConditional = new Passable<>(false); + init { + val wasQuoted = Passable(false) + val wasConditional = Passable(false) - KeyValue currentKey = kv; + var currentKey: KeyValue? = kv do { - String s = readToken(wasQuoted, wasConditional); + var s = readToken(wasQuoted, wasConditional) - if (Strings.isNullOrEmpty(s)) { - break; + if (s.isNullOrEmpty()) { + break } if (currentKey == null) { - currentKey = new KeyValue(s); + currentKey = KeyValue(s) } else { - currentKey.setName(s); + currentKey.name = s } - s = readToken(wasQuoted, wasConditional); + s = readToken(wasQuoted, wasConditional) if (wasConditional.getValue()) { // Now get the '{' - s = readToken(wasQuoted, wasConditional); + s = readToken(wasQuoted, wasConditional) } - if (s.startsWith("{") && !wasQuoted.getValue()) { + if (s != null && s.startsWith("{") && !wasQuoted.getValue()) { // header is valid so load the file - currentKey.recursiveLoadFromBuffer(this); + currentKey.recursiveLoadFromBuffer(this) } else { - throw new IllegalStateException("LoadFromBuffer: missing {"); + throw IllegalStateException("LoadFromBuffer: missing {") } - currentKey = null; - } while (!endOfStream()); + currentKey = null + } while (!endOfStream) } - private void eatWhiteSpace() throws IOException { - while (!endOfStream()) { - if (!Character.isWhitespace((char) peek())) { - break; + // override read() to peek the char. + override fun read(): Int { + if (peekedChar != null) { + val result = peekedChar!! + peekedChar = null + return result + } + return super.read() + } + + @Throws(IOException::class) + private fun eatWhiteSpace() { + while (!endOfStream) { + if (!peek.toChar().isWhitespace()) { + break } - read(); + read() } } - private boolean eatCPPComment() throws IOException { - if (!endOfStream()) { - char next = (char) peek(); + @Throws(IOException::class) + private fun eatCPPComment(): Boolean { + if (!endOfStream) { + val next = peek.toChar() if (next == '/') { - readLine(); - return true; + readLine() + return true /* * As came up in parsing the Dota 2 units.txt file, the reference (Valve) implementation * of the KV format considers a single forward slash to be sufficient to comment out the @@ -94,127 +126,112 @@ public class KVTextReader extends PushbackInputStream { */ } - return false; + return false } - return false; - } - private void readLine() throws IOException { - char c; - do { - c = (char) read(); - } while (c != '\n' && !endOfStream()); + return false } - private byte peek() throws IOException { - int p = read(); - if (p >= 0) { - unread(p); - } - return (byte) p; + @Throws(IOException::class) + private fun readLine() { + var c: Char + do { + c = read().toChar() + } while (c != '\n' && !endOfStream) } - public String readToken(Passable wasQuoted, Passable wasConditional) throws IOException { - wasQuoted.setValue(false); - wasConditional.setValue(false); + @Throws(IOException::class) + fun readToken(wasQuoted: Passable, wasConditional: Passable): String? { + wasQuoted.value = false + wasConditional.value = false while (true) { - eatWhiteSpace(); + eatWhiteSpace() - if (endOfStream()) { - return null; + if (endOfStream) { + return null } if (!eatCPPComment()) { - break; + break } } - if (endOfStream()) { - return null; + if (endOfStream) { + return null } - char next = (char) peek(); + var next = peek.toChar() if (next == '"') { - wasQuoted.setValue(true); + wasQuoted.value = true // " - read(); + read() - sb.setLength(0); - while (!endOfStream()) { - if (peek() == '\\') { - read(); + sb.clear() + while (!endOfStream) { + if (peek.toChar() == '\\') { + read() - char escapedChar = (char) read(); + val escapedChar = read().toChar() + var replacedChar = ESCAPED_MAPPING[escapedChar] ?: escapedChar - Character replacedChar = ESCAPED_MAPPING.get(escapedChar); - if (replacedChar == null) { - replacedChar = escapedChar; - } + sb.append(replacedChar) - sb.append((char) replacedChar); - - continue; + continue } - if (peek() == '"') { - break; + if (peek.toChar() == '"') { + break } - sb.append((char) read()); + sb.append(read().toChar()) } // " - read(); + read() - return sb.toString(); + return sb.toString() } if (next == '{' || next == '}') { - read(); - return String.valueOf(next); + read() + return next.toString() } - boolean bConditionalStart = false; - int count = 0; - sb.setLength(0); - - while (!endOfStream()) { - next = (char) peek(); + var bConditionalStart = false + val count = 0 + sb.clear() + while (!endOfStream) { + next = peek.toChar() if (next == '"' || next == '{' || next == '}') { - break; + break } if (next == '[') { - bConditionalStart = true; + bConditionalStart = true } if (next == ']' && bConditionalStart) { - wasConditional.setValue(true); + wasConditional.value = true } - if (Character.isWhitespace(next)) { - break; + if (next.isWhitespace()) { + break } + // count isn't used anymore, but still defined in SK. + @Suppress("KotlinConstantConditions") if (count < 1023) { - sb.append(next); + sb.append(next) } else { - throw new IOException("ReadToken overflow"); + throw IOException("ReadToken overflow") } - read(); + read() } - return sb.toString(); - } - private boolean endOfStream() { - try { - return peek() == -1; - } catch (IOException e) { - return true; - } + return sb.toString() } } From 6dce786e13891d6e2d619d36cfff1340be85385b Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Fri, 18 Oct 2024 00:13:08 -0500 Subject: [PATCH 13/21] KVTextReader can throw exceptions --- src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt index e5a8cd51..8114195f 100644 --- a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt +++ b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt @@ -13,6 +13,7 @@ import java.nio.charset.StandardCharsets * @since 2018-02-26 */ class KVTextReader +@Throws(IllegalStateException::class, IOException::class) internal constructor( kv: KeyValue, inputStream: InputStream, From c5f476bbf4ca067dfd5f58d2857753208ed1646b Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sat, 19 Oct 2024 20:41:29 -0500 Subject: [PATCH 14/21] Rename .java to .kt --- .../in/dragonbra/javasteam/util/{Passable.java => Passable.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/in/dragonbra/javasteam/util/{Passable.java => Passable.kt} (100%) diff --git a/src/main/java/in/dragonbra/javasteam/util/Passable.java b/src/main/java/in/dragonbra/javasteam/util/Passable.kt similarity index 100% rename from src/main/java/in/dragonbra/javasteam/util/Passable.java rename to src/main/java/in/dragonbra/javasteam/util/Passable.kt From e0f01e89bb275e76e5866d9e15c465eea2d60492 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sat, 19 Oct 2024 20:41:31 -0500 Subject: [PATCH 15/21] Port Passable to kotlin, update KVTextReader and QueryDetails kDoc. --- .../steammasterserver/QueryDetails.kt | 2 +- .../dragonbra/javasteam/types/KVTextReader.kt | 4 ++-- .../in/dragonbra/javasteam/util/Passable.kt | 23 ++----------------- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/QueryDetails.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/QueryDetails.kt index 0b0fab07..81e877b2 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/QueryDetails.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/QueryDetails.kt @@ -7,7 +7,7 @@ import java.net.InetAddress * Details used when performing a server list query. * * @param appID Gets or sets the AppID used when querying servers. - * @param filter Gets or sets the filter used for querying the master server. Check https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol for details on how the filter is structured. + * @param filter Gets or sets the filter used for querying the master server. Check [Master Server Query Protocol](https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol) for details on how the filter is structured. * @param region Gets or sets the region that servers will be returned from. * @param geoLocatedIP Gets or sets the IP address that will be GeoIP located. This is done to return servers closer to this location. * @param maxServers Gets or sets the maximum number of servers to return. diff --git a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt index 8114195f..99f77a4a 100644 --- a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt +++ b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt @@ -74,12 +74,12 @@ internal constructor( s = readToken(wasQuoted, wasConditional) - if (wasConditional.getValue()) { + if (wasConditional.value == true) { // Now get the '{' s = readToken(wasQuoted, wasConditional) } - if (s != null && s.startsWith("{") && !wasQuoted.getValue()) { + if (s != null && s.startsWith("{") && wasQuoted.value == false) { // header is valid so load the file currentKey.recursiveLoadFromBuffer(this) } else { diff --git a/src/main/java/in/dragonbra/javasteam/util/Passable.kt b/src/main/java/in/dragonbra/javasteam/util/Passable.kt index 9931e930..54469cfd 100644 --- a/src/main/java/in/dragonbra/javasteam/util/Passable.kt +++ b/src/main/java/in/dragonbra/javasteam/util/Passable.kt @@ -1,22 +1,3 @@ -package in.dragonbra.javasteam.util; +package `in`.dragonbra.javasteam.util -public class Passable { - - private T value; - - public Passable() { - this(null); - } - - public Passable(T value) { - this.value = value; - } - - public T getValue() { - return value; - } - - public void setValue(T value) { - this.value = value; - } -} +class Passable @JvmOverloads constructor(var value: T? = null) From 4a64936cd6e83f1205c3c327b3b09939edcb5130 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sat, 19 Oct 2024 20:42:32 -0500 Subject: [PATCH 16/21] Rename .java to .kt --- .../dragonbra/javasteam/util/{NetHelpers.java => NetHelpers.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/in/dragonbra/javasteam/util/{NetHelpers.java => NetHelpers.kt} (100%) diff --git a/src/main/java/in/dragonbra/javasteam/util/NetHelpers.java b/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt similarity index 100% rename from src/main/java/in/dragonbra/javasteam/util/NetHelpers.java rename to src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt From 5e8e9f40c744cb00b01f893d4b336c43b9602e43 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sat, 19 Oct 2024 20:42:33 -0500 Subject: [PATCH 17/21] Port NetHelpers to kotlin. Add missing methods. Add missing methods and tests. --- .../networking/steam3/TcpConnection.java | 8 +- .../networking/steam3/UdpConnection.java | 3 +- .../steammasterserver/SteamMasterServer.kt | 2 +- .../steam/handlers/steamuser/SteamUser.kt | 9 +- .../in/dragonbra/javasteam/util/NetHelpers.kt | 129 +++++++++++++----- .../javasteam/util/NetHelpersTest.java | 96 ++++++++++++- 6 files changed, 203 insertions(+), 44 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/networking/steam3/TcpConnection.java b/src/main/java/in/dragonbra/javasteam/networking/steam3/TcpConnection.java index e2a5fe8c..05fd3a1d 100644 --- a/src/main/java/in/dragonbra/javasteam/networking/steam3/TcpConnection.java +++ b/src/main/java/in/dragonbra/javasteam/networking/steam3/TcpConnection.java @@ -1,5 +1,6 @@ package in.dragonbra.javasteam.networking.steam3; +import in.dragonbra.javasteam.util.NetHelpers; import in.dragonbra.javasteam.util.log.LogManager; import in.dragonbra.javasteam.util.log.Logger; import in.dragonbra.javasteam.util.stream.BinaryReader; @@ -173,7 +174,12 @@ public InetAddress getLocalIP() { return null; } - return socket.getLocalAddress(); + try { + return NetHelpers.getLocalIP(socket); + } catch (Exception e) { + logger.debug("Socket exception trying to read bound IP: ", e); + return null; + } } } diff --git a/src/main/java/in/dragonbra/javasteam/networking/steam3/UdpConnection.java b/src/main/java/in/dragonbra/javasteam/networking/steam3/UdpConnection.java index c1ed7138..fbdb1698 100644 --- a/src/main/java/in/dragonbra/javasteam/networking/steam3/UdpConnection.java +++ b/src/main/java/in/dragonbra/javasteam/networking/steam3/UdpConnection.java @@ -3,6 +3,7 @@ import in.dragonbra.javasteam.enums.EUdpPacketType; import in.dragonbra.javasteam.generated.ChallengeData; import in.dragonbra.javasteam.generated.ConnectData; +import in.dragonbra.javasteam.util.NetHelpers; import in.dragonbra.javasteam.util.log.LogManager; import in.dragonbra.javasteam.util.log.Logger; import in.dragonbra.javasteam.util.stream.MemoryStream; @@ -161,7 +162,7 @@ public void send(byte[] data) { @Override public InetAddress getLocalIP() { - return sock.getLocalAddress(); + return NetHelpers.getLocalIP(sock); } @Override diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt index 127147ed..a8423f5d 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt @@ -32,7 +32,7 @@ class SteamMasterServer : ClientMsgHandler() { query.body.setAppId(details.appID) details.geoLocatedIP?.let { - query.body.setGeoLocationIp(NetHelpers.getIPAddress(it)) + query.body.geoLocationIp = NetHelpers.getIPAddress(it) } query.body.setFilterText(details.filter) 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 125ec7ad..0337d3a8 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 @@ -74,11 +74,10 @@ class SteamUser : ClientMsgHandler() { v4 = details.loginID!! }.build().also(logon.body::setObfuscatedPrivateIp) } else { - CMsgIPAddress.newBuilder().apply { - client.localIP?.let { localIp -> - v4 = NetHelpers.getIPAddress(localIp) xor MsgClientLogon.ObfuscationMask - } - }.build().also(logon.body::setObfuscatedPrivateIp) + client.localIP?.let { clientIP -> + val msgIpAddr = NetHelpers.getMsgIPAddress(clientIP) + logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(msgIpAddr) + } } // Legacy field, Steam client still sets it diff --git a/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt b/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt index 01b00e27..bce86eb3 100644 --- a/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt +++ b/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt @@ -1,56 +1,123 @@ -package in.dragonbra.javasteam.util; +package `in`.dragonbra.javasteam.util -import org.apache.commons.validator.routines.InetAddressValidator; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; +import com.google.protobuf.ByteString +import `in`.dragonbra.javasteam.generated.MsgClientLogon +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesBase.CMsgIPAddress +import org.apache.commons.validator.routines.InetAddressValidator +import java.lang.IllegalArgumentException +import java.net.DatagramSocket +import java.net.Inet6Address +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket +import java.nio.ByteBuffer /** * @author lngtr * @since 2018-02-22 */ -public class NetHelpers { +object NetHelpers { - public static InetAddress getIPAddress(int ipAddr) { - ByteBuffer b = ByteBuffer.allocate(4); - b.putInt(ipAddr); + @JvmStatic + @Throws(IllegalArgumentException::class) + fun getIPAddress(ipAddr: InetAddress): Int { + require(ipAddr.address.size == 4) { "only works with IPv4 addresses." } - byte[] result = b.array(); + val byteBuffer = ByteBuffer.wrap(ipAddr.address) + return byteBuffer.int and 0xFFFFFFFFL.toInt() + } - try { - return InetAddress.getByAddress(result); - } catch (UnknownHostException e) { - return null; + @JvmStatic + @Throws(Exception::class) + fun getLocalIP(socket: Socket): InetAddress? = getLocalIP(socket.localAddress) + + @JvmStatic + @Throws(IllegalArgumentException::class) + fun getLocalIP(datagramSocket: DatagramSocket): InetAddress? = getLocalIP(datagramSocket.localAddress) + + @JvmStatic + fun getLocalIP(endpoint: InetAddress?): InetAddress? { + if (endpoint == null || endpoint.address == InetAddress.getByName("0.0.0.0")) { + return null } + + return endpoint + } + + @JvmStatic + fun getIPAddress(ipAddr: Int): InetAddress { + val result = byteArrayOf( + ((ipAddr shr 24) and 0xFF).toByte(), + ((ipAddr shr 16) and 0xFF).toByte(), + ((ipAddr shr 8) and 0xFF).toByte(), + (ipAddr and 0xFF).toByte() + ) + return InetAddress.getByAddress(result) } - public static int getIPAddress(InetAddress ip) { - final ByteBuffer buff = ByteBuffer.wrap(ip.getAddress()); - return (int) (buff.getInt() & 0xFFFFFFFFL); + @JvmStatic + fun getIPAddress(ipAddr: CMsgIPAddress): InetAddress = if (ipAddr.hasV6()) { + InetAddress.getByAddress(ipAddr.v6.toByteArray()) + } else { + getIPAddress(ipAddr.v4) } + @JvmStatic + fun getMsgIPAddress(ipAddr: InetAddress): CMsgIPAddress { + val msgIpAddress = CMsgIPAddress.newBuilder() - public static InetSocketAddress tryParseIPEndPoint(String address) { - if (address == null) { - return null; + if (ipAddr is Inet6Address) { + msgIpAddress.v6 = ByteString.copyFrom(ipAddr.address) + } else { + msgIpAddress.v4 = getIPAddress(ipAddr) } - String[] split = address.split(":"); + return msgIpAddress.build() + } + + @JvmStatic + fun obfuscatePrivateIP(msgIpAddress: CMsgIPAddress): CMsgIPAddress { + val localIp = msgIpAddress.toBuilder() - if (!InetAddressValidator.getInstance().isValidInet4Address(split[0])) { - return null; + if (localIp.hasV6()) { + val v6Bytes = msgIpAddress.v6.toByteArray() + for (i in 0..15 step 4) { + v6Bytes[i] = (v6Bytes[i].toInt() xor 0x0D).toByte() + v6Bytes[i + 1] = (v6Bytes[i + 1].toInt() xor 0xF0).toByte() + v6Bytes[i + 2] = (v6Bytes[i + 2].toInt() xor 0xAD).toByte() + v6Bytes[i + 3] = (v6Bytes[i + 3].toInt() xor 0xBA).toByte() + } + localIp.v6 = ByteString.copyFrom(v6Bytes) + } else { + localIp.v4 = msgIpAddress.v4 xor MsgClientLogon.ObfuscationMask } + return localIp.build() + } + + @JvmStatic + fun tryParseIPEndPoint(stringValue: String): InetSocketAddress? { try { - if (split.length > 1) { - return new InetSocketAddress(split[0], Integer.parseInt(split[1])); + val split = stringValue.lastIndexOf(':') + if (split == -1) { + return null } - } catch (IllegalArgumentException exception) { - // no-op - } - return null; + var ip = stringValue.substring(0, split) + val port = stringValue.substring(split + 1).toInt() + + if (ip.startsWith("[") && ip.endsWith("]")) { + ip = ip.substring(1, ip.length - 1) // Remove the brackets + } + + val validator = InetAddressValidator.getInstance() + return if (validator.isValidInet4Address(ip) || validator.isValidInet6Address(ip)) { + InetSocketAddress(ip, port) + } else { + null + } + } catch (_: Exception) { + return null + } } } diff --git a/src/test/java/in/dragonbra/javasteam/util/NetHelpersTest.java b/src/test/java/in/dragonbra/javasteam/util/NetHelpersTest.java index 35444d23..00b4a5af 100644 --- a/src/test/java/in/dragonbra/javasteam/util/NetHelpersTest.java +++ b/src/test/java/in/dragonbra/javasteam/util/NetHelpersTest.java @@ -1,25 +1,111 @@ package in.dragonbra.javasteam.util; import in.dragonbra.javasteam.TestBase; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class NetHelpersTest extends TestBase { + private static final InetAddress loopbackV4 = InetAddress.getLoopbackAddress(); + private static final InetAddress loopbackV6; + + static { + try { + loopbackV6 = InetAddress.getByName("::1"); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + @Test + public void getMsgIPAddress() { + byte[] ipv6Bytes = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1 + }; + + Assertions.assertEquals(2130706433, NetHelpers.getMsgIPAddress(loopbackV4).getV4()); + Assertions.assertArrayEquals(ipv6Bytes, NetHelpers.getMsgIPAddress(loopbackV6).getV6().toByteArray()); + } + + @Test + public void getIPAddressFromMsg() { + var v4Addr = NetHelpers.getMsgIPAddress(loopbackV4); + Assertions.assertEquals(loopbackV4, NetHelpers.getIPAddress(v4Addr)); + + var v6Addr = NetHelpers.getMsgIPAddress(loopbackV6); + Assertions.assertEquals(loopbackV6, NetHelpers.getIPAddress(v6Addr)); + } + + @Test + public void getIPAddress() throws UnknownHostException { + Assertions.assertEquals(loopbackV4, NetHelpers.getIPAddress(2130706433)); + Assertions.assertEquals(InetAddress.getByName("0.0.0.1"), NetHelpers.getIPAddress(1)); + Assertions.assertEquals(InetAddress.getByName("255.255.255.255"), NetHelpers.getIPAddress(0xFFFFFFFF)); + Assertions.assertEquals(InetAddress.getByName("0.0.0.0"), NetHelpers.getIPAddress(0)); + } + + @Test + public void getIPAddressAsInt() throws UnknownHostException { + Assertions.assertEquals(2130706433, NetHelpers.getIPAddress(loopbackV4)); + Assertions.assertEquals(1, NetHelpers.getIPAddress(InetAddress.getByName("0.0.0.1"))); + Assertions.assertEquals(0xFFFFFFFF, NetHelpers.getIPAddress(InetAddress.getByName("255.255.255.255"))); + Assertions.assertEquals(-1062731775, NetHelpers.getIPAddress(InetAddress.getByName("192.168.0.1"))); + Assertions.assertEquals(167772161, NetHelpers.getIPAddress(InetAddress.getByName("10.0.0.1"))); + Assertions.assertEquals(-1408237567, NetHelpers.getIPAddress(InetAddress.getByName("172.16.0.1"))); + } + + @Test + public void obfuscatePrivateIP() { + byte[] ipv6Bytes = { + (byte) 0x0D, (byte) 0xF0, (byte) 0xAD, (byte) 0xBA, + (byte) 0x0D, (byte) 0xF0, (byte) 0xAD, (byte) 0xBA, + (byte) 0x0D, (byte) 0xF0, (byte) 0xAD, (byte) 0xBA, + (byte) 0x0D, (byte) 0xF0, (byte) 0xAD, (byte) 0xBB + }; + + var v4Addr = NetHelpers.getMsgIPAddress(loopbackV4); + Assertions.assertEquals(3316510732L, NetHelpers.obfuscatePrivateIP(v4Addr).getV4() & 0xFFFFFFFFL); + var v6Addr = NetHelpers.getMsgIPAddress(loopbackV6); + Assertions.assertArrayEquals(ipv6Bytes, NetHelpers.obfuscatePrivateIP(v6Addr).getV6().toByteArray()); + } + + @Test + public void tryParseIPEndPoint() throws UnknownHostException { + var v4Addr = NetHelpers.tryParseIPEndPoint("127.0.0.1:1337"); + Assertions.assertNotNull(v4Addr); + Assertions.assertEquals(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 1337), v4Addr); + + var v6Addr = NetHelpers.tryParseIPEndPoint("[::1]:1337"); + Assertions.assertNotNull(v6Addr); + Assertions.assertEquals(new InetSocketAddress(loopbackV6, 1337), v6Addr); + + var v6Addr2 = NetHelpers.tryParseIPEndPoint("::1:1337"); + Assertions.assertNotNull(v6Addr2); + Assertions.assertEquals(new InetSocketAddress(loopbackV6, 1337), v6Addr2); + } + + @Test + public void fail_getIPAddressAsLong_If_V6() { + Assertions.assertThrows(IllegalArgumentException.class, () -> NetHelpers.getIPAddress(loopbackV6)); + } + @Test public void testIntToAddress() throws UnknownHostException { int ipAddress = -1560361686; InetAddress address = NetHelpers.getIPAddress(ipAddress); - assertEquals(InetAddress.getByName("162.254.197.42"), address); + Assertions.assertEquals(InetAddress.getByName("162.254.197.42"), address); } @Test public void testAddressToInt() throws UnknownHostException { int address = NetHelpers.getIPAddress(InetAddress.getByName("162.254.197.42")); - assertEquals(-1560361686, address); + Assertions.assertEquals(-1560361686, address); } -} \ No newline at end of file +} From d630be574bc266a3b07416f01e8e9c31a866b521 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sun, 20 Oct 2024 02:09:28 -0500 Subject: [PATCH 18/21] Minor kotlin code cleanup. BinaryReader.readNullTermUtf8String breaks instead of throwing, update it's test. Use Property Access Syntax for fields in kotlin. Don't throw in handlers. Instead, log it to 'error' since some callbacks can have valid data or continue with an empty list of the data that failed. KeyValue.tryReadAsBinary can throw IllegalArgumentException and IOException. Use US_ASCII formatting for LegacyGameKeyCallback.key MemoryStream can take a ByteString constructor. SteamAuthentication.getPasswordRSAPublicKey takes non-null accountName. SteamAuthentication.beginAuthSessionViaCredentials takes non-null authSessionDetails. Setters that work with ip related values use NetHelpers functions. WebSocketConnection.getLocalIP returns 0.0.0.0 InetAddress. --- .../networking/steam3/WebSocketConnection.kt | 3 +- .../AccessTokenGenerateResult.kt | 6 +- .../steam/authentication/AuthSession.kt | 81 ++++++++-------- .../authentication/AuthSessionDetails.kt | 5 +- .../authentication/CredentialsAuthSession.kt | 6 +- .../steam/authentication/QrAuthSession.kt | 4 +- .../authentication/SteamAuthentication.kt | 10 +- .../steam/handlers/ClientMsgHandler.kt | 2 +- .../handlers/steamapps/PICSProductInfo.kt | 10 +- .../steam/handlers/steamapps/SteamApps.kt | 68 +++++++------- .../callback/GuestPassListCallback.kt | 7 +- .../callback/LegacyGameKeyCallback.kt | 3 +- .../callback/PICSProductInfoCallback.kt | 5 +- .../callback/PurchaseResponseCallback.kt | 10 +- .../steamapps/callback/VACStatusCallback.kt | 10 +- .../steam/handlers/steamcloud/SteamCloud.kt | 25 ++--- .../handlers/steamfriends/SteamFriends.kt | 92 ++++++++++--------- .../callback/ChatEnterCallback.kt | 24 ++--- .../callback/ChatMemberInfoCallback.kt | 16 +++- .../SteamGameCoordinator.kt | 12 +-- .../steamgameserver/SteamGameServer.kt | 55 ++++++----- .../steammasterserver/SteamMasterServer.kt | 19 ++-- .../steamnetworking/SteamNetworking.kt | 9 +- .../callback/CommentNotificationsCallback.kt | 7 +- .../callback/ItemAnnouncementsCallback.kt | 3 +- .../OfflineMessageNotificationCallback.kt | 7 +- .../callback/UserNotificationsCallback.kt | 3 +- .../steamscreenshots/SteamScreenshots.kt | 25 ++--- .../SteamUnifiedMessages.kt | 10 +- .../callback/ServiceMethodNotification.kt | 2 +- .../steam/handlers/steamuser/SteamUser.kt | 72 ++++++++------- .../steamuser/callback/LoggedOnCallback.kt | 17 ++-- .../callback/MarketingMessageCallback.kt | 18 ++-- .../steamuserstats/LeaderboardEntry.kt | 9 +- .../handlers/steamuserstats/SteamUserStats.kt | 58 ++++++------ .../handlers/steamworkshop/SteamWorkshop.kt | 11 ++- .../steam/steamclient/SteamClient.kt | 2 +- .../javasteam/types/AsyncJobMultiple.kt | 2 +- .../dragonbra/javasteam/types/KeyValue.java | 5 +- .../javasteam/util/stream/BinaryReader.java | 15 ++- .../javasteam/util/stream/MemoryStream.java | 15 ++- .../util/stream/BinaryReaderTest.java | 9 +- 42 files changed, 422 insertions(+), 350 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt b/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt index 37929ef6..5497ff10 100644 --- a/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt +++ b/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt @@ -54,8 +54,7 @@ class WebSocketConnection : } } - // TODO get local ip? SK uses `IPAddress.None` here. - override fun getLocalIP(): InetAddress? = null + override fun getLocalIP(): InetAddress? = InetAddress.getByAddress(byteArrayOf(0, 0, 0, 0)) override fun getCurrentEndPoint(): InetSocketAddress? = socketEndPoint diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt index 0ff3beb0..b1a81bda 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AccessTokenGenerateResult.kt @@ -1,14 +1,12 @@ package `in`.dragonbra.javasteam.steam.authentication -import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_AccessToken_GenerateForApp_Response import `in`.dragonbra.javasteam.steam.handlers.steamuser.LogOnDetails /** * Represents access token generation result. */ -class AccessTokenGenerateResult( - response: SteammessagesAuthSteamclient.CAuthentication_AccessToken_GenerateForApp_Response.Builder, -) { +class AccessTokenGenerateResult(response: CAuthentication_AccessToken_GenerateForApp_Response.Builder) { /** * New refresh token. diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt index 22f5e490..f0523f0a 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSession.kt @@ -2,7 +2,10 @@ package `in`.dragonbra.javasteam.steam.authentication import com.google.protobuf.ByteString import `in`.dragonbra.javasteam.enums.EResult -import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_AllowedConfirmation +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Request +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Response +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.EAuthSessionGuardType import `in`.dragonbra.javasteam.rpc.service.Authentication import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -11,21 +14,13 @@ import kotlinx.coroutines.future.await import kotlinx.coroutines.future.future import java.util.concurrent.CompletableFuture -//region Aliases -typealias AllowedConfirmations = SteammessagesAuthSteamclient.CAuthentication_AllowedConfirmation -typealias AuthSessionStatusRequest = SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Request -typealias AuthSessionStatusResponse = SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Response -typealias AuthSessionStatusResponseBuilder = SteammessagesAuthSteamclient.CAuthentication_PollAuthSessionStatus_Response.Builder -typealias SessionGuardType = SteammessagesAuthSteamclient.EAuthSessionGuardType -//endregion - /** * Represents an authentication session which can be used to finish the authentication and get access tokens. * * @param authentication Unified messages class for Authentication related messages, see [Authentication]. * @param authenticator Authenticator object which will be used to handle 2-factor authentication if necessary. - * @param clientId Unique identifier of requestor, also used for routing, portion of QR code. - * @param requestId Unique request ID to be presented by requestor at poll time. + * @param clientID Unique identifier of requestor, also used for routing, portion of QR code. + * @param requestID Unique request ID to be presented by requestor at poll time. * @param allowedConfirmations Confirmation types that will be able to confirm the request. * @param pollingInterval Refresh interval with which requestor should call PollAuthSessionStatus. */ @@ -33,9 +28,9 @@ typealias SessionGuardType = SteammessagesAuthSteamclient.EAuthSessionGuardType open class AuthSession( val authentication: SteamAuthentication, val authenticator: IAuthenticator?, - var clientId: Long, // Should be 'private set' - val requestId: ByteArray, - var allowedConfirmations: List, + var clientID: Long, // Should be 'private set' + val requestID: ByteArray, + var allowedConfirmations: List, val pollingInterval: Float, ) { @@ -67,14 +62,14 @@ open class AuthSession( var preferredConfirmation = allowedConfirmations.firstOrNull() ?: throw AuthenticationException("There are no allowed confirmations") - if (preferredConfirmation.confirmationType == SessionGuardType.k_EAuthSessionGuardType_Unknown) { + if (preferredConfirmation.confirmationType == EAuthSessionGuardType.k_EAuthSessionGuardType_Unknown) { throw AuthenticationException("There are no allowed confirmations") } // If an authenticator is provided and the device confirmation is available, allow consumers to choose whether they want to // simply poll until confirmation is accepted, or whether they want to fall back to the next preferred confirmation type. authenticator?.let { auth -> - if (preferredConfirmation.confirmationType == SessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation) { + if (preferredConfirmation.confirmationType == EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation) { val prefersToPollForConfirmation = auth.acceptDeviceConfirmation().await() if (!prefersToPollForConfirmation) { @@ -92,14 +87,15 @@ open class AuthSession( var pollLoop = false when (preferredConfirmation.confirmationType) { - SessionGuardType.k_EAuthSessionGuardType_None -> Unit // // No steam guard - SessionGuardType.k_EAuthSessionGuardType_EmailCode, - SessionGuardType.k_EAuthSessionGuardType_DeviceCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_None -> Unit // // No steam guard + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode, -> { // 2-factor code from the authenticator app or sent to an email handleCodeAuth(preferredConfirmation) } - SessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation -> { + + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation -> { // This is a prompt that appears in the Steam mobile app pollLoop = true } @@ -118,7 +114,7 @@ open class AuthSession( } @Throws(AuthenticationException::class) - private suspend fun handleCodeAuth(preferredConfirmation: AllowedConfirmations) { + private suspend fun handleCodeAuth(preferredConfirmation: CAuthentication_AllowedConfirmation) { val credentialsAuthSession = this as? CredentialsAuthSession ?: throw AuthenticationException( "Got ${preferredConfirmation.confirmationType} confirmation type in a session " + @@ -132,8 +128,8 @@ open class AuthSession( } val expectedInvalidCodeResult = when (preferredConfirmation.confirmationType) { - SessionGuardType.k_EAuthSessionGuardType_EmailCode -> EResult.InvalidLoginAuthCode - SessionGuardType.k_EAuthSessionGuardType_DeviceCode -> EResult.TwoFactorCodeMismatch + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode -> EResult.InvalidLoginAuthCode + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode -> EResult.TwoFactorCodeMismatch else -> throw AuthenticationException("\'${preferredConfirmation.confirmationType}\' not implemented") } @@ -143,13 +139,15 @@ open class AuthSession( while (waitingForValidCode) { try { val task = when (preferredConfirmation.confirmationType) { - SessionGuardType.k_EAuthSessionGuardType_EmailCode -> { + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode -> { val msg = preferredConfirmation.associatedMessage authenticator.getEmailCode(msg, previousCodeWasIncorrect).await() } - SessionGuardType.k_EAuthSessionGuardType_DeviceCode -> { + + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode -> { authenticator.getDeviceCode(previousCodeWasIncorrect).await() } + else -> throw AuthenticationException() } @@ -183,9 +181,10 @@ open class AuthSession( */ @Throws(AuthenticationException::class) fun pollAuthSessionStatus(): AuthPollResult? { - val request = AuthSessionStatusRequest.newBuilder() - request.clientId = clientId - request.requestId = ByteString.copyFrom(requestId) + val request = CAuthentication_PollAuthSessionStatus_Request.newBuilder().apply { + clientId = clientID + requestId = ByteString.copyFrom(requestID) + } val message = authentication.authenticationService.pollAuthSessionStatus(request.build()).runBlock() @@ -194,11 +193,11 @@ open class AuthSession( throw AuthenticationException("Failed to poll status", message.result) } - val response: AuthSessionStatusResponseBuilder = - message.getDeserializedResponse(AuthSessionStatusResponse::class.java) + val response: CAuthentication_PollAuthSessionStatus_Response.Builder = + message.getDeserializedResponse(CAuthentication_PollAuthSessionStatus_Response::class.java) if (response.newClientId > 0) { - clientId = response.newClientId + clientID = response.newClientId } handlePollAuthSessionStatusResponse(response) @@ -210,9 +209,9 @@ open class AuthSession( return null } - internal open fun handlePollAuthSessionStatusResponse(response: AuthSessionStatusResponseBuilder) { + internal open fun handlePollAuthSessionStatusResponse(response: CAuthentication_PollAuthSessionStatus_Response.Builder) { if (response.newClientId != 0L) { - clientId = response.newClientId + clientID = response.newClientId } } @@ -221,15 +220,15 @@ open class AuthSession( * @param confirmations the list of confirmations * @return a sorted list of confirmations */ - private fun sortConfirmations(confirmations: List): List { + private fun sortConfirmations(confirmations: List): List { val preferredConfirmationTypes = arrayOf( - SessionGuardType.k_EAuthSessionGuardType_None, - SessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation, - SessionGuardType.k_EAuthSessionGuardType_DeviceCode, - SessionGuardType.k_EAuthSessionGuardType_EmailCode, - SessionGuardType.k_EAuthSessionGuardType_EmailConfirmation, - SessionGuardType.k_EAuthSessionGuardType_MachineToken, - SessionGuardType.k_EAuthSessionGuardType_Unknown + EAuthSessionGuardType.k_EAuthSessionGuardType_None, + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceConfirmation, + EAuthSessionGuardType.k_EAuthSessionGuardType_DeviceCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailCode, + EAuthSessionGuardType.k_EAuthSessionGuardType_EmailConfirmation, + EAuthSessionGuardType.k_EAuthSessionGuardType_MachineToken, + EAuthSessionGuardType.k_EAuthSessionGuardType_Unknown ) val sortOrder = preferredConfirmationTypes.withIndex().associate { (index, value) -> value to index } diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSessionDetails.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSessionDetails.kt index 19773942..8c27cac6 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSessionDetails.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/AuthSessionDetails.kt @@ -1,7 +1,7 @@ package `in`.dragonbra.javasteam.steam.authentication import `in`.dragonbra.javasteam.enums.EOSType -import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesAuthSteamclient.EAuthTokenPlatformType import `in`.dragonbra.javasteam.util.Utils /** @@ -29,8 +29,7 @@ class AuthSessionDetails { * Gets or sets the platform type that the login will be performed for. */ @JvmField - var platformType: SteammessagesAuthSteamclient.EAuthTokenPlatformType = - SteammessagesAuthSteamclient.EAuthTokenPlatformType.k_EAuthTokenPlatformType_SteamClient + var platformType: EAuthTokenPlatformType = EAuthTokenPlatformType.k_EAuthTokenPlatformType_SteamClient /** * Gets or Sets the client operating system type. diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt index 0b53875a..f89b3cc8 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/CredentialsAuthSession.kt @@ -17,8 +17,8 @@ class CredentialsAuthSession( ) : AuthSession( authentication = authentication, authenticator = authenticator, - clientId = response.clientId, - requestId = response.requestId.toByteArray(), + clientID = response.clientId, + requestID = response.requestId.toByteArray(), allowedConfirmations = response.allowedConfirmationsList, pollingInterval = response.interval ) { @@ -35,7 +35,7 @@ class CredentialsAuthSession( @Throws(AuthenticationException::class) fun sendSteamGuardCode(code: String?, codeType: EAuthSessionGuardType?) { val request = CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request.newBuilder().apply { - this.clientId = this@CredentialsAuthSession.clientId // Could rename this to clientID instead. + this.clientId = clientID this.steamid = steamID.convertToUInt64() this.code = code this.codeType = codeType diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt index 4d584179..8010eafc 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/QrAuthSession.kt @@ -13,8 +13,8 @@ class QrAuthSession( ) : AuthSession( authentication = authentication, authenticator = authenticator, - clientId = response.clientId, - requestId = response.requestId.toByteArray(), + clientID = response.clientId, + requestID = response.requestId.toByteArray(), allowedConfirmations = response.allowedConfirmationsList, pollingInterval = response.interval ) { diff --git a/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt b/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt index e86320be..066fed01 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/authentication/SteamAuthentication.kt @@ -53,7 +53,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { * @throws AuthenticationException if getting the public key failed. */ @Throws(AuthenticationException::class) - private fun getPasswordRSAPublicKey(accountName: String?): CAuthentication_GetPasswordRSAPublicKey_Response.Builder { + private fun getPasswordRSAPublicKey(accountName: String): CAuthentication_GetPasswordRSAPublicKey_Response.Builder { val request = CAuthentication_GetPasswordRSAPublicKey_Request.newBuilder().apply { this.accountName = accountName } @@ -145,11 +145,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { * @return [CredentialsAuthSession] */ @Throws(AuthenticationException::class) - fun beginAuthSessionViaCredentials(authSessionDetails: AuthSessionDetails?): CredentialsAuthSession { - if (authSessionDetails == null) { - throw IllegalArgumentException("authSessionDetails is null") - } - + fun beginAuthSessionViaCredentials(authSessionDetails: AuthSessionDetails): CredentialsAuthSession { if (authSessionDetails.username.isNullOrEmpty() || authSessionDetails.password.isNullOrEmpty()) { throw IllegalArgumentException( "BeginAuthSessionViaCredentials requires a username and password to be set in authSessionDetails." @@ -161,7 +157,7 @@ class SteamAuthentication(private val steamClient: SteamClient) { } // Encrypt the password - val passwordRSAPublicKey = getPasswordRSAPublicKey(authSessionDetails.username) + val passwordRSAPublicKey = getPasswordRSAPublicKey(authSessionDetails.username!!) val publicModulus = BigInteger(passwordRSAPublicKey.publickeyMod, 16) val publicExponent = BigInteger(passwordRSAPublicKey.publickeyExp, 16) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/ClientMsgHandler.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/ClientMsgHandler.kt index 5b4d68e5..93f65f69 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/ClientMsgHandler.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/ClientMsgHandler.kt @@ -27,7 +27,7 @@ abstract class ClientMsgHandler { */ protected var isExpectDisconnection: Boolean get() = client.isExpectDisconnection - protected set(expectDisconnection) { + set(expectDisconnection) { client.isExpectDisconnection = expectDisconnection } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt index d14d74aa..59c0ab0b 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt @@ -4,6 +4,7 @@ import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverA import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.KeyValue import `in`.dragonbra.javasteam.util.Strings +import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.stream.BinaryReader import `in`.dragonbra.javasteam.util.stream.MemoryStream import java.io.ByteArrayInputStream @@ -17,6 +18,10 @@ import java.util.* @Suppress("MemberVisibilityCanBePrivate", "unused") class PICSProductInfo : CallbackMsg { + companion object { + private val logger = LogManager.getLogger(PICSProductInfo::class.java) + } + /** * Gets the ID of the app or package. */ @@ -92,7 +97,7 @@ class PICSProductInfo : CallbackMsg { keyValues.readAsText(ms) } } catch (e: IOException) { - throw IllegalArgumentException("failed to read buffer", e) + logger.error("failed to read buffer", e) } } @@ -112,6 +117,7 @@ class PICSProductInfo : CallbackMsg { if (packageInfo.hasBuffer()) { // we don't want to read the trailing null byte try { + // TODO memory stream like SK? BinaryReader(ByteArrayInputStream(packageInfo.buffer.toByteArray())).use { br -> // steamclient checks this value == 1 before it attempts to read the KV from the buffer // see: CPackageInfo::UpdateFromBuffer(CSHA const&,uint,CUtlBuffer &) @@ -120,7 +126,7 @@ class PICSProductInfo : CallbackMsg { keyValues.tryReadAsBinary(br) } } catch (e: IOException) { - throw IllegalArgumentException("failed to read buffer", e) + logger.error("failed to read buffer", e) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/SteamApps.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/SteamApps.kt index 87c804ca..d3b15272 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/SteamApps.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/SteamApps.kt @@ -51,9 +51,9 @@ class SteamApps : ClientMsgHandler() { CMsgClientGetAppOwnershipTicket::class.java, EMsg.ClientGetAppOwnershipTicket ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() - body.setAppId(appId) + body.appId = appId } client.send(request) @@ -74,10 +74,10 @@ class SteamApps : ClientMsgHandler() { CMsgClientGetDepotDecryptionKey::class.java, EMsg.ClientGetDepotDecryptionKey ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() - body.setDepotId(depotId) - body.setAppId(appId) + body.depotId = depotId + body.appId = appId } client.send(request) @@ -95,8 +95,8 @@ class SteamApps : ClientMsgHandler() { */ @JvmOverloads fun picsGetAccessTokens(app: Int? = null, `package`: Int? = null): AsyncJobSingle { - val apps = mutableListOf().apply { app?.let { add(it) } } - val packages = mutableListOf().apply { `package`?.let { add(it) } } + val apps = listOfNotNull(app) + val packages = listOfNotNull(`package`) return picsGetAccessTokens(apps, packages) } @@ -114,7 +114,7 @@ class SteamApps : ClientMsgHandler() { CMsgClientPICSAccessTokenRequest::class.java, EMsg.ClientPICSAccessTokenRequest ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() body.addAllAppids(appIds) body.addAllPackageids(packageIds) @@ -144,11 +144,11 @@ class SteamApps : ClientMsgHandler() { CMsgClientPICSChangesSinceRequest::class.java, EMsg.ClientPICSChangesSinceRequest ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() - body.setSinceChangeNumber(lastChangeNumber) - body.setSendAppInfoChanges(sendAppChangeList) - body.setSendPackageInfoChanges(sendPackageChangelist) + body.sinceChangeNumber = lastChangeNumber + body.sendAppInfoChanges = sendAppChangeList + body.sendPackageInfoChanges = sendPackageChangelist } client.send(request) @@ -171,8 +171,8 @@ class SteamApps : ClientMsgHandler() { `package`: PICSRequest? = null, metaDataOnly: Boolean = false, ): AsyncJobMultiple { - val apps = mutableListOf().apply { app?.let { add(it) } } - val packages = mutableListOf().apply { `package`?.let { add(it) } } + val apps = listOfNotNull(app) + val packages = listOfNotNull(`package`) return picsGetProductInfo(apps, packages, metaDataOnly) } @@ -196,28 +196,28 @@ class SteamApps : ClientMsgHandler() { CMsgClientPICSProductInfoRequest::class.java, EMsg.ClientPICSProductInfoRequest ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() apps.forEach { appRequest -> - val appInfo = CMsgClientPICSProductInfoRequest.AppInfo.newBuilder() - - appInfo.setAccessToken(appRequest.accessToken) - appInfo.setAppid(appRequest.id) - appInfo.setOnlyPublicObsolete(false) + val appInfo = CMsgClientPICSProductInfoRequest.AppInfo.newBuilder().apply { + accessToken = appRequest.accessToken + appid = appRequest.id + onlyPublicObsolete = false + } body.addApps(appInfo) } packages.forEach { packageRequest -> - val packageInfo = CMsgClientPICSProductInfoRequest.PackageInfo.newBuilder() - - packageInfo.setAccessToken(packageRequest.accessToken) - packageInfo.setPackageid(packageRequest.id) + val packageInfo = CMsgClientPICSProductInfoRequest.PackageInfo.newBuilder().apply { + accessToken = packageRequest.accessToken + packageid = packageRequest.id + } body.addPackages(packageInfo) } - body.setMetaDataOnly(metaDataOnly) + body.metaDataOnly = metaDataOnly } client.send(request) @@ -239,11 +239,11 @@ class SteamApps : ClientMsgHandler() { CMsgClientGetCDNAuthToken::class.java, EMsg.ClientGetCDNAuthToken ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() - body.setAppId(app) - body.setDepotId(depot) - body.setHostName(hostName) + body.appId = app + body.depotId = depot + body.hostName = hostName } client.send(request) @@ -272,7 +272,7 @@ class SteamApps : ClientMsgHandler() { CMsgClientRequestFreeLicense::class.java, EMsg.ClientRequestFreeLicense ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() body.addAllAppids(apps) } @@ -295,10 +295,10 @@ class SteamApps : ClientMsgHandler() { CMsgClientCheckAppBetaPassword::class.java, EMsg.ClientCheckAppBetaPassword ).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = client.getNextJobID() - body.setAppId(app) - body.setBetapassword(password) + body.appId = app + body.betapassword = password } client.send(request) @@ -314,7 +314,7 @@ class SteamApps : ClientMsgHandler() { */ fun getLegacyGameKey(appId: Int): AsyncJobSingle { val request = ClientMsg(MsgClientGetLegacyGameKey::class.java).apply { - setSourceJobID(client.getNextJobID()) + sourceJobID = (client.getNextJobID()) body.appId = appId } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/GuestPassListCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/GuestPassListCallback.kt index 26733181..0fcfec5f 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/GuestPassListCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/GuestPassListCallback.kt @@ -6,6 +6,7 @@ import `in`.dragonbra.javasteam.enums.EResult import `in`.dragonbra.javasteam.generated.MsgClientUpdateGuestPassesList import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.KeyValue +import `in`.dragonbra.javasteam.util.log.LogManager import java.io.IOException /** @@ -13,6 +14,10 @@ import java.io.IOException */ class GuestPassListCallback(packetMsg: IPacketMsg) : CallbackMsg() { + companion object { + private val logger = LogManager.getLogger(GuestPassListCallback::class.java) + } + /** * Gets the result of the operation. */ @@ -49,7 +54,7 @@ class GuestPassListCallback(packetMsg: IPacketMsg) : CallbackMsg() { tempList.add(kv) } } catch (e: IOException) { - throw IllegalArgumentException("failed to read guest passes", e) + logger.error("failed to read guest passes", e) } guestPasses = tempList.toList() diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/LegacyGameKeyCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/LegacyGameKeyCallback.kt index e3ea1a01..dfd44a26 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/LegacyGameKeyCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/LegacyGameKeyCallback.kt @@ -6,6 +6,7 @@ import `in`.dragonbra.javasteam.enums.EResult import `in`.dragonbra.javasteam.generated.MsgClientGetLegacyGameKeyResponse import `in`.dragonbra.javasteam.steam.handlers.steamapps.SteamApps import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg +import java.nio.charset.StandardCharsets /** * This callback is received in response to calling [SteamApps.getLegacyGameKey]. @@ -38,7 +39,7 @@ class LegacyGameKeyCallback(packetMsg: IPacketMsg) : CallbackMsg() { if (msg.length > 0) { val length: Int = msg.length - 1 val payload = keyResponse.payload.toByteArray() - key = String(payload, 0, length) + key = String(payload, 0, length, StandardCharsets.US_ASCII) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PICSProductInfoCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PICSProductInfoCallback.kt index cf888a84..ab92651f 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PICSProductInfoCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PICSProductInfoCallback.kt @@ -54,8 +54,9 @@ class PICSProductInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { isMetaDataOnly = msg.metaDataOnly isResponsePending = msg.responsePending - unknownPackages = Collections.unmodifiableList(msg.unknownPackageidsList) - unknownApps = Collections.unmodifiableList(msg.unknownAppidsList) + + unknownPackages = msg.unknownPackageidsList + unknownApps = msg.unknownAppidsList apps = msg.appsList.associate { it.appid to PICSProductInfo(msg, it) } packages = msg.packagesList.associate { it.packageid to PICSProductInfo(it) } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PurchaseResponseCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PurchaseResponseCallback.kt index 1b95344a..3390a66c 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PurchaseResponseCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/PurchaseResponseCallback.kt @@ -7,8 +7,8 @@ import `in`.dragonbra.javasteam.enums.EResult import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserver2.CMsgClientPurchaseResponse import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.KeyValue +import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.stream.MemoryStream -import java.io.IOException /** * This callback is received in a response to activating a Steam key. @@ -16,6 +16,10 @@ import java.io.IOException @Suppress("MemberVisibilityCanBePrivate") class PurchaseResponseCallback(packetMsg: IPacketMsg) : CallbackMsg() { + companion object { + private val logger = LogManager.getLogger(PurchaseResponseCallback::class.java) + } + /** * Gets Result of the operation */ @@ -47,8 +51,8 @@ class PurchaseResponseCallback(packetMsg: IPacketMsg) : CallbackMsg() { try { val ms = MemoryStream(msg.purchaseReceiptInfo.toByteArray()) purchaseReceiptInfo.tryReadAsBinary(ms) - } catch (exception: IOException) { - throw IllegalArgumentException("input stream is null") + } catch (e: Exception) { + logger.error("Failed to read purchase receipt info", e) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/VACStatusCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/VACStatusCallback.kt index 349ff59d..7ee500c0 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/VACStatusCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/callback/VACStatusCallback.kt @@ -4,9 +4,9 @@ import `in`.dragonbra.javasteam.base.ClientMsg import `in`.dragonbra.javasteam.base.IPacketMsg import `in`.dragonbra.javasteam.generated.MsgClientVACBanStatus import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg +import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.stream.BinaryReader import java.io.ByteArrayInputStream -import java.io.IOException import java.util.* /** @@ -14,6 +14,10 @@ import java.util.* */ class VACStatusCallback(packetMsg: IPacketMsg) : CallbackMsg() { + companion object { + private val logger = LogManager.getLogger(VACStatusCallback::class.java) + } + /** * Gets a list of VAC banned apps the client is banned from. */ @@ -31,8 +35,8 @@ class VACStatusCallback(packetMsg: IPacketMsg) : CallbackMsg() { tempList.add(br.readInt()) } } - } catch (e: IOException) { - throw IllegalArgumentException("failed to read bans", e) + } catch (e: Exception) { + logger.error("failed to read bans", e) } bannedApps = tempList.toList() 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 4e8efa19..7f672918 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 @@ -30,10 +30,11 @@ class SteamCloud : ClientMsgHandler() { val request = ClientMsgProtobuf( CMsgClientUFSGetUGCDetails::class.java, EMsg.ClientUFSGetUGCDetails - ) - request.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - request.body.setHcontent(ugcId.value) + body.hcontent = ugcId.value + } client.send(request) @@ -52,11 +53,12 @@ class SteamCloud : ClientMsgHandler() { val request = ClientMsgProtobuf( CMsgClientUFSGetSingleFileInfo::class.java, EMsg.ClientUFSGetSingleFileInfo - ) - request.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - request.body.setAppId(appId) - request.body.setFileName(filename) + body.appId = appId + body.fileName = filename + } client.send(request) @@ -75,11 +77,12 @@ class SteamCloud : ClientMsgHandler() { val request = ClientMsgProtobuf( CMsgClientUFSShareFile::class.java, EMsg.ClientUFSShareFile - ) - request.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - request.body.setAppId(appId) - request.body.setFileName(filename) + body.appId = appId + body.fileName = filename + } client.send(request) 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 0bd5080c..4624f1f0 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 @@ -152,8 +152,8 @@ class SteamFriends : ClientMsgHandler() { CMsgClientChangeStatus::class.java, EMsg.ClientChangeStatus ).apply { - body.setPersonaState(cache.localUser.personaState.code()) - body.setPlayerName(name) + body.personaState = cache.localUser.personaState.code() + body.playerName = name }.also(client::send) } @@ -176,8 +176,8 @@ class SteamFriends : ClientMsgHandler() { CMsgClientChangeStatus::class.java, EMsg.ClientChangeStatus ).apply { - body.setPersonaState(state.code()) - body.setPersonaSetByUser(true) + body.personaState = state.code() + body.personaSetByUser = true }.also(client::send) } @@ -190,8 +190,8 @@ class SteamFriends : ClientMsgHandler() { CMsgClientChangeStatus::class.java, EMsg.ClientChangeStatus ).apply { - body.setPersonaSetByUser(true) - body.setPersonaStateFlags(0) + body.personaSetByUser = true + body.personaStateFlags = 0 }.also(client::send) } @@ -212,8 +212,8 @@ class SteamFriends : ClientMsgHandler() { CMsgClientChangeStatus::class.java, EMsg.ClientChangeStatus ).apply { - body.setPersonaSetByUser(true) - body.setPersonaStateFlags(flag.code()) + body.personaSetByUser = true + body.personaStateFlags = flag.code() }.also(client::send) } @@ -349,9 +349,9 @@ class SteamFriends : ClientMsgHandler() { CMsgClientFriendMsg::class.java, EMsg.ClientFriendMsg ).apply { - body.setSteamid(target.convertToUInt64()) - body.setChatEntryType(type.code()) - body.setMessage(ByteString.copyFrom(message, StandardCharsets.UTF_8)) + body.steamid = target.convertToUInt64() + body.chatEntryType = type.code() + body.message = ByteString.copyFrom(message, StandardCharsets.UTF_8) }.also(client::send) } @@ -365,7 +365,7 @@ class SteamFriends : ClientMsgHandler() { CMsgClientAddFriend::class.java, EMsg.ClientAddFriend ).apply { - body.setAccountnameOrEmailToAdd(accountNameOrEmail) + body.accountnameOrEmailToAdd = accountNameOrEmail }.also(client::send) } @@ -379,7 +379,7 @@ class SteamFriends : ClientMsgHandler() { CMsgClientAddFriend::class.java, EMsg.ClientAddFriend ).apply { - body.setSteamidToAdd(steamID.convertToUInt64()) + body.steamidToAdd = steamID.convertToUInt64() }.also(client::send) } @@ -393,7 +393,7 @@ class SteamFriends : ClientMsgHandler() { CMsgClientRemoveFriend::class.java, EMsg.ClientRemoveFriend ).apply { - body.setFriendid(steamID.convertToUInt64()) + body.friendid = steamID.convertToUInt64() }.also(client::send) } @@ -469,11 +469,11 @@ class SteamFriends : ClientMsgHandler() { CMsgClientChatInvite::class.java, EMsg.ClientChatInvite ).apply { - body.setSteamIdChat(chatID.convertToUInt64()) - body.setSteamIdInvited(steamIdUser.convertToUInt64()) + body.steamIdChat = chatID.convertToUInt64() + body.steamIdInvited = steamIdUser.convertToUInt64() // steamclient also sends the steamid of the user that did the invitation // we'll mimic that behavior - body.setSteamIdPatron(client.steamID.convertToUInt64()) + body.steamIdPatron = client.steamID.convertToUInt64() }.also(client::send) } @@ -548,7 +548,7 @@ class SteamFriends : ClientMsgHandler() { EMsg.ClientRequestFriendData ).apply { body.addAllFriends(steamIdList.map { it.convertToUInt64() }) - body.setPersonaStateRequested(info) + body.personaStateRequested = info }.also(client::send) } @@ -574,12 +574,13 @@ class SteamFriends : ClientMsgHandler() { */ @JvmOverloads fun ignoreFriend(steamID: SteamID, setIgnore: Boolean = true): AsyncJobSingle { - val ignore = ClientMsg(MsgClientSetIgnoreFriend::class.java) - ignore.setSourceJobID(client.getNextJobID()) + val ignore = ClientMsg(MsgClientSetIgnoreFriend::class.java).apply { + sourceJobID = client.getNextJobID() - ignore.body.mySteamId = client.steamID - ignore.body.ignore = if (setIgnore) 1.toByte() else 0.toByte() - ignore.body.steamIdFriend = steamID + body.mySteamId = client.steamID + body.ignore = if (setIgnore) 1.toByte() else 0.toByte() + body.steamIdFriend = steamID + } client.send(ignore) @@ -597,10 +598,11 @@ class SteamFriends : ClientMsgHandler() { val request = ClientMsgProtobuf( CMsgClientFriendProfileInfo::class.java, EMsg.ClientFriendProfileInfo - ) - request.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - request.body.setSteamidFriend(steamID.convertToUInt64()) + body.steamidFriend = steamID.convertToUInt64() + } client.send(request) @@ -618,7 +620,7 @@ class SteamFriends : ClientMsgHandler() { CMsgClientChatGetFriendMessageHistory::class.java, EMsg.ClientChatGetFriendMessageHistory ).apply { - body.setSteamid(steamID.convertToUInt64()) + body.steamid = steamID.convertToUInt64() }.also(client::send) } @@ -643,16 +645,16 @@ class SteamFriends : ClientMsgHandler() { * @return The Job ID of the request. This can be used to find the appropriate [NicknameCallback]. */ fun setFriendNickname(friendID: SteamID, nickname: String): JobID { + val jobID: JobID = client.getNextJobID() val request = ClientMsgProtobuf( CMsgClientSetPlayerNickname::class.java, EMsg.AMClientSetPlayerNickname - ) - val jobID: JobID = client.getNextJobID() - - request.setSourceJobID(jobID) + ).apply { + sourceJobID = jobID - request.body.setSteamid(friendID.convertToUInt64()) - request.body.setNickname(nickname) + body.steamid = friendID.convertToUInt64() + body.nickname = nickname + } client.send(request) @@ -676,21 +678,21 @@ class SteamFriends : ClientMsgHandler() { * @return The Job ID of the request. This can be used to find the appropriate [AliasHistoryCallback]. */ fun requestAliasHistory(steamIDs: List): JobID { + val jobID: JobID = client.getNextJobID() val request = ClientMsgProtobuf( CMsgClientAMGetPersonaNameHistory::class.java, EMsg.ClientAMGetPersonaNameHistory - ) - val jobID: JobID = client.getNextJobID() - - request.setSourceJobID(jobID) + ).apply { + sourceJobID = jobID - request.body.addAllIds( - steamIDs.map { - CMsgClientAMGetPersonaNameHistory.IdInstance.newBuilder().setSteamid(it.convertToUInt64()).build() - } - ) + body.addAllIds( + steamIDs.map { + CMsgClientAMGetPersonaNameHistory.IdInstance.newBuilder().setSteamid(it.convertToUInt64()).build() + } + ) - request.body.setIdCount(request.body.idsCount) + body.idCount = body.idsCount + } client.send(request) @@ -716,8 +718,8 @@ class SteamFriends : ClientMsgHandler() { val callback = getCallback(packetMsg) // Ignore messages that we don't have a handler function for - callback?.let { - client.postCallback(it) + if (callback != null) { + client.postCallback(callback) return } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatEnterCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatEnterCallback.kt index 5ae865db..c38e0abe 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatEnterCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatEnterCallback.kt @@ -8,11 +8,8 @@ import `in`.dragonbra.javasteam.generated.MsgClientChatEnter import `in`.dragonbra.javasteam.steam.handlers.steamfriends.ChatMemberInfo import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.SteamID +import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.stream.BinaryReader -import java.io.ByteArrayInputStream -import java.io.IOException -import java.nio.charset.StandardCharsets -import java.util.* /** * This callback is fired in response to attempting to join a chat. @@ -20,6 +17,10 @@ import java.util.* @Suppress("MemberVisibilityCanBePrivate") class ChatEnterCallback(packetMsg: IPacketMsg) : CallbackMsg() { + companion object { + private val logger = LogManager.getLogger(ChatEnterCallback::class.java) + } + /** * Gets the [SteamID] of the chat room. */ @@ -90,19 +91,18 @@ class ChatEnterCallback(packetMsg: IPacketMsg) : CallbackMsg() { numChatMembers = msg.numMembers - val bais = ByteArrayInputStream(chatEnter.payload.toByteArray()) - + val ms = chatEnter.payload try { - BinaryReader(bais).use { br -> + BinaryReader(ms).use { br -> // steamclient always attempts to read the chat room name, regardless of the enter response - chatRoomName = br.readNullTermString(StandardCharsets.UTF_8) + chatRoomName = br.readNullTermString() if (enterResponse != EChatRoomEnterResponse.Success) { // the rest of the payload depends on a successful chat enter return@use } - val memberList: MutableList = ArrayList() + val memberList: MutableList = mutableListOf() for (i in 0 until numChatMembers) { val memberInfo = ChatMemberInfo() @@ -110,9 +110,11 @@ class ChatEnterCallback(packetMsg: IPacketMsg) : CallbackMsg() { memberList.add(memberInfo) } - chatMembers = Collections.unmodifiableList(memberList) + + chatMembers = memberList.toList() } - } catch (ignored: IOException) { + } catch (e: Exception) { + logger.error("Failed to read chat enter info.", e) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt index bf8e64ac..5c6a7a6b 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt @@ -8,8 +8,9 @@ import `in`.dragonbra.javasteam.generated.MsgClientChatMemberInfo import `in`.dragonbra.javasteam.steam.handlers.steamfriends.ChatMemberInfo import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.SteamID +import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.stream.BinaryReader -import java.io.ByteArrayInputStream +import `in`.dragonbra.javasteam.util.stream.MemoryStream import java.io.IOException import java.util.* @@ -19,6 +20,10 @@ import java.util.* @Suppress("MemberVisibilityCanBePrivate") class ChatMemberInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { + companion object { + private val logger = LogManager.getLogger(ChatMemberInfoCallback::class.java) + } + /** * Gets SteamId of the chat room. */ @@ -42,7 +47,7 @@ class ChatMemberInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { type = msg.type when (type) { - EChatInfoType.StateChange -> stateChangeInfo = StateChangeDetails(membInfo.payload.toByteArray()) + EChatInfoType.StateChange -> stateChangeInfo = StateChangeDetails(membInfo.payload) // todo: handle more types // based off disassembly // - for InfoUpdate, a ChatMemberInfo object is present @@ -55,7 +60,7 @@ class ChatMemberInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { /** * Represents state change information. */ - class StateChangeDetails(data: ByteArray?) { + class StateChangeDetails(ms: MemoryStream) { /** * Gets the [SteamID] of the chatter that was acted on. */ @@ -79,7 +84,7 @@ class ChatMemberInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { init { try { - BinaryReader(ByteArrayInputStream(data)).use { br -> + BinaryReader(ms).use { br -> chatterActedOn = SteamID(br.readLong()) stateChange = EChatMemberStateChange.from(br.readInt()) chatterActedBy = SteamID(br.readLong()) @@ -88,7 +93,8 @@ class ChatMemberInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { memberInfo!!.readFromStream(br) } } - } catch (ignored: IOException) { + } catch (e: IOException) { + logger.error("Failed to read chat member info", e) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgamecoordinator/SteamGameCoordinator.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgamecoordinator/SteamGameCoordinator.kt index 7d7f3c02..a80b0b8c 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgamecoordinator/SteamGameCoordinator.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamgamecoordinator/SteamGameCoordinator.kt @@ -22,13 +22,13 @@ class SteamGameCoordinator : ClientMsgHandler() { * @param appId The app id of the game coordinator to send to. */ fun send(msg: IClientGCMsg, appId: Int) { - val clientMsg = ClientMsgProtobuf(CMsgGCClient::class.java, EMsg.ClientToGC) + val clientMsg = ClientMsgProtobuf(CMsgGCClient::class.java, EMsg.ClientToGC).apply { + protoHeader.routingAppid = appId + body.msgtype = MsgUtil.makeGCMsg(msg.getMsgType(), msg.isProto()) + body.appid = appId - clientMsg.protoHeader.setRoutingAppid(appId) - clientMsg.body.setMsgtype(MsgUtil.makeGCMsg(msg.getMsgType(), msg.isProto())) - clientMsg.body.setAppid(appId) - - clientMsg.body.setPayload(ByteString.copyFrom(msg.serialize())) + body.payload = ByteString.copyFrom(msg.serialize()) + } client.send(clientMsg) } 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 820ba37e..29fcb19f 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 @@ -8,6 +8,7 @@ import `in`.dragonbra.javasteam.enums.EMsg import `in`.dragonbra.javasteam.enums.EResult import `in`.dragonbra.javasteam.enums.EServerFlags import `in`.dragonbra.javasteam.generated.MsgClientLogon +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesBase.CMsgIPAddress import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverGameservers.CMsgGSServerType import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientLogOff import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLogin.CMsgClientLogon @@ -48,19 +49,19 @@ class SteamGameServer : ClientMsgHandler() { val gsId = SteamID(0, 0, client.universe, EAccountType.GameServer) - logon.protoHeader.setClientSessionid(0) - logon.protoHeader.setSteamid(gsId.convertToUInt64()) + logon.protoHeader.clientSessionid = 0 + logon.protoHeader.steamid = gsId.convertToUInt64() - val localIp: Int = NetHelpers.getIPAddress(client.localIP) - logon.body.setDeprecatedObfustucatedPrivateIp(localIp xor MsgClientLogon.ObfuscationMask) // TODO: Using deprecated method. + val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP) + logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) // TODO validate - logon.body.setProtocolVersion(MsgClientLogon.CurrentProtocol) + logon.body.protocolVersion = MsgClientLogon.CurrentProtocol - logon.body.setClientOsType(Utils.getOSType().code()) - logon.body.setGameServerAppId(details.appID) - logon.body.setMachineId(ByteString.copyFrom(HardwareUtils.getMachineID())) + logon.body.clientOsType = Utils.getOSType().code() + logon.body.gameServerAppId = details.appID + logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) - logon.body.setGameServerToken(details.token) + logon.body.gameServerToken = details.token client.send(logon) } @@ -83,17 +84,17 @@ class SteamGameServer : ClientMsgHandler() { val gsId = SteamID(0, 0, client.universe, EAccountType.AnonGameServer) - logon.protoHeader.setClientSessionid(0) - logon.protoHeader.setSteamid(gsId.convertToUInt64()) + logon.protoHeader.clientSessionid = 0 + logon.protoHeader.steamid = gsId.convertToUInt64() - val localIp: Int = NetHelpers.getIPAddress(client.localIP) - logon.body.setDeprecatedObfustucatedPrivateIp(localIp xor MsgClientLogon.ObfuscationMask) // TODO: Using deprecated method. + val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP) + logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) // TODO: validate - logon.body.setProtocolVersion(MsgClientLogon.CurrentProtocol) + logon.body.protocolVersion = MsgClientLogon.CurrentProtocol - logon.body.setClientOsType(Utils.getOSType().code()) - logon.body.setGameServerAppId(appId) - logon.body.setMachineId(ByteString.copyFrom(HardwareUtils.getMachineID())) + logon.body.clientOsType = Utils.getOSType().code() + logon.body.gameServerAppId = appId + logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) client.send(logon) } @@ -120,16 +121,20 @@ class SteamGameServer : ClientMsgHandler() { fun sendStatus(details: StatusDetails) { require(!(details.address != null && details.address is Inet6Address)) { "Only IPv4 addresses are supported." } - val status = ClientMsgProtobuf(CMsgGSServerType::class.java, EMsg.GSServerType) - status.body.setAppIdServed(details.appID) - status.body.setFlags(EServerFlags.code(details.serverFlags)) - status.body.setGameDir(details.gameDirectory) - status.body.setGamePort(details.port) - status.body.setGameQueryPort(details.queryPort) - status.body.setGameVersion(details.version) + val status = ClientMsgProtobuf( + CMsgGSServerType::class.java, + EMsg.GSServerType + ).apply { + body.appIdServed = details.appID + body.flags = EServerFlags.code(details.serverFlags) + body.gameDir = details.gameDirectory + body.gamePort = details.port + body.gameQueryPort = details.queryPort + body.gameVersion = details.version + } details.address?.let { - status.body.setDeprecatedGameIpAddress(NetHelpers.getIPAddress(it)) // TODO: Using deprecated method. + status.body.deprecatedGameIpAddress = NetHelpers.getIPAddress(it) // TODO: validate } client.send(status) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt index a8423f5d..4da2451b 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steammasterserver/SteamMasterServer.kt @@ -26,19 +26,20 @@ class SteamMasterServer : ClientMsgHandler() { val query = ClientMsgProtobuf( CMsgClientGMSServerQuery::class.java, EMsg.ClientGMSServerQuery - ) - query.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - query.body.setAppId(details.appID) + body.appId = details.appID - details.geoLocatedIP?.let { - query.body.geoLocationIp = NetHelpers.getIPAddress(it) - } + if (details.geoLocatedIP != null) { + body.geoLocationIp = NetHelpers.getIPAddress(details.geoLocatedIP!!) + } - query.body.setFilterText(details.filter) - query.body.setRegionCode(details.region.code().toInt()) + body.filterText = details.filter + body.regionCode = details.region.code().toInt() - query.body.setMaxServers(details.maxServers) + body.maxServers = details.maxServers + } client.send(query) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnetworking/SteamNetworking.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnetworking/SteamNetworking.kt index 68ecca70..8bfabbb4 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnetworking/SteamNetworking.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnetworking/SteamNetworking.kt @@ -28,11 +28,12 @@ class SteamNetworking : ClientMsgHandler() { val msg = ClientMsgProtobuf( CMsgClientNetworkingCertRequest::class.java, EMsg.ClientNetworkingCertRequest - ) - msg.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - msg.body.setAppId(appId) - msg.body.setKeyData(ByteString.copyFrom(publicKey)) + body.appId = appId + body.keyData = ByteString.copyFrom(publicKey) + } client.send(msg) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/CommentNotificationsCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/CommentNotificationsCallback.kt index d4be5642..86179827 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/CommentNotificationsCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/CommentNotificationsCallback.kt @@ -30,9 +30,10 @@ class CommentNotificationsCallback(packetMsg: IPacketMsg) : CallbackMsg() { CMsgClientCommentNotifications::class.java, packetMsg ) + val msg = resp.body - commentCount = resp.body.countNewComments - commentOwnerCount = resp.body.countNewCommentsOwner - commentSubscriptionsCount = resp.body.countNewCommentsSubscriptions + commentCount = msg.countNewComments + commentOwnerCount = msg.countNewCommentsOwner + commentSubscriptionsCount = msg.countNewCommentsSubscriptions } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/ItemAnnouncementsCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/ItemAnnouncementsCallback.kt index 792da30b..356b53b4 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/ItemAnnouncementsCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/ItemAnnouncementsCallback.kt @@ -20,7 +20,8 @@ class ItemAnnouncementsCallback(packetMsg: IPacketMsg) : CallbackMsg() { CMsgClientItemAnnouncements::class.java, packetMsg ) + val msg = resp.body - count = resp.body.countNewItems + count = msg.countNewItems } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/OfflineMessageNotificationCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/OfflineMessageNotificationCallback.kt index abab8494..bc67b903 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/OfflineMessageNotificationCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/OfflineMessageNotificationCallback.kt @@ -27,10 +27,9 @@ class OfflineMessageNotificationCallback(packetMsg: IPacketMsg) : CallbackMsg() CMsgClientOfflineMessageNotification::class.java, packetMsg ) + val msg = resp.body - messageCount = resp.body.offlineMessages - friendsWithOfflineMessages = resp.body.friendsWithOfflineMessagesList.map { - SteamID(it.toLong()) - } + messageCount = msg.offlineMessages + friendsWithOfflineMessages = msg.friendsWithOfflineMessagesList.map { SteamID(it.toLong()) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/UserNotificationsCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/UserNotificationsCallback.kt index c89b85de..1af62493 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/UserNotificationsCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamnotifications/callback/UserNotificationsCallback.kt @@ -21,7 +21,8 @@ class UserNotificationsCallback(packetMsg: IPacketMsg) : CallbackMsg() { CMsgClientUserNotifications::class.java, packetMsg ) + val msg = resp.body - notifications = resp.body.notificationsList.map(::Notification) + notifications = msg.notificationsList.map(::Notification) } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamscreenshots/SteamScreenshots.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamscreenshots/SteamScreenshots.kt index df6e3283..c49e0506 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamscreenshots/SteamScreenshots.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamscreenshots/SteamScreenshots.kt @@ -27,20 +27,21 @@ class SteamScreenshots : ClientMsgHandler() { val msg = ClientMsgProtobuf( CMsgClientUCMAddScreenshot::class.java, EMsg.ClientUCMAddScreenshot - ) - msg.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - details.gameID?.let { - msg.body.setAppid(it.appID) - } + if (details.gameID != null) { + body.appid = details.gameID!!.appID + } - msg.body.setCaption(details.caption) - msg.body.setFilename(details.ufsImageFilePath) - msg.body.setPermissions(details.privacy.code()) - msg.body.setThumbname(details.usfThumbnailFilePath) - msg.body.setWidth(details.width) - msg.body.setHeight(details.height) - msg.body.setRtime32Created((details.creationTime.time / 1000L).toInt()) + body.caption = details.caption + body.filename = details.ufsImageFilePath + body.permissions = details.privacy.code() + body.thumbname = details.usfThumbnailFilePath + body.width = details.width + body.height = details.height + body.rtime32Created = (details.creationTime.time / 1000L).toInt() + } client.send(msg) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt index 33ed1390..5397e45d 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/SteamUnifiedMessages.kt @@ -57,8 +57,8 @@ class SteamUnifiedMessages : ClientMsgHandler() { } ClientMsgProtobuf(message.javaClass, eMsg).apply { - setSourceJobID(jobID) - header.proto.setTargetJobName(rpcName) + sourceJobID = jobID + header.proto.targetJobName = rpcName body!!.mergeFrom(message) }.also(client::send) @@ -83,7 +83,7 @@ class SteamUnifiedMessages : ClientMsgHandler() { } ClientMsgProtobuf(message.javaClass, eMsg).apply { - header.proto.setTargetJobName(rpcName) + header.proto.targetJobName = rpcName body!!.mergeFrom(message) }.also(client::send) } @@ -98,7 +98,7 @@ class SteamUnifiedMessages : ClientMsgHandler() { private fun handleServiceMethod(packetMsg: IPacketMsg) { require(packetMsg is PacketClientMsgProtobuf) { "Packet message is expected to be protobuf." } - val jobName = packetMsg.header.proto.getTargetJobName() + val jobName = packetMsg.header.proto.targetJobName if (jobName.isNullOrEmpty()) { logger.debug("Job name is null or empty") @@ -130,7 +130,7 @@ class SteamUnifiedMessages : ClientMsgHandler() { client.postCallback(ServiceMethodNotification(argumentType, packetMsg)) } - } catch (e: ClassNotFoundException) { + } catch (_: ClassNotFoundException) { // The RPC service implementation was not implemented. // Either the .proto is missing, or the service was not converted to an interface yet. logger.debug("Service Method: $serviceName, was not found") diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodNotification.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodNotification.kt index 5d70a96e..d26e8ceb 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodNotification.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamunifiedmessages/callback/ServiceMethodNotification.kt @@ -54,7 +54,7 @@ class ServiceMethodNotification(messageType: Class, packetM init { // Note: JobID will be -1 - methodName = clientMsg.header.proto.getTargetJobName() + methodName = clientMsg.header.proto.targetJobName body = clientMsg.body.build() } } 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 0337d3a8..bba743cd 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 @@ -70,59 +70,64 @@ class SteamUser : ClientMsgHandler() { if (details.loginID != null) { // TODO: Support IPv6 login ids? - CMsgIPAddress.newBuilder().apply { + logon.body.obfuscatedPrivateIp = CMsgIPAddress.newBuilder().apply { v4 = details.loginID!! - }.build().also(logon.body::setObfuscatedPrivateIp) + }.build() } else { - client.localIP?.let { clientIP -> - val msgIpAddr = NetHelpers.getMsgIPAddress(clientIP) + client.localIP?.let { localIP -> + val msgIpAddr = NetHelpers.getMsgIPAddress(localIP) logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(msgIpAddr) } } // Legacy field, Steam client still sets it if (logon.body.obfuscatedPrivateIp.hasV4()) { - logon.body.setDeprecatedObfustucatedPrivateIp(logon.body.obfuscatedPrivateIp.getV4()) + logon.body.deprecatedObfustucatedPrivateIp = logon.body.obfuscatedPrivateIp.v4 } - logon.protoHeader.setClientSessionid(0) - logon.protoHeader.setSteamid(steamID.convertToUInt64()) + logon.protoHeader.clientSessionid = 0 + logon.protoHeader.steamid = steamID.convertToUInt64() - logon.body.setAccountName(details.username) - details.password?.let { logon.body.setPassword(it) } - logon.body.setShouldRememberPassword(details.shouldRememberPassword) + logon.body.accountName = details.username + logon.body.password = details.password + logon.body.shouldRememberPassword = details.shouldRememberPassword - logon.body.setProtocolVersion(MsgClientLogon.CurrentProtocol) - logon.body.setClientOsType(details.clientOSType.code()) - logon.body.setClientLanguage(details.clientLanguage) - logon.body.setCellId(details.cellID ?: client.configuration.cellID) + logon.body.protocolVersion = MsgClientLogon.CurrentProtocol + logon.body.clientOsType = details.clientOSType.code() + logon.body.clientLanguage = details.clientLanguage + logon.body.cellId = details.cellID ?: client.configuration.cellID - logon.body.setSteam2TicketRequest(details.requestSteam2Ticket) + logon.body.steam2TicketRequest = details.requestSteam2Ticket // we're now using the latest steamclient package version, this is required to get a proper sentry file for steam guard - logon.body.setClientPackageVersion(1771) // todo: determine if this is still required - logon.body.setSupportsRateLimitResponse(true) - logon.body.setMachineName(details.machineName) - val machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) - logon.body.setMachineId(machineId) + logon.body.clientPackageVersion = 1771 // todo: determine if this is still required + logon.body.supportsRateLimitResponse = true + logon.body.machineName = details.machineName + logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) if (details.chatMode != ChatMode.DEFAULT) { - logon.body.setChatMode(details.chatMode.mode) + logon.body.chatMode = details.chatMode.mode } if (details.uiMode != EUIMode.Unknown) { - logon.body.setUiMode(details.uiMode.code()) + logon.body.uiMode = details.uiMode.code() } if (details.isSteamDeck) { - logon.body.setIsSteamDeck(true) + logon.body.isSteamDeck = true } // steam guard - details.authCode?.let(logon.body::setAuthCode) - details.twoFactorCode?.let(logon.body::setTwoFactorCode) + if (details.authCode != null) { + logon.body.authCode = details.authCode + } + if (details.twoFactorCode != null) { + logon.body.twoFactorCode = details.twoFactorCode + } - details.accessToken?.let(logon.body::setAccessToken) + if (details.accessToken != null) { + logon.body.accessToken = details.accessToken + } client.send(logon) } @@ -145,16 +150,15 @@ class SteamUser : ClientMsgHandler() { val auId = SteamID(0, 0, client.universe, EAccountType.AnonUser) - logon.protoHeader.setClientSessionid(0) - logon.protoHeader.setSteamid(auId.convertToUInt64()) + logon.protoHeader.clientSessionid = 0 + logon.protoHeader.steamid = auId.convertToUInt64() - logon.body.setProtocolVersion(MsgClientLogon.CurrentProtocol) - logon.body.setClientOsType(details.clientOSType.code()) - logon.body.setClientLanguage(details.clientLanguage) - logon.body.setCellId(details.cellID ?: client.configuration.cellID) + logon.body.protocolVersion = MsgClientLogon.CurrentProtocol + logon.body.clientOsType = details.clientOSType.code() + logon.body.clientLanguage = details.clientLanguage + logon.body.cellId = details.cellID ?: client.configuration.cellID - val machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) - logon.body.setMachineId(machineId) + logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) client.send(logon) } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/LoggedOnCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/LoggedOnCallback.kt index 5912fbb7..a2c40331 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/LoggedOnCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/LoggedOnCallback.kt @@ -14,6 +14,7 @@ import `in`.dragonbra.javasteam.steam.handlers.steamuser.SteamUser import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.SteamID import `in`.dragonbra.javasteam.util.NetHelpers +import `in`.dragonbra.javasteam.util.log.LogManager import java.net.InetAddress import java.util.* @@ -23,6 +24,10 @@ import java.util.* @Suppress("unused", "MemberVisibilityCanBePrivate") class LoggedOnCallback : CallbackMsg { + companion object { + private val logger = LogManager.getLogger(LoggedOnCallback::class.java) + } + /** * Gets the result of the logon. */ @@ -127,7 +132,7 @@ class LoggedOnCallback : CallbackMsg { constructor(packetMsg: IPacketMsg) { if (!packetMsg.isProto) { - handleNonProtoLogin(packetMsg) + handleNonProtoLogon(packetMsg) return } @@ -143,7 +148,7 @@ class LoggedOnCallback : CallbackMsg { outOfGameSecsPerHeartbeat = resp.legacyOutOfGameHeartbeatSeconds inGameSecsPerHeartbeat = resp.heartbeatSeconds - publicIP = NetHelpers.getIPAddress(resp.publicIp.v4) // Has ipV6 support, but still using ipV4 + publicIP = NetHelpers.getIPAddress(resp.publicIp) serverTime = Date(resp.rtime32ServerTime * 1000L) @@ -165,11 +170,11 @@ class LoggedOnCallback : CallbackMsg { numLoginFailuresToMigrate = resp.countLoginfailuresToMigrate numDisconnectsToMigrate = resp.countDisconnectsToMigrate - resp.parentalSettings?.let { + if (resp.parentalSettings != null) { try { - parentalSettings = ParentalSettings.parseFrom(it) + parentalSettings = ParentalSettings.parseFrom(resp.parentalSettings) } catch (e: InvalidProtocolBufferException) { - e.printStackTrace() + logger.error("Failed to parse parental settings", e) } } } @@ -178,7 +183,7 @@ class LoggedOnCallback : CallbackMsg { this.result = result } - private fun handleNonProtoLogin(packetMsg: IPacketMsg) { + private fun handleNonProtoLogon(packetMsg: IPacketMsg) { val loginResp = ClientMsg(MsgClientLogOnResponse::class.java, packetMsg) val resp = loginResp.body diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/MarketingMessageCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/MarketingMessageCallback.kt index e4956152..b1d8e5c0 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/MarketingMessageCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuser/callback/MarketingMessageCallback.kt @@ -9,8 +9,7 @@ import `in`.dragonbra.javasteam.types.GlobalID import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.log.Logger import `in`.dragonbra.javasteam.util.stream.BinaryReader -import java.io.ByteArrayInputStream -import java.io.IOException +import `in`.dragonbra.javasteam.util.stream.MemoryStream import java.nio.charset.StandardCharsets import java.util.* @@ -35,10 +34,10 @@ class MarketingMessageCallback(packetMsg: IPacketMsg) : CallbackMsg() { updateTime = Date(body.marketingMessageUpdateTime * 1000L) - val msgList: MutableList = ArrayList() + val msgList: MutableList = mutableListOf() try { - BinaryReader(ByteArrayInputStream(marketingMessage.payload.toByteArray())).use { br -> + BinaryReader(marketingMessage.payload).use { br -> for (i in 0 until body.count) { val dataLen = br.readInt() - 4 // total length includes the 4 byte length val messageData = br.readBytes(dataLen) @@ -46,8 +45,8 @@ class MarketingMessageCallback(packetMsg: IPacketMsg) : CallbackMsg() { msgList.add(Message(messageData)) } } - } catch (e: IOException) { - logger.debug(e) + } catch (e: Exception) { + logger.error("Failed to get marketing messages", e) } messages = msgList.toList() @@ -78,13 +77,14 @@ class MarketingMessageCallback(packetMsg: IPacketMsg) : CallbackMsg() { init { try { - BinaryReader(ByteArrayInputStream(data)).use { br -> + val ms = MemoryStream(data) + BinaryReader(ms).use { br -> id = GlobalID(br.readLong()) url = br.readNullTermString(StandardCharsets.UTF_8) flags = EMarketingMessageFlags.from(br.readInt()) } - } catch (e: IOException) { - logger.debug(e) + } catch (e: Exception) { + logger.error("Failed to parse marketing messages", e) } } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/LeaderboardEntry.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/LeaderboardEntry.kt index 117d353b..d431105a 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/LeaderboardEntry.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/LeaderboardEntry.kt @@ -3,6 +3,7 @@ package `in`.dragonbra.javasteam.steam.handlers.steamuserstats import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLbs.CMsgClientLBSGetLBEntriesResponse import `in`.dragonbra.javasteam.types.SteamID import `in`.dragonbra.javasteam.types.UGCHandle +import `in`.dragonbra.javasteam.util.log.LogManager import `in`.dragonbra.javasteam.util.stream.BinaryReader import `in`.dragonbra.javasteam.util.stream.MemoryStream import java.io.IOException @@ -13,6 +14,10 @@ import java.io.IOException @Suppress("unused") class LeaderboardEntry(entry: CMsgClientLBSGetLBEntriesResponse.Entry) { + companion object { + private val logger = LogManager.getLogger(LeaderboardEntry::class.java) + } + /** * Gets the [SteamID] for this entry. */ @@ -39,7 +44,7 @@ class LeaderboardEntry(entry: CMsgClientLBSGetLBEntriesResponse.Entry) { val details: List init { - val entryDetails = mutableListOf() + val entryDetails: MutableList = mutableListOf() if (entry.details != null) { val ms = MemoryStream(entry.details.toByteArray()) @@ -50,7 +55,7 @@ class LeaderboardEntry(entry: CMsgClientLBSGetLBEntriesResponse.Entry) { entryDetails.add(br.readInt()) } } catch (e: IOException) { - throw IllegalArgumentException("failed to read details", e) + logger.error("failed to read details", e) } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt index a7ee1d3b..5666a59c 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt @@ -32,10 +32,11 @@ class SteamUserStats : ClientMsgHandler() { val msg = ClientMsgProtobuf( CMsgDPGetNumberOfCurrentPlayers::class.java, EMsg.ClientGetNumberOfCurrentPlayersDP - ) - msg.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - msg.body.setAppid(appId) + body.appid = appId + } client.send(msg) @@ -55,15 +56,16 @@ class SteamUserStats : ClientMsgHandler() { val msg = ClientMsgProtobuf( CMsgClientLBSFindOrCreateLB::class.java, EMsg.ClientLBSFindOrCreateLB - ) - msg.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = (client.getNextJobID()) - // routing_appid has to be set correctly to receive a response - msg.protoHeader.setRoutingAppid(appId) + // routing_appid has to be set correctly to receive a response + protoHeader.routingAppid = appId - msg.body.setAppId(appId) - msg.body.setLeaderboardName(name) - msg.body.setCreateIfNotFound(false) + body.appId = appId + body.leaderboardName = name + body.createIfNotFound = false + } client.send(msg) @@ -90,17 +92,18 @@ class SteamUserStats : ClientMsgHandler() { val msg = ClientMsgProtobuf( CMsgClientLBSFindOrCreateLB::class.java, EMsg.ClientLBSFindOrCreateLB - ) - msg.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - // routing_appid has to be set correctly to receive a response - msg.protoHeader.setRoutingAppid(appId) + // routing_appid has to be set correctly to receive a response + protoHeader.routingAppid = appId - msg.body.setAppId(appId) - msg.body.setLeaderboardName(name) - msg.body.setLeaderboardDisplayType(displayType.code()) - msg.body.setLeaderboardSortMethod(sortMethod.code()) - msg.body.setCreateIfNotFound(true) + body.appId = appId + body.leaderboardName = name + body.leaderboardDisplayType = displayType.code() + body.leaderboardSortMethod = sortMethod.code() + body.createIfNotFound = true + } client.send(msg) @@ -129,14 +132,15 @@ class SteamUserStats : ClientMsgHandler() { val msg = ClientMsgProtobuf( CMsgClientLBSGetLBEntries::class.java, EMsg.ClientLBSGetLBEntries - ) - msg.setSourceJobID(client.getNextJobID()) - - msg.body.setAppId(appId) - msg.body.setLeaderboardId(id) - msg.body.setLeaderboardDataRequest(dataRequest.code()) - msg.body.setRangeStart(rangeStart) - msg.body.setRangeEnd(rangeEnd) + ).apply { + sourceJobID = client.getNextJobID() + + body.appId = appId + body.leaderboardId = id + body.leaderboardDataRequest = dataRequest.code() + body.rangeStart = rangeStart + body.rangeEnd = rangeEnd + } client.send(msg) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamworkshop/SteamWorkshop.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamworkshop/SteamWorkshop.kt index 1e811983..d9bd8034 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamworkshop/SteamWorkshop.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamworkshop/SteamWorkshop.kt @@ -26,12 +26,13 @@ class SteamWorkshop : ClientMsgHandler() { val enumRequest = ClientMsgProtobuf( CMsgClientUCMEnumeratePublishedFilesByUserAction::class.java, EMsg.ClientUCMEnumeratePublishedFilesByUserAction - ) - enumRequest.setSourceJobID(client.getNextJobID()) + ).apply { + sourceJobID = client.getNextJobID() - enumRequest.body.setAction(details.userAction.code()) - enumRequest.body.setAppId(details.appID) - enumRequest.body.setStartIndex(details.startIndex) + body.action = details.userAction.code() + body.appId = details.appID + body.startIndex = details.startIndex + } client.send(enumRequest) diff --git a/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt b/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt index 3f2e75b1..60c6bdbc 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/steamclient/SteamClient.kt @@ -189,7 +189,7 @@ class SteamClient @JvmOverloads constructor( boxID = 0L processID = 0L sequentialCount = currentJobId.incrementAndGet() - setStartTime(processStartTime) + startTime = processStartTime } fun startJob(job: AsyncJob) { diff --git a/src/main/java/in/dragonbra/javasteam/types/AsyncJobMultiple.kt b/src/main/java/in/dragonbra/javasteam/types/AsyncJobMultiple.kt index 4c4c8ed4..ef4aafd8 100644 --- a/src/main/java/in/dragonbra/javasteam/types/AsyncJobMultiple.kt +++ b/src/main/java/in/dragonbra/javasteam/types/AsyncJobMultiple.kt @@ -49,7 +49,7 @@ class AsyncJobMultiple( } override fun setFailed(dueToRemoteFailure: Boolean) { - if (results.size == 0) { + if (results.isEmpty()) { // if we have zero callbacks in our result set, we cancel this task if (dueToRemoteFailure) { // if we're canceling with a remote failure, post a job failure exception diff --git a/src/main/java/in/dragonbra/javasteam/types/KeyValue.java b/src/main/java/in/dragonbra/javasteam/types/KeyValue.java index da11ecc5..d5f10dbc 100644 --- a/src/main/java/in/dragonbra/javasteam/types/KeyValue.java +++ b/src/main/java/in/dragonbra/javasteam/types/KeyValue.java @@ -606,9 +606,10 @@ private static void writeString(OutputStream os, String str, boolean quote) thro * * @param is The input {@link InputStream} to read from. * @return true if the read was successful; otherwise, false. - * @throws IOException exception while reading from the stream + * @throws IOException exception while reading from the stream + * @throws IllegalArgumentException exception while reading from the stream */ - public boolean tryReadAsBinary(InputStream is) throws IOException { + public boolean tryReadAsBinary(InputStream is) throws IllegalArgumentException, IOException { if (is == null) { throw new IllegalArgumentException("input stream is null"); } diff --git a/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java b/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java index b5fa2d57..960561e1 100644 --- a/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java +++ b/src/main/java/in/dragonbra/javasteam/util/stream/BinaryReader.java @@ -136,19 +136,16 @@ private String readNullTermUtf8String() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b; - while ((b = in.read()) > 0) { + while ((b = in.read()) != 0) { + if (b <= 0) { + break; + } baos.write(b); - position += 1; - } - - if (b == -1) { - throw new EOFException(); - } - - if (b == 0) { position++; } + position++; // Increment for the null terminator + return baos.toString(StandardCharsets.UTF_8); } diff --git a/src/main/java/in/dragonbra/javasteam/util/stream/MemoryStream.java b/src/main/java/in/dragonbra/javasteam/util/stream/MemoryStream.java index 395f9f6d..f3c687d5 100644 --- a/src/main/java/in/dragonbra/javasteam/util/stream/MemoryStream.java +++ b/src/main/java/in/dragonbra/javasteam/util/stream/MemoryStream.java @@ -1,5 +1,7 @@ package in.dragonbra.javasteam.util.stream; +import com.google.protobuf.ByteString; + import java.io.Closeable; import java.io.InputStream; import java.io.OutputStream; @@ -46,6 +48,17 @@ public MemoryStream(int capacity) { this.bufferVisible = true; } + /** + * Initializes a new non-resizable instance of the MemoryStream class based on the + * specified byte array. + * + * @param byteString The sequence of bytes from which to create the current stream. + */ + public MemoryStream(ByteString byteString) { + this(byteString.toByteArray(), true); + } + + /** * Initializes a new non-resizable instance of the MemoryStream class based on the * specified byte array. @@ -392,4 +405,4 @@ public void write(int b) { memoryStream.writeByte((byte) b); } } -} \ No newline at end of file +} diff --git a/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java b/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java index cf8c58cf..76ef7e44 100644 --- a/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java +++ b/src/test/java/in/dragonbra/javasteam/util/stream/BinaryReaderTest.java @@ -154,7 +154,14 @@ void testEOFException() { Assertions.assertThrows(EOFException.class, () -> binaryReader.readByte()); Assertions.assertThrows(EOFException.class, () -> binaryReader.readChar()); Assertions.assertThrows(EOFException.class, () -> binaryReader.readInt()); - Assertions.assertThrows(EOFException.class, () -> binaryReader.readNullTermString()); Assertions.assertThrows(EOFException.class, () -> binaryReader.readShort()); } + + @Test + void testEmptyNullTermString() throws IOException { + byte[] data = {}; + binaryReader = new BinaryReader(new MemoryStream(data)); + + Assertions.assertEquals("", binaryReader.readNullTermString()); + } } From a9ad1da35d056841051ad4c6296d82b627fe9291 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sun, 20 Oct 2024 22:18:46 -0500 Subject: [PATCH 19/21] Label TODOs that are still on SteamKit --- .../javasteam/steam/handlers/steamapps/PICSProductInfo.kt | 2 +- .../javasteam/steam/handlers/steamfriends/SteamFriends.kt | 2 +- .../handlers/steamfriends/callback/ChatMemberInfoCallback.kt | 2 +- .../handlers/steamfriends/callback/ChatRoomInfoCallback.kt | 2 +- .../dragonbra/javasteam/steam/handlers/steamuser/SteamUser.kt | 4 ++-- src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt index 59c0ab0b..42287a37 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamapps/PICSProductInfo.kt @@ -121,7 +121,7 @@ class PICSProductInfo : CallbackMsg { BinaryReader(ByteArrayInputStream(packageInfo.buffer.toByteArray())).use { br -> // steamclient checks this value == 1 before it attempts to read the KV from the buffer // see: CPackageInfo::UpdateFromBuffer(CSHA const&,uint,CUtlBuffer &) - // todo: we've apparently ignored this with zero ill effects, but perhaps we want to respect it? + // todo: (SK) we've apparently ignored this with zero ill effects, but perhaps we want to respect it? br.readInt() keyValues.tryReadAsBinary(br) } 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 4624f1f0..43318c97 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 @@ -777,7 +777,7 @@ class SteamFriends : ClientMsgHandler() { logger.debug("Unknown item in handlePersonaState(): $friendID") } - // todo: cache other details/account types? + // todo: (SK) cache other details/account types? } perState.body.friendsList.forEach { friend -> diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt index 5c6a7a6b..4fab55d4 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatMemberInfoCallback.kt @@ -48,7 +48,7 @@ class ChatMemberInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { when (type) { EChatInfoType.StateChange -> stateChangeInfo = StateChangeDetails(membInfo.payload) - // todo: handle more types + // todo: (SK) handle more types // based off disassembly // - for InfoUpdate, a ChatMemberInfo object is present // - for MemberLimitChange, looks like an ignored uint64 (probably steamid) followed diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatRoomInfoCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatRoomInfoCallback.kt index 659a4a2a..8e1e24f4 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatRoomInfoCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamfriends/callback/ChatRoomInfoCallback.kt @@ -29,6 +29,6 @@ class ChatRoomInfoCallback(packetMsg: IPacketMsg) : CallbackMsg() { chatRoomID = msg.steamIdChat type = msg.type - // todo: handle inner payload based on the type similar to ChatMemberInfoCallback + // todo: (SK) handle inner payload based on the type similar to ChatMemberInfoCallback } } 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 bba743cd..e0d81539 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 @@ -69,7 +69,7 @@ class SteamUser : ClientMsgHandler() { val steamID = SteamID(details.accountID, details.accountInstance, client.universe, EAccountType.Individual) if (details.loginID != null) { - // TODO: Support IPv6 login ids? + // TODO: (SK) Support IPv6 login ids? logon.body.obfuscatedPrivateIp = CMsgIPAddress.newBuilder().apply { v4 = details.loginID!! }.build() @@ -100,7 +100,7 @@ class SteamUser : ClientMsgHandler() { logon.body.steam2TicketRequest = details.requestSteam2Ticket // we're now using the latest steamclient package version, this is required to get a proper sentry file for steam guard - logon.body.clientPackageVersion = 1771 // todo: determine if this is still required + logon.body.clientPackageVersion = 1771 // todo: (SK) determine if this is still required logon.body.supportsRateLimitResponse = true logon.body.machineName = details.machineName logon.body.machineId = ByteString.copyFrom(HardwareUtils.getMachineID()) diff --git a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt index 99f77a4a..b56db0bb 100644 --- a/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt +++ b/src/main/java/in/dragonbra/javasteam/types/KVTextReader.kt @@ -26,7 +26,7 @@ internal constructor( 'n' to '\n', 'r' to '\r', 't' to '\t', - // todo: any others? + // todo: (SK) any others? ) } From cc926b68b2154a4636f125c049041f6957814b9d Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sun, 20 Oct 2024 22:20:35 -0500 Subject: [PATCH 20/21] Check off TODOs and add some notes. Minor code tweaks. --- .../in/dragonbra/javasteam/steam/CMClient.java | 2 ++ .../handlers/steamgameserver/SteamGameServer.kt | 8 ++++---- .../steam/handlers/steamuser/SteamUser.kt | 4 +++- .../java/in/dragonbra/javasteam/util/NetHelpers.kt | 14 ++++++++------ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java index 706716e4..4005701e 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/CMClient.java +++ b/src/main/java/in/dragonbra/javasteam/steam/CMClient.java @@ -388,6 +388,8 @@ private void handleLogOnResponse(IPacketMsg packetMsg) { EResult logonResponse = EResult.from(logonResp.getBody().getEresult()); logger.debug("handleLogOnResponse got response: " + logonResponse); + // Note: Sometimes if you sign in too many times, steam may confuse "InvalidPassword" with "RateLimitExceeded" + if (logonResponse == EResult.OK) { sessionID = logonResp.getProtoHeader().getClientSessionid(); steamID = new SteamID(logonResp.getProtoHeader().getSteamid()); 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 29fcb19f..af829db7 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 @@ -53,7 +53,7 @@ class SteamGameServer : ClientMsgHandler() { logon.protoHeader.steamid = gsId.convertToUInt64() val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP) - logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) // TODO validate + logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) logon.body.protocolVersion = MsgClientLogon.CurrentProtocol @@ -88,7 +88,7 @@ class SteamGameServer : ClientMsgHandler() { logon.protoHeader.steamid = gsId.convertToUInt64() val localIp: CMsgIPAddress = NetHelpers.getMsgIPAddress(client.localIP) - logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) // TODO: validate + logon.body.obfuscatedPrivateIp = NetHelpers.obfuscatePrivateIP(localIp) logon.body.protocolVersion = MsgClientLogon.CurrentProtocol @@ -133,8 +133,8 @@ class SteamGameServer : ClientMsgHandler() { body.gameVersion = details.version } - details.address?.let { - status.body.deprecatedGameIpAddress = NetHelpers.getIPAddress(it) // TODO: validate + if (details.address != null) { + status.body.deprecatedGameIpAddress = NetHelpers.getIPAddress(details.address!!) } client.send(status) 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 e0d81539..e43ea22f 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 @@ -89,7 +89,9 @@ class SteamUser : ClientMsgHandler() { logon.protoHeader.steamid = steamID.convertToUInt64() logon.body.accountName = details.username - logon.body.password = details.password + if (details.password != null) { + logon.body.password = details.password + } logon.body.shouldRememberPassword = details.shouldRememberPassword logon.body.protocolVersion = MsgClientLogon.CurrentProtocol diff --git a/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt b/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt index bce86eb3..cc1e502c 100644 --- a/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt +++ b/src/main/java/in/dragonbra/javasteam/util/NetHelpers.kt @@ -23,8 +23,7 @@ object NetHelpers { fun getIPAddress(ipAddr: InetAddress): Int { require(ipAddr.address.size == 4) { "only works with IPv4 addresses." } - val byteBuffer = ByteBuffer.wrap(ipAddr.address) - return byteBuffer.int and 0xFFFFFFFFL.toInt() + return ByteBuffer.wrap(ipAddr.address).int // and 0xFFFFFFFFL.toInt() } @JvmStatic @@ -56,10 +55,12 @@ object NetHelpers { } @JvmStatic - fun getIPAddress(ipAddr: CMsgIPAddress): InetAddress = if (ipAddr.hasV6()) { - InetAddress.getByAddress(ipAddr.v6.toByteArray()) - } else { - getIPAddress(ipAddr.v4) + fun getIPAddress(ipAddr: CMsgIPAddress): InetAddress { + return if (ipAddr.hasV6()) { + InetAddress.getByAddress(ipAddr.v6.toByteArray()) + } else { + getIPAddress(ipAddr.v4) + } } @JvmStatic @@ -80,6 +81,7 @@ object NetHelpers { val localIp = msgIpAddress.toBuilder() if (localIp.hasV6()) { + // TODO v6 validation val v6Bytes = msgIpAddress.v6.toByteArray() for (i in 0..15 step 4) { v6Bytes[i] = (v6Bytes[i].toInt() xor 0x0D).toByte() From c23c26d8ab57d5ae75bbaef8402e7fdb50061271 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sun, 20 Oct 2024 22:20:59 -0500 Subject: [PATCH 21/21] Close the connection if steam is done sending data. --- .../javasteam/networking/steam3/WebSocketConnection.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt b/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt index 5497ff10..9dce1967 100644 --- a/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt +++ b/src/main/java/in/dragonbra/javasteam/networking/steam3/WebSocketConnection.kt @@ -87,7 +87,10 @@ class WebSocketConnection : } override fun onClosing(code: Int, reason: String) { - logger.debug("Closing connection") + logger.debug("Closing connection: $code, reason: ${reason.ifEmpty { "No reason given" }}") + // Steam can close a connection if there is nothing else it wants to send. + // For example: AccountLoginDeniedNeedTwoFactor, InvalidPassword, etc. + disconnectCore(code == 1000) } override fun onError(t: Throwable) {