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 javax.inject.Inject import javax.inject.Singleton @Singleton public class CreateAccountCodec @Inject constructor( private val rsaKey: RSAPrivateCrtKeyParameters ) : VariableShortPacketCodec( 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() val padding = input.readableBytes() require(padding in 1..XTEA_BLOCK_SIZE) { "Padding ($padding bytes) must be between 1 and $XTEA_BLOCK_SIZE bytes" } input.skipBytes(padding) return LoginRequest.CreateAccount( build, gameNewsletters, otherNewsletters, shareDetailsWithBusinessPartners, username, password, affiliate, year, month, 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.day) plaintext.writeByte(input.month) plaintext.writeInt(xteaKey.k2) plaintext.writeShort(input.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) val padding = XTEA_BLOCK_SIZE - (output.writerIndex() - xteaIndex) % XTEA_BLOCK_SIZE for (i in 0 until padding) { 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 } }