Add CREATE_ACCOUNT packet

Signed-off-by: Graham <gpe@openrs2.org>
Graham 2 years ago
parent fef6441889
commit 650e298bc9
  1. 15
      crypto/src/main/kotlin/org/openrs2/crypto/Xtea.kt
  2. 2
      protocol/src/main/kotlin/org/openrs2/protocol/ProtocolModule.kt
  3. 132
      protocol/src/main/kotlin/org/openrs2/protocol/login/upstream/CreateAccountCodec.kt
  4. 12
      protocol/src/main/kotlin/org/openrs2/protocol/login/upstream/LoginRequest.kt

@ -1,11 +1,12 @@
package org.openrs2.crypto
import io.netty.buffer.ByteBuf
import java.security.SecureRandom
private const val GOLDEN_RATIO = 0x9E3779B9.toInt()
private const val ROUNDS = 32
private const val BLOCK_SIZE = 8
private const val BLOCK_SIZE_MASK = BLOCK_SIZE - 1
public const val XTEA_BLOCK_SIZE: Int = 8
private const val BLOCK_SIZE_MASK = XTEA_BLOCK_SIZE - 1
public data class XteaKey(
public val k0: Int,
@ -35,6 +36,12 @@ public data class XteaKey(
@JvmStatic
public val ZERO: XteaKey = XteaKey(0, 0, 0, 0)
@JvmStatic
@JvmOverloads
public fun generate(r: SecureRandom = secureRandom): XteaKey {
return XteaKey(r.nextInt(), r.nextInt(), r.nextInt(), r.nextInt())
}
@JvmStatic
public fun fromIntArray(a: IntArray): XteaKey {
require(a.size == 4)
@ -71,7 +78,7 @@ public fun ByteBuf.xteaEncrypt(index: Int, length: Int, key: XteaKey) {
val k = key.toIntArray()
val end = index + (length and BLOCK_SIZE_MASK.inv())
for (i in index until end step BLOCK_SIZE) {
for (i in index until end step XTEA_BLOCK_SIZE) {
var sum = 0
var v0 = getInt(i)
var v1 = getInt(i + 4)
@ -91,7 +98,7 @@ public fun ByteBuf.xteaDecrypt(index: Int, length: Int, key: XteaKey) {
val k = key.toIntArray()
val end = index + (length and BLOCK_SIZE_MASK.inv())
for (i in index until end step BLOCK_SIZE) {
for (i in index until end step XTEA_BLOCK_SIZE) {
@Suppress("INTEGER_OVERFLOW")
var sum = GOLDEN_RATIO * ROUNDS
var v0 = getInt(i)

@ -13,6 +13,7 @@ import org.openrs2.protocol.login.downstream.Js5OkCodec
import org.openrs2.protocol.login.downstream.LoginDownstream
import org.openrs2.protocol.login.downstream.ServerFullCodec
import org.openrs2.protocol.login.upstream.CheckWorldSuitabilityCodec
import org.openrs2.protocol.login.upstream.CreateAccountCodec
import org.openrs2.protocol.login.upstream.CreateCheckDateOfBirthCountryCodec
import org.openrs2.protocol.login.upstream.CreateCheckNameCodec
import org.openrs2.protocol.login.upstream.InitCrossDomainConnectionCodec
@ -44,6 +45,7 @@ public object ProtocolModule : AbstractModule() {
InitJaggrabConnectionCodec::class.java,
CreateCheckDateOfBirthCountryCodec::class.java,
CreateCheckNameCodec::class.java,
CreateAccountCodec::class.java,
RequestWorldListCodec::class.java,
CheckWorldSuitabilityCodec::class.java,
InitCrossDomainConnectionCodec::class.java

@ -0,0 +1,132 @@
package org.openrs2.protocol.login.upstream
import io.netty.buffer.ByteBuf
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters
import org.openrs2.buffer.readString
import org.openrs2.buffer.use
import org.openrs2.buffer.writeString
import org.openrs2.crypto.Rsa
import org.openrs2.crypto.StreamCipher
import org.openrs2.crypto.XTEA_BLOCK_SIZE
import org.openrs2.crypto.XteaKey
import org.openrs2.crypto.publicKey
import org.openrs2.crypto.rsa
import org.openrs2.crypto.secureRandom
import org.openrs2.crypto.xteaDecrypt
import org.openrs2.crypto.xteaEncrypt
import org.openrs2.protocol.VariableShortPacketCodec
import org.openrs2.util.Base37
import java.time.LocalDate
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class CreateAccountCodec @Inject constructor(
private val rsaKey: RSAPrivateCrtKeyParameters
) : VariableShortPacketCodec<LoginRequest.CreateAccount>(
type = LoginRequest.CreateAccount::class.java,
opcode = 22
) {
override fun decode(input: ByteBuf, cipher: StreamCipher): LoginRequest.CreateAccount {
val build = input.readShort().toInt()
val ciphertextLen = input.readUnsignedByte().toInt()
val ciphertext = input.readSlice(ciphertextLen)
ciphertext.rsa(rsaKey).use { plaintext ->
require(plaintext.readUnsignedByte().toInt() == Rsa.MAGIC) {
"Invalid RSA magic"
}
val flags = plaintext.readUnsignedShort()
val gameNewsletters = (flags and FLAG_GAME_NEWSLETTERS) != 0
val otherNewsletters = (flags and FLAG_OTHER_NEWSLETTERS) != 0
val shareDetailsWithBusinessPartners = (flags and FLAG_SHARE_DETAILS_WITH_BUSINESS_PARTNERS) != 0
val username = Base37.decodeLowerCase(plaintext.readLong())
val k0 = plaintext.readInt()
val password = plaintext.readString()
val k1 = plaintext.readInt()
val affiliate = plaintext.readUnsignedShort()
val day = plaintext.readUnsignedByte().toInt()
val month = plaintext.readUnsignedByte().toInt()
val k2 = plaintext.readInt()
val year = plaintext.readUnsignedShort()
val country = plaintext.readUnsignedShort()
val k3 = plaintext.readInt()
val xteaKey = XteaKey(k0, k1, k2, k3)
input.xteaDecrypt(input.readerIndex(), input.readableBytes(), xteaKey)
val email = input.readString()
return LoginRequest.CreateAccount(
build,
gameNewsletters,
otherNewsletters,
shareDetailsWithBusinessPartners,
username,
password,
affiliate,
dateOfBirth = LocalDate.of(year, month + 1, day),
country,
email
)
}
}
override fun encode(input: LoginRequest.CreateAccount, output: ByteBuf, cipher: StreamCipher) {
val xteaKey = XteaKey.generate()
output.writeShort(input.build)
output.alloc().buffer().use { plaintext ->
plaintext.writeByte(Rsa.MAGIC)
var flags = 0
if (input.gameNewsletters) {
flags = flags or FLAG_GAME_NEWSLETTERS
}
if (input.otherNewsletters) {
flags = flags or FLAG_OTHER_NEWSLETTERS
}
if (input.shareDetailsWithBusinessPartners) {
flags = flags or FLAG_SHARE_DETAILS_WITH_BUSINESS_PARTNERS
}
plaintext.writeShort(flags)
plaintext.writeLong(Base37.encode(input.username))
plaintext.writeInt(xteaKey.k0)
plaintext.writeString(input.password)
plaintext.writeInt(xteaKey.k1)
plaintext.writeShort(input.affiliate)
plaintext.writeByte(input.dateOfBirth.dayOfMonth)
plaintext.writeByte(input.dateOfBirth.monthValue - 1)
plaintext.writeInt(xteaKey.k2)
plaintext.writeShort(input.dateOfBirth.year)
plaintext.writeShort(input.country)
plaintext.writeInt(xteaKey.k3)
plaintext.rsa(rsaKey.publicKey).use { ciphertext ->
output.writeByte(ciphertext.readableBytes())
output.writeBytes(ciphertext)
}
}
val xteaIndex = output.writerIndex()
output.writeString(input.email)
while ((output.writerIndex() - xteaIndex) % XTEA_BLOCK_SIZE != 0) {
output.writeByte(secureRandom.nextInt())
}
output.xteaEncrypt(xteaIndex, output.writerIndex() - xteaIndex, xteaKey)
}
private companion object {
private const val FLAG_GAME_NEWSLETTERS = 0x1
private const val FLAG_OTHER_NEWSLETTERS = 0x2
private const val FLAG_SHARE_DETAILS_WITH_BUSINESS_PARTNERS = 0x4
}
}

@ -12,6 +12,18 @@ public sealed class LoginRequest : Packet {
public val country: Int
) : LoginRequest()
public data class CreateCheckName(public val username: String) : LoginRequest()
public data class CreateAccount(
public val build: Int,
public val gameNewsletters: Boolean,
public val otherNewsletters: Boolean,
public val shareDetailsWithBusinessPartners: Boolean,
public val username: String,
public val password: String,
public val affiliate: Int,
public val dateOfBirth: LocalDate,
public val country: Int,
public val email: String
) : LoginRequest()
public data class RequestWorldList(public val checksum: Int) : LoginRequest()
public data class CheckWorldSuitability(
public val build: Int,

Loading…
Cancel
Save