1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-24 20:43:33 +08:00

Support login cache and login without password. close

This commit is contained in:
Him188 2021-04-10 18:43:47 +08:00
parent 455ed6fbeb
commit f3d3130f2d
26 changed files with 311 additions and 168 deletions

View File

@ -5578,6 +5578,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun getHeartbeatTimeoutMillis ()J
public final fun getHighwayUploadCoroutineCount ()I
public final fun getJson ()Lkotlinx/serialization/json/Json;
public final fun getLoginCacheEnabled ()Z
public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver;
public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1;
public final fun getParentCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
@ -5621,6 +5622,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun setHeartbeatTimeoutMillis (J)V
public final fun setHighwayUploadCoroutineCount (I)V
public final fun setJson (Lkotlinx/serialization/json/Json;)V
public final fun setLoginCacheEnabled (Z)V
public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V
public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
public final fun setParentCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V

View File

@ -5578,6 +5578,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun getHeartbeatTimeoutMillis ()J
public final fun getHighwayUploadCoroutineCount ()I
public final fun getJson ()Lkotlinx/serialization/json/Json;
public final fun getLoginCacheEnabled ()Z
public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver;
public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1;
public final fun getParentCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
@ -5621,6 +5622,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun setHeartbeatTimeoutMillis (J)V
public final fun setHighwayUploadCoroutineCount (I)V
public final fun setJson (Lkotlinx/serialization/json/Json;)V
public final fun setLoginCacheEnabled (Z)V
public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V
public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
public final fun setParentCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V

View File

