diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/CyclicRedundancyCheckBlock.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/CyclicRedundancyCheckBlock.kt new file mode 100644 index 000000000..9b0a3e4d1 --- /dev/null +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/CyclicRedundancyCheckBlock.kt @@ -0,0 +1,31 @@ +package net.rsprot.protocol.loginprot.incoming.util + +/** + * CRC blocks are helper structures used for the server to verify that the CRC is up-to-date. + * As the client transmits less CRCs than there are cache indices, we provide validation methods + * through this abstract class at the respective revision's decoder level, so we can perform checks + * that correspond to the information received from the client, and not what the server fully knows of. + * @property clientCrc the int array of client CRCs, indexed by the cache archives. + */ +public abstract class CyclicRedundancyCheckBlock( + protected val clientCrc: IntArray, +) { + public abstract fun validate(serverCrc: IntArray): Boolean + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CyclicRedundancyCheckBlock) return false + + if (!clientCrc.contentEquals(other.clientCrc)) return false + + return true + } + + override fun hashCode(): Int { + return clientCrc.contentHashCode() + } + + override fun toString(): String { + return "CyclicRedundancyCheckBlock(clientCrc=${clientCrc.contentToString()})" + } +} diff --git a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/LoginBlock.kt b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/LoginBlock.kt index 3f80ddbd7..1ae3bf395 100644 --- a/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/LoginBlock.kt +++ b/protocol/osrs-221-model/src/main/kotlin/net/rsprot/protocol/loginprot/incoming/util/LoginBlock.kt @@ -23,7 +23,7 @@ public class LoginBlock( public val hostPlatformStats: HostPlatformStats, private val _secondClientType: UByte, private val _crcBlockHeader: UByte, - public val crc: IntArray, + public val crc: CyclicRedundancyCheckBlock, public val authentication: T, ) : IncomingMessage { public val firstClientType: Int @@ -68,7 +68,7 @@ public class LoginBlock( if (hostPlatformStats != other.hostPlatformStats) return false if (_secondClientType != other._secondClientType) return false if (_crcBlockHeader != other._crcBlockHeader) return false - if (!crc.contentEquals(other.crc)) return false + if (crc != other.crc) return false if (authentication != other.authentication) return false return true @@ -94,7 +94,7 @@ public class LoginBlock( result = 31 * result + hostPlatformStats.hashCode() result = 31 * result + _secondClientType.hashCode() result = 31 * result + _crcBlockHeader.hashCode() - result = 31 * result + crc.contentHashCode() + result = 31 * result + crc.hashCode() result = 31 * result + authentication.hashCode() return result } @@ -112,7 +112,7 @@ public class LoginBlock( "siteSettings='$siteSettings', " + "affiliate=$affiliate, " + "hostPlatformStats=$hostPlatformStats, " + - "crc=${crc.contentToString()}, " + + "crc=$crc, " + "firstClientType=$firstClientType, " + "platformType=$platformType, " + "constZero1=$constZero1, " + diff --git a/protocol/osrs-221-shared/src/main/kotlin/net/rsprot/protocol/common/loginprot/incoming/codec/shared/LoginBlockDecoder.kt b/protocol/osrs-221-shared/src/main/kotlin/net/rsprot/protocol/common/loginprot/incoming/codec/shared/LoginBlockDecoder.kt index 64efbf231..e3ea79260 100644 --- a/protocol/osrs-221-shared/src/main/kotlin/net/rsprot/protocol/common/loginprot/incoming/codec/shared/LoginBlockDecoder.kt +++ b/protocol/osrs-221-shared/src/main/kotlin/net/rsprot/protocol/common/loginprot/incoming/codec/shared/LoginBlockDecoder.kt @@ -3,6 +3,7 @@ package net.rsprot.protocol.common.loginprot.incoming.codec.shared import net.rsprot.buffer.JagByteBuf import net.rsprot.protocol.cryptography.decipherRsa import net.rsprot.protocol.cryptography.xteaDecrypt +import net.rsprot.protocol.loginprot.incoming.util.CyclicRedundancyCheckBlock import net.rsprot.protocol.loginprot.incoming.util.HostPlatformStats import net.rsprot.protocol.loginprot.incoming.util.LoginBlock import java.math.BigInteger @@ -52,11 +53,7 @@ public abstract class LoginBlockDecoder( val hostPlatformStats = decodeHostPlatformStats(xteaBuffer) val secondClientType = xteaBuffer.g1() val crcBlockHeader = xteaBuffer.g4() - // As revision 221 isn't out yet, we will just naively read the values - val crc = - IntArray(21) { - xteaBuffer.g4() - } + val crc = decodeCrc(xteaBuffer) return LoginBlock( version, subVersion, @@ -82,6 +79,46 @@ public abstract class LoginBlockDecoder( ) } + private fun decodeCrc(buffer: JagByteBuf): CyclicRedundancyCheckBlock { + val transmittedCount = 21 + val crc = IntArray(transmittedCount) + crc[0] = buffer.g4Alt1() + crc[4] = buffer.g4Alt3() + crc[15] = buffer.g4Alt3() + crc[17] = buffer.g4Alt1() + crc[13] = buffer.g4Alt2() + crc[16] = buffer.g4Alt1() + crc[1] = buffer.g4Alt1() + crc[10] = buffer.g4() + crc[9] = buffer.g4Alt2() + crc[7] = buffer.g4() + crc[8] = buffer.g4Alt2() + crc[6] = buffer.g4Alt1() + crc[20] = buffer.g4Alt3() + crc[19] = buffer.g4Alt3() + crc[14] = buffer.g4() + crc[12] = buffer.g4() + crc[5] = buffer.g4Alt2() + crc[3] = buffer.g4Alt3() + crc[11] = buffer.g4Alt3() + crc[2] = buffer.g4Alt3() + crc[18] = buffer.g4Alt3() + + return object : CyclicRedundancyCheckBlock(crc) { + override fun validate(serverCrc: IntArray): Boolean { + require(serverCrc.size >= transmittedCount) { + "Server CRC length less than expected: ${serverCrc.size}, expected >= $transmittedCount" + } + for (i in 0..