@ -485,6 +485,20 @@ public open class BotConfiguration { // open for Java
contactListCache.groupMemberListCacheEnabled = true
}
/**
* 登录缓存.
*
* 开始后在密码登录成功时会保存秘钥等信息, 在下次启动时通过这些信息登录, 而不提交密码.
* 可以减少验证码出现的频率.
*
* 秘钥信息会由密码加密保存. 如果秘钥过期, 则会进行普通密码登录.
*
* 默认 `true` (开启).
*
* @since 2.6
*/
public var loginCacheEnabled: Boolean = true
///////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -126,6 +126,9 @@ public inline fun Input.readString(length: UShort, charset: Charset = Charsets.U
public inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length.toInt()), charset = charset)
public fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray())
public fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
public fun File.createFileIfNotExists() {
if (!this.exists()) {
this.parentFile.mkdirs()

View File

@ -13,6 +13,7 @@
package net.mamoe.mirai.utils
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.descriptors.ClassSerialDescriptorBuilder
@ -30,6 +31,16 @@ public fun <T> File.loadNotBlankAs(
return stringFormat.decodeFromString(serializer, this.readText())
}
public fun <T> File.loadNotBlankAs(
serializer: DeserializationStrategy<T>,
binaryFormat: BinaryFormat,
): T? {
if (!this.exists() || this.length() == 0L) {
return null
}
return binaryFormat.decodeFromByteArray(serializer, this.readBytes())
}
public fun SerialDescriptor.copy(newName: String): SerialDescriptor =
buildClassSerialDescriptor(newName) { takeElementsFrom(this@copy) }

View File

@ -22,13 +22,17 @@ import kotlinx.serialization.json.*
import net.mamoe.mirai.*
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
import net.mamoe.mirai.event.events.FriendAddEvent
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
import net.mamoe.mirai.event.events.NewFriendRequestEvent
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
import net.mamoe.mirai.internal.network.highway.*
import net.mamoe.mirai.internal.network.protocol
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg

View File

@ -28,12 +28,17 @@ import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.utils.ScheduledJob
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.friendCacheFile
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.ForwardMessage
import net.mamoe.mirai.message.data.RichMessage
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.*
import java.io.File
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.time.milliseconds
@ -59,18 +64,79 @@ internal class QQAndroidBot constructor(
) : AbstractBot<QQAndroidBotNetworkHandler>(configuration, account.id) {
val bdhSyncer: BdhSessionSyncer = BdhSessionSyncer(this)
///////////////////////////////////////////////////////////////////////////
// Account secrets cache
///////////////////////////////////////////////////////////////////////////
// We cannot extract these logics until we rewrite the network framework.
private val cacheDir: File by lazy {
configuration.workingDir.resolve(bot.configuration.cacheDir).apply { mkdirs() }
}
private val accountSecretsFile: File by lazy {
cacheDir.resolve("account.secrets")
}
private fun saveSecrets(secrets: AccountSecretsImpl) {
if (secrets.wLoginSigInfoField == null) return
accountSecretsFile.writeBytes(
TEA.encrypt(
secrets.toByteArray(AccountSecretsImpl.serializer()),
account.passwordMd5
)
)
network.logger.info { "Saved account secrets to local cache for fast login." }
}
init {
if (configuration.loginCacheEnabled) {
eventChannel.parentScope(this).subscribeAlways<WtLogin.Login.LoginPacketResponse> { event ->
if (event is WtLogin.Login.LoginPacketResponse.Success) {
if (client.wLoginSigInfoInitialized) {
saveSecrets(AccountSecretsImpl(client))
}
}
}
}
}
private fun loadSecretsFromCacheOrCreate(deviceInfo: DeviceInfo): AccountSecrets {
val loaded = if (configuration.loginCacheEnabled && accountSecretsFile.exists()) {
kotlin.runCatching {
TEA.decrypt(accountSecretsFile.readBytes(), account.passwordMd5).loadAs(AccountSecretsImpl.serializer())
}.getOrElse { e ->
logger.error("Failed to load account secrets from local cache. Invalidating cache...", e)
accountSecretsFile.delete()
null
}
} else null
if (loaded != null) {
logger.info { "Loaded account secrets from local cache." }
return loaded
}
return AccountSecretsImpl(deviceInfo, account) // wLoginSigInfoField is null, no need to save.
}
/////////////////////////// accounts secrets end
var client: QQAndroidClient = initClient()
private set
fun initClient(): QQAndroidClient {
val device = configuration.deviceInfo?.invoke(this) ?: DeviceInfo.random()
client = QQAndroidClient(
account,
bot = this,
device = configuration.deviceInfo?.invoke(this) ?: DeviceInfo.random()
device = device,
accountSecrets = loadSecretsFromCacheOrCreate(device)
)
client._bot = this
return client
}
override val bot: QQAndroidBot get() = this
internal var firstLoginSucceed: Boolean = false

View File

@ -0,0 +1,83 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network
import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.get_mpasswd
import net.mamoe.mirai.internal.utils.io.ProtoBuf
import net.mamoe.mirai.utils.DeviceInfo
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toByteArray
import java.util.concurrent.CopyOnWriteArraySet
internal interface AccountSecrets {
var wLoginSigInfoField: WLoginSigInfo?
val wLoginSigInfoInitialized get() = wLoginSigInfoField != null
var wLoginSigInfo
get() = wLoginSigInfoField ?: error("wLoginSigInfoField is not yet initialized")
set(value) {
wLoginSigInfoField = value
}
/**
* t537
*/
var loginExtraData: MutableSet<LoginExtraData>
var G: ByteArray // sigInfo[2]
var dpwd: ByteArray
var randSeed: ByteArray // t403
/**
* t108 时更新
*/
var ksid: ByteArray
var tgtgtKey: ByteArray
val randomKey: ByteArray
}
@Serializable
internal class AccountSecretsImpl(
override var loginExtraData: MutableSet<LoginExtraData>,
override var wLoginSigInfoField: WLoginSigInfo?,
override var G: ByteArray,
override var dpwd: ByteArray,
override var randSeed: ByteArray,
override var ksid: ByteArray,
override var tgtgtKey: ByteArray,
override val randomKey: ByteArray,
) : AccountSecrets, ProtoBuf
internal fun AccountSecretsImpl(
other: AccountSecrets,
): AccountSecretsImpl = other.run {
AccountSecretsImpl(loginExtraData, wLoginSigInfoField, G, dpwd, randSeed, ksid, tgtgtKey, randomKey)
}
internal fun AccountSecretsImpl(
device: DeviceInfo, account: BotAccount
): AccountSecretsImpl {
return AccountSecretsImpl(
loginExtraData = CopyOnWriteArraySet(),
wLoginSigInfoField = null,
G = device.guid,
dpwd = get_mpasswd().toByteArray(),
randSeed = EMPTY_BYTE_ARRAY,
ksid = EMPTY_BYTE_ARRAY,
tgtgtKey = (account.passwordMd5 + ByteArray(4) + account.id.toInt().toByteArray()).md5(),
randomKey = getRandomByteArray(16),
)
}

View File

@ -26,7 +26,6 @@ import net.mamoe.mirai.internal.network.protocol.SyncingCacheList
import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.Tlv
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.get_mpasswd
import net.mamoe.mirai.internal.utils.MiraiProtocolInternal
import net.mamoe.mirai.internal.utils.NetworkType
import net.mamoe.mirai.internal.utils.crypto.ECDH
@ -71,12 +70,11 @@ internal open class QQAndroidClient(
val account: BotAccount,
val ecdh: ECDH = ECDH(),
val device: DeviceInfo,
bot: QQAndroidBot
) {
val protocol = MiraiProtocolInternal[bot.configuration.protocol]
accountSecrets: AccountSecrets
) : AccountSecrets by accountSecrets {
lateinit var _bot: QQAndroidBot
val bot: QQAndroidBot get() = _bot
val subAppId: Long
get() = protocol.id
internal var strangerSeq: Int = 0
@ -84,15 +82,9 @@ internal open class QQAndroidClient(
var onlineStatus: OnlineStatus = OnlineStatus.ONLINE
val bot: QQAndroidBot by bot.unsafeWeakRef()
internal var tgtgtKey: ByteArray = (account.passwordMd5 + ByteArray(4) + uin.toInt().toByteArray()).md5()
internal val randomKey: ByteArray = getRandomByteArray(16)
internal val miscBitMap: Int = protocol.miscBitMap // 184024956 // 也可能是 150470524 ?
internal val mainSigMap: Int = protocol.mainSigMap
internal var subSigMap: Int = protocol.subSigMap // 0x10400 //=66,560
internal val miscBitMap: Int get() = protocol.miscBitMap // 184024956 // 也可能是 150470524 ?
internal val mainSigMap: Int get() = protocol.mainSigMap
private val _ssoSequenceId: AtomicInt = atomic(85600)
@ -101,14 +93,10 @@ internal open class QQAndroidClient(
@MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket")
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
var openAppId: Long = 715019303L
val apkVersionName: ByteArray get() = protocol.ver.toByteArray() //"8.4.18".toByteArray()
val buildVer: String get() = "8.4.18.4810" // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410
val clientVersion: String = "android ${protocol.ver}" // android 8.5.0
val buildTime: Long get() = protocol.buildTime
val sdkVersion: String get() = protocol.sdkVer
private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
@ -127,6 +115,7 @@ internal open class QQAndroidClient(
return friendSeq.compareAndSet(compare, id % 65535)
}
private val requestPacketRequestId: AtomicInt = atomic(1921334513)
internal fun nextRequestPacketRequestId(): Int = requestPacketRequestId.getAndAdd(2)
@ -139,17 +128,15 @@ internal open class QQAndroidClient(
private val highwayDataTransSequenceIdForApplyUp: AtomicInt = atomic(77918)
internal fun nextHighwayDataTransSequenceIdForApplyUp(): Int = highwayDataTransSequenceIdForApplyUp.getAndAdd(2)
val appClientVersion: Int = 0
val ssoVersion: Int = 15
var networkType: NetworkType = NetworkType.WIFI
val apkSignatureMd5: ByteArray get() = protocol.sign.hexToBytes() // "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes()
/**
* 协议版本?, 8.2.7 的为 8001
*/
val protocolVersion: Short = 8001
internal val groupConfig: GroupConfig = GroupConfig()
@ -214,6 +201,7 @@ internal open class QQAndroidClient(
val pendingGroupMessageReceiptCacheList = SyncingCacheList<PendingGroupMessageReceiptSyncId>(50)
}
val syncingController = MessageSvcSyncData()
/*
@ -248,44 +236,27 @@ internal open class QQAndroidClient(
var t530: ByteArray? = null
var t528: ByteArray? = null
/**
* t108 时更新
*/
var ksid: ByteArray = EMPTY_BYTE_ARRAY
/**
* t186
*/
var pwdFlag: Boolean = false
/**
* t537
*/
var loginExtraData: MutableSet<LoginExtraData> = CopyOnWriteArraySet()
lateinit var wFastLoginInfo: WFastLoginInfo
var reserveUinInfo: ReserveUinInfo? = null
lateinit var wLoginSigInfo: WLoginSigInfo
val wLoginSigInfoInitialized get() = ::wLoginSigInfo.isInitialized
var G: ByteArray = device.guid // sigInfo[2]
var dpwd: ByteArray = get_mpasswd().toByteArray()
var randSeed: ByteArray = EMPTY_BYTE_ARRAY // t403
var tlv113: ByteArray? = null
var t402: ByteArray? = null
lateinit var qrPushSig: ByteArray
lateinit var mainDisplayName: ByteArray
var transportSequenceId = 1
var lastT106Full: ByteArray? = null
lateinit var t104: ByteArray
}
internal val QQAndroidClient.clientVersion: String get() = "android ${protocol.ver}" // android 8.5.0
internal val QQAndroidClient.protocol get() = MiraiProtocolInternal[bot.configuration.protocol]
internal val QQAndroidClient.sdkVersion: String get() = protocol.sdkVer
internal val QQAndroidClient.buildTime: Long get() = protocol.buildTime
internal val QQAndroidClient.subAppId: Long get() = protocol.id
internal val QQAndroidClient.apkSignatureMd5: ByteArray get() = protocol.sign.hexToBytes() // "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes()
internal val QQAndroidClient.subSigMap: Int get() = protocol.subSigMap // 0x10400 //=66,560
internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraData) {
loginExtraData.run {
writeLong(uin)

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.network.BdhSession
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.subAppId
import net.mamoe.mirai.internal.utils.PlatformSocket
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf

View File

@ -10,9 +10,7 @@
package net.mamoe.mirai.internal.network
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUShort
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.internal.network.getRandomByteArray
import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger
@ -44,6 +42,7 @@ internal class WFastLoginInfo(
}
}
@Serializable
internal class WLoginSimpleInfo(
val uin: Long, // uin
val imgType: ByteArray,
@ -56,6 +55,7 @@ internal class WLoginSimpleInfo(
}
}
@Serializable
internal class LoginExtraData(
val uin: Long,
val ip: ByteArray,
@ -67,6 +67,7 @@ internal class LoginExtraData(
}
}
@Serializable
internal class WLoginSigInfo(
val uin: Long,
var encryptA1: ByteArray?, // sigInfo[0]
@ -83,34 +84,34 @@ internal class WLoginSigInfo(
var tgt: ByteArray,
var a2CreationTime: Long,
var tgtKey: ByteArray,
var userStSig: UserStSig,
var userStSig: KeyWithCreationTime,
/**
* TransEmpPacket 加密使用
*/
var userStKey: ByteArray,
var userStWebSig: UserStWebSig,
var userA5: UserA5,
var userA8: UserA8,
var lsKey: LSKey,
var sKey: SKey,
var userSig64: UserSig64,
var userStWebSig: KeyWithExpiry,
var userA5: KeyWithCreationTime,
var userA8: KeyWithExpiry,
var lsKey: KeyWithExpiry,
var sKey: KeyWithExpiry,
var userSig64: KeyWithCreationTime,
var openId: ByteArray,
var openKey: OpenKey,
var vKey: VKey,
var accessToken: AccessToken,
var d2: D2,
var openKey: KeyWithCreationTime,
var vKey: KeyWithExpiry,
var accessToken: KeyWithCreationTime,
var d2: KeyWithExpiry,
var d2Key: ByteArray,
var sid: Sid,
var aqSig: AqSig,
var sid: KeyWithExpiry,
var aqSig: KeyWithCreationTime,
var psKeyMap: PSKeyMap,
var pt4TokenMap: Pt4TokenMap,
var pt4TokenMap: MutableMap<String, KeyWithExpiry> = mutableMapOf(), // = Pt4TokenMap maybe compiler bug
var superKey: ByteArray,
var payToken: ByteArray,
var pf: ByteArray,
var pfKey: ByteArray,
var da2: ByteArray,
// val pt4Token: ByteArray,
var wtSessionTicket: WtSessionTicket,
var wtSessionTicket: KeyWithCreationTime,
var wtSessionTicketKey: ByteArray,
var deviceToken: ByteArray,
var encryptedDownloadSession: EncryptedDownloadSession? = null
@ -130,41 +131,8 @@ internal class WLoginSigInfo(
}
}
internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
internal fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray())
internal fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal typealias PSKeyMap = MutableMap<String, KeyWithExpiry>
internal typealias Pt4TokenMap = MutableMap<String, KeyWithExpiry>
internal fun parsePSKeyMapAndPt4TokenMap(
data: ByteArray,
@ -180,20 +148,16 @@ internal fun parsePSKeyMapAndPt4TokenMap(
val pt4token = readUShortLVByteArray()
when {
psKey.isNotEmpty() -> outPSKeyMap[domain] = PSKey(psKey, creationTime, expireTime)
pt4token.isNotEmpty() -> outPt4TokenMap[domain] = Pt4Token(pt4token, creationTime, expireTime)
psKey.isNotEmpty() -> outPSKeyMap[domain] = KeyWithExpiry(psKey, creationTime, expireTime)
pt4token.isNotEmpty() -> outPt4TokenMap[domain] = KeyWithExpiry(pt4token, creationTime, expireTime)
}
}
}
internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
@Serializable
internal open class KeyWithExpiry(
data: ByteArray,
creationTime: Long,
@SerialName("data1") override val data: ByteArray,
@SerialName("creationTime1") override val creationTime: Long,
val expireTime: Long
) : KeyWithCreationTime(data, creationTime) {
override fun toString(): String {
@ -201,9 +165,10 @@ internal open class KeyWithExpiry(
}
}
@Serializable
internal open class KeyWithCreationTime(
val data: ByteArray,
val creationTime: Long
open val data: ByteArray,
open val creationTime: Long
) {
override fun toString(): String {
return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)"

View File

@ -16,8 +16,8 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.utils.io.encryptAndWrite
import net.mamoe.mirai.internal.utils.io.writeHex
import net.mamoe.mirai.internal.utils.io.writeIntLVPacket
@ -286,7 +286,7 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
val body = encryptMethod.makeBody(client, bodyBlock)
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
writeShort(8001)
writeShort(commandId.toShort())
writeShort(1) // const??
writeInt(client.uin.toInt())

View File

@ -26,7 +26,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
import net.mamoe.mirai.internal.network.readUShortLVByteArray
import net.mamoe.mirai.internal.network.tryDecryptOrNull
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey

View File

@ -15,6 +15,7 @@ import kotlinx.io.core.*
import net.mamoe.mirai.internal.network.LoginExtraData
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.LoginType
import net.mamoe.mirai.internal.network.subAppId
import net.mamoe.mirai.internal.network.writeLoginExtraData
import net.mamoe.mirai.internal.utils.GuidSource
import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag

View File

@ -13,6 +13,7 @@ import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.clientVersion
import net.mamoe.mirai.internal.network.protocol.data.proto.OidbCmd0xb77
import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.*
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.internal.network.subAppId
import net.mamoe.mirai.internal.utils.io.serialization.*
import net.mamoe.mirai.utils.daysToSeconds

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -14,6 +14,7 @@ import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.subAppId
internal class Heartbeat {

View File

@ -27,10 +27,7 @@ import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.appId
import net.mamoe.mirai.internal.createOtherClient
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.FriendListCache
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.getRandomByteArray
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.protocol.data.jce.*
import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x769
import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcGetOnline

View File

@ -11,6 +11,9 @@ package net.mamoe.mirai.internal.network.protocol.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.DebuggingProperties.SHOW_TLV_MAP_ON_LOGIN_SUCCESS
@ -95,43 +98,55 @@ internal class WtLogin {
}
sealed class LoginPacketResponse : Packet {
object Success : LoginPacketResponse() {
sealed class LoginPacketResponse : Packet, BotEvent, Packet.NoEventLog, AbstractEvent() {
class Success(override val bot: Bot) : LoginPacketResponse() {
override fun toString(): String = "LoginPacketResponse.Success"
}
data class Error(
override val bot: Bot,
val code: Int,
val title: String,
val message: String,
val errorInfo: String
val errorInfo: String,
) : LoginPacketResponse()
sealed class Captcha : LoginPacketResponse() {
class Slider(
val url: String
override val bot: Bot,
val url: String,
) : Captcha() {
override fun toString(): String = "LoginPacketResponse.Captcha.Slider"
}
class Picture(
override val bot: Bot,
val data: ByteArray,
val sign: ByteArray
val sign: ByteArray,
) : Captcha() {
override fun toString(): String = "LoginPacketResponse.Captcha.Picture"
}
}
data class UnsafeLogin(val url: String) : LoginPacketResponse()
data class UnsafeLogin(
override val bot: Bot,
val url: String,
) : LoginPacketResponse()
class SMSVerifyCodeNeeded(val t402: ByteArray, val t403: ByteArray) : LoginPacketResponse() {
class SMSVerifyCodeNeeded(
override val bot: Bot,
val t402: ByteArray,
val t403: ByteArray,
) : LoginPacketResponse() {
override fun toString(): String {
return "LoginPacketResponse.SMSVerifyCodeNeeded(t402=${t402.toUHexString()}, t403=${t403.toUHexString()})"
}
}
object DeviceLockLogin : LoginPacketResponse() {
class DeviceLockLogin(
override val bot: Bot,
) : LoginPacketResponse() {
override fun toString(): String = "WtLogin.Login.LoginPacketResponse.DeviceLockLogin"
}
}
@ -165,11 +180,11 @@ internal class WtLogin {
return when (type.toInt()) {
0 -> onLoginSuccess(subCommand, tlvMap, bot)
2 -> onSolveLoginCaptcha(tlvMap, bot)
160, 239 /*-96*/ -> onUnsafeDeviceLogin(tlvMap)
160, 239 /*-96*/ -> onUnsafeDeviceLogin(tlvMap, bot)
204 /*-52*/ -> onDevLockLogin(tlvMap, bot)
// 1, 15 -> onErrorMessage(tlvMap) ?: error("Cannot find error message")
else -> {
onErrorMessage(type.toInt(), tlvMap)
onErrorMessage(type.toInt(), tlvMap, bot)
?: error("Cannot find error message, unknown login result type: $type, TLVMap = ${tlvMap._miraiContentToString()}")
}
}
@ -186,11 +201,11 @@ internal class WtLogin {
client.G = (client.device.guid + client.dpwd + tlvMap.getOrFail(0x402)).md5()
}
// println("403 " + tlvMap[0x403]?.toUHexString())
return LoginPacketResponse.DeviceLockLogin
return LoginPacketResponse.DeviceLockLogin(bot)
}
private fun onUnsafeDeviceLogin(tlvMap: TlvMap): LoginPacketResponse.UnsafeLogin {
return LoginPacketResponse.UnsafeLogin(tlvMap.getOrFail(0x204).encodeToString())
private fun onUnsafeDeviceLogin(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.UnsafeLogin {
return LoginPacketResponse.UnsafeLogin(bot, tlvMap.getOrFail(0x204).encodeToString())
}
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
@ -207,7 +222,7 @@ internal class WtLogin {
// val ret = tlvMap[0x104]?.let { println(it.toUHexString()) }
bot.client.t104 = tlvMap.getOrFail(0x104)
tlvMap[0x192]?.let {
return LoginPacketResponse.Captcha.Slider(it.encodeToString())
return LoginPacketResponse.Captcha.Slider(bot, it.encodeToString())
}
tlvMap[0x165]?.let {
// if (question[18].toInt() == 0x36) {
@ -220,8 +235,8 @@ internal class WtLogin {
val sign = imageData.readBytes(signInfoLength.toInt())
LoginPacketResponse.Captcha.Picture(
data = imageData.readBytes(),
sign = sign
bot,
data = imageData.readBytes(), sign = sign
)
}
// } else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString())
@ -374,7 +389,7 @@ internal class WtLogin {
if (client.wLoginSigInfoInitialized) {
client.wLoginSigInfo.apply {
superKey = tlvMap119.getOrDefault(0x16d, superKey)
d2 = D2(
d2 = KeyWithExpiry(
tlvMap119.getOrDefault(0x143, d2.data),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x143, 1728000L)
@ -383,13 +398,14 @@ internal class WtLogin {
tgt = tlvMap119.getOrDefault(0x10a, tgt)
tgtKey = tlvMap119.getOrDefault(0x10d, tgtKey)
a2ExpiryTime = creationTime + changeTokenTimeMap.getOrDefault(0x10a, 2160000L)
userStWebSig = UserStWebSig(
userStWebSig = KeyWithExpiry(
tlvMap119.getOrDefault(0x103, userStWebSig.data),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x103, 6000L)
)
userStKey = tlvMap119.getOrDefault(0x10e, userStKey)
userStSig = UserStSig((tlvMap119.getOrDefault(0x114, userStSig.data)), creationTime)
userStSig =
KeyWithCreationTime((tlvMap119.getOrDefault(0x114, userStSig.data)), creationTime)
appPri = tlvMap119[0x11f]?.let {
it.read {
//change interval (int time)
@ -398,12 +414,12 @@ internal class WtLogin {
}
}
?: appPri
sKey = SKey(
sKey = KeyWithExpiry(
tlvMap119.getOrEmpty(0x120),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x120, 86400L)
)
wtSessionTicket = WtSessionTicket(
wtSessionTicket = KeyWithCreationTime(
tlvMap119.getOrDefault(
0x133,
client.wLoginSigInfo.wtSessionTicket.data
@ -454,50 +470,50 @@ internal class WtLogin {
tgt = tlvMap119.getOrFail(0x10a),
a2CreationTime = creationTime,
tgtKey = tlvMap119.getOrEmpty(0x10d), // from asyncContext._login_bitmap
userStSig = UserStSig((tlvMap119.getOrEmpty(0x114)), creationTime),
userStSig = KeyWithCreationTime((tlvMap119.getOrEmpty(0x114)), creationTime),
userStKey = tlvMap119.getOrEmpty(0x10e),
userStWebSig = UserStWebSig(
userStWebSig = KeyWithExpiry(
tlvMap119.getOrEmpty(0x103),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x103, 6000L)
),
userA5 = UserA5(tlvMap119.getOrEmpty(0x10b), creationTime),
userA8 = UserA8(
userA5 = KeyWithCreationTime(tlvMap119.getOrEmpty(0x10b), creationTime),
userA8 = KeyWithExpiry(
tlvMap119.getOrEmpty(0x102),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x102, 72000L)
),
lsKey = LSKey(
lsKey = KeyWithExpiry(
tlvMap119.getOrEmpty(0x11c),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x11c, 1641600L)
),
sKey = SKey(
sKey = KeyWithExpiry(
tlvMap119.getOrEmpty(0x120),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x120, 86400L)
),
userSig64 = UserSig64(tlvMap119.getOrEmpty(0x121), creationTime),
userSig64 = KeyWithCreationTime(tlvMap119.getOrEmpty(0x121), creationTime),
openId = openId.orEmpty(),
openKey = OpenKey(openKey.orEmpty(), creationTime),
vKey = VKey(
openKey = KeyWithCreationTime(openKey.orEmpty(), creationTime),
vKey = KeyWithExpiry(
tlvMap119.getOrEmpty(0x136),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x136, 1728000L)
),
accessToken = AccessToken(tlvMap119.getOrEmpty(0x136), creationTime),
d2 = D2(
accessToken = KeyWithCreationTime(tlvMap119.getOrEmpty(0x136), creationTime),
d2 = KeyWithExpiry(
tlvMap119.getOrFail(0x143),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x143, 1728000L)
),
d2Key = tlvMap119.getOrEmpty(0x305),
sid = Sid(
sid = KeyWithExpiry(
tlvMap119.getOrEmpty(0x164),
creationTime,
creationTime + changeTokenTimeMap.getOrDefault(0x164, 1728000L)
),
aqSig = AqSig(tlvMap119.getOrEmpty(0x171), creationTime),
aqSig = KeyWithCreationTime(tlvMap119.getOrEmpty(0x171), creationTime),
psKeyMap = outPSKeyMap.orEmpty().toMutableMap(),
pt4TokenMap = outPt4TokenMap.orEmpty().toMutableMap(),
superKey = tlvMap119.getOrEmpty(0x16d),
@ -505,7 +521,7 @@ internal class WtLogin {
pf = pf.orEmpty(),
pfKey = pfKey.orEmpty(),
da2 = tlvMap119.getOrEmpty(0x203),
wtSessionTicket = WtSessionTicket(tlvMap119.getOrEmpty(0x133), creationTime),
wtSessionTicket = KeyWithCreationTime(tlvMap119.getOrEmpty(0x133), creationTime),
wtSessionTicketKey = tlvMap119.getOrEmpty(0x134),
deviceToken = tlvMap119.getOrEmpty(0x322),
encryptedDownloadSession = tlvMap119[0x11d]?.let {
@ -517,7 +533,7 @@ internal class WtLogin {
}
}
return LoginPacketResponse.Success
return LoginPacketResponse.Success(bot)
}
}

View File

@ -9,7 +9,7 @@
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.utils.GuidSource

View File

@ -9,7 +9,7 @@
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray

View File

@ -12,6 +12,8 @@ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.subAppId
import net.mamoe.mirai.internal.network.subSigMap
internal object WtLogin2 : WtLoginExt {

View File

@ -12,6 +12,8 @@ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.subAppId
import net.mamoe.mirai.internal.network.subSigMap
internal object WtLogin20 : WtLoginExt {
operator fun invoke(

View File

@ -10,7 +10,7 @@
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import kotlinx.io.core.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.LoginExtraData
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.WLoginSigInfo
@ -17,7 +18,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.Tlv
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.protocol.packet.t145
import net.mamoe.mirai.internal.network.readUShortLVByteArray
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray
import net.mamoe.mirai.utils.*
@ -41,14 +41,14 @@ internal inline fun WtLoginExt.analysisTlv0x531(
internal interface WtLoginExt { // so as not to register to global extension
fun onErrorMessage(type: Int, tlvMap: TlvMap): WtLogin.Login.LoginPacketResponse.Error? {
fun onErrorMessage(type: Int, tlvMap: TlvMap, bot: QQAndroidBot): WtLogin.Login.LoginPacketResponse.Error? {
return tlvMap[0x149]?.read {
discardExact(2) //type
val title: String = readUShortLVString()
val content: String = readUShortLVString()
val otherInfo: String = readUShortLVString()
WtLogin.Login.LoginPacketResponse.Error(type, title, content, otherInfo)
WtLogin.Login.LoginPacketResponse.Error(bot, type, title, content, otherInfo)
} ?: tlvMap[0x146]?.read {
discardExact(2) // ver
discardExact(2) // code
@ -57,7 +57,7 @@ internal interface WtLoginExt { // so as not to register to global extension
val message = readUShortLVString()
val errorInfo = readUShortLVString()
WtLogin.Login.LoginPacketResponse.Error(type, title, message, errorInfo)
WtLogin.Login.LoginPacketResponse.Error(bot, type, title, message, errorInfo)
}
}

View File

@ -20,6 +20,7 @@ import net.mamoe.mirai.internal.contact.groupCode
import net.mamoe.mirai.internal.message.FileMessageImpl
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.protocol
import net.mamoe.mirai.internal.network.protocol.data.proto.*
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult