mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-23 13:50:12 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
This commit is contained in:
commit
ed99f8f84c
@ -25,9 +25,11 @@ enum class JceCharset(val kotlinCharset: Charset) {
|
||||
UTF8(Charset.forName("UTF8"))
|
||||
}
|
||||
|
||||
|
||||
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
|
||||
|
||||
/**
|
||||
* Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray]
|
||||
*/
|
||||
class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
|
||||
|
||||
private inner class ListWriter(
|
||||
@ -152,7 +154,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
this.writeHead(STRUCT_END, 0)
|
||||
}
|
||||
} else if (value is ProtoBuf) {
|
||||
this.encodeTaggedByteArray(popTag(), net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.dump(value))
|
||||
this.encodeTaggedByteArray(popTag(), ProtoBufWithNullableSupport.dump(value))
|
||||
} else {
|
||||
serializer.serialize(this, value)
|
||||
}
|
||||
@ -417,7 +419,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
|
||||
println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
|
||||
// println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
|
||||
if (deserializer is NullReader) {
|
||||
return null
|
||||
}
|
||||
@ -444,7 +446,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
else input.readByteArray(tag).toMutableList() as T
|
||||
}
|
||||
val tag = popTag()
|
||||
println(tag)
|
||||
// println(tag)
|
||||
@Suppress("SENSELESS_COMPARISON") // false positive
|
||||
if (input.skipToTagOrNull(tag) {
|
||||
return deserializer.deserialize(JceListReader(input.readInt(0), input))
|
||||
|
@ -6,16 +6,12 @@ package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.io.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.CompositeDecoder.Companion.READ_DONE
|
||||
import kotlinx.serialization.internal.*
|
||||
import kotlinx.serialization.modules.EmptyModule
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import kotlinx.serialization.protobuf.ProtoNumberType
|
||||
import kotlinx.serialization.protobuf.ProtoType
|
||||
import kotlinx.serialization.protobuf.ProtobufDecodingException
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeSignedVarintInt
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeSignedVarintLong
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeVarint
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint
|
||||
|
||||
internal typealias ProtoDesc = Pair<Int, ProtoNumberType>
|
||||
@ -28,6 +24,12 @@ internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefa
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 带有 null (optional) support 的 Protocol buffers 序列化器.
|
||||
* 所有的为 null 的属性都将不会被序列化. 以此实现可选属性.
|
||||
*
|
||||
* 代码复制自 kotlinx.serialization. 修改部分已进行标注 (详见 "MIRAI MODIFY START")
|
||||
*/
|
||||
class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
|
||||
|
||||
internal open inner class ProtobufWriter(val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() {
|
||||
@ -171,178 +173,6 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
|
||||
}
|
||||
}
|
||||
|
||||
private open inner class ProtobufReader(val decoder: ProtobufDecoder) : TaggedDecoder<ProtoDesc>() {
|
||||
override val context: SerialModule
|
||||
get() = this@ProtoBufWithNullableSupport.context
|
||||
|
||||
private val indexByTag: MutableMap<Int, Int> = mutableMapOf()
|
||||
private fun findIndexByTag(desc: SerialDescriptor, serialId: Int, zeroBasedDefault: Boolean = false): Int =
|
||||
(0 until desc.elementsCount).firstOrNull {
|
||||
extractParameters(
|
||||
desc,
|
||||
it,
|
||||
zeroBasedDefault
|
||||
).first == serialId
|
||||
} ?: -1
|
||||
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder = when (desc.kind) {
|
||||
StructureKind.LIST -> RepeatedReader(decoder, currentTag)
|
||||
StructureKind.CLASS, UnionKind.OBJECT, is PolymorphicKind ->
|
||||
ProtobufReader(makeDelimited(decoder, currentTagOrNull))
|
||||
StructureKind.MAP -> MapEntryReader(makeDelimited(decoder, currentTagOrNull), currentTagOrNull)
|
||||
else -> throw SerializationException("Primitives are not supported at top-level")
|
||||
}
|
||||
|
||||
override fun decodeTaggedBoolean(tag: ProtoDesc): Boolean = when (val i = decoder.nextInt(ProtoNumberType.DEFAULT)) {
|
||||
0 -> false
|
||||
1 -> true
|
||||
else -> throw ProtobufDecodingException("Expected boolean value (0 or 1), found $i")
|
||||
}
|
||||
|
||||
override fun decodeTaggedByte(tag: ProtoDesc): Byte = decoder.nextInt(tag.second).toByte()
|
||||
override fun decodeTaggedShort(tag: ProtoDesc): Short = decoder.nextInt(tag.second).toShort()
|
||||
override fun decodeTaggedInt(tag: ProtoDesc): Int = decoder.nextInt(tag.second)
|
||||
override fun decodeTaggedLong(tag: ProtoDesc): Long = decoder.nextLong(tag.second)
|
||||
override fun decodeTaggedFloat(tag: ProtoDesc): Float = decoder.nextFloat()
|
||||
override fun decodeTaggedDouble(tag: ProtoDesc): Double = decoder.nextDouble()
|
||||
override fun decodeTaggedChar(tag: ProtoDesc): Char = decoder.nextInt(tag.second).toChar()
|
||||
override fun decodeTaggedString(tag: ProtoDesc): String = decoder.nextString()
|
||||
override fun decodeTaggedEnum(tag: ProtoDesc, enumDescription: SerialDescriptor): Int =
|
||||
findIndexByTag(enumDescription, decoder.nextInt(ProtoNumberType.DEFAULT), zeroBasedDefault = true)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when {
|
||||
// encode maps as collection of map entries, not merged collection of key-values
|
||||
deserializer.descriptor is MapLikeDescriptor -> {
|
||||
val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
|
||||
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
|
||||
val setOfEntries = HashSetSerializer(mapEntrySerial).deserialize(this)
|
||||
setOfEntries.associateBy({ it.key }, { it.value }) as T
|
||||
}
|
||||
deserializer.descriptor == ByteArraySerializer.descriptor -> decoder.nextObject() as T
|
||||
else -> deserializer.deserialize(this)
|
||||
}
|
||||
|
||||
override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
|
||||
|
||||
override fun decodeElementIndex(desc: SerialDescriptor): Int {
|
||||
while (true) {
|
||||
if (decoder.curId == -1) // EOF
|
||||
return READ_DONE
|
||||
val ind = indexByTag.getOrPut(decoder.curId) { findIndexByTag(desc, decoder.curId) }
|
||||
if (ind == -1) // not found
|
||||
decoder.skipElement()
|
||||
else return ind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class RepeatedReader(decoder: ProtobufDecoder, val targetTag: ProtoDesc) : ProtobufReader(decoder) {
|
||||
private var ind = -1
|
||||
|
||||
override fun decodeElementIndex(desc: SerialDescriptor) = if (decoder.curId == targetTag.first) ++ind else READ_DONE
|
||||
override fun SerialDescriptor.getTag(index: Int): ProtoDesc = targetTag
|
||||
}
|
||||
|
||||
private inner class MapEntryReader(decoder: ProtobufDecoder, val parentTag: ProtoDesc?) : ProtobufReader(decoder) {
|
||||
override fun SerialDescriptor.getTag(index: Int): ProtoDesc =
|
||||
if (index % 2 == 0) 1 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
|
||||
else 2 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
|
||||
}
|
||||
|
||||
internal class ProtobufDecoder(val inp: ByteArrayInputStream) {
|
||||
val curId
|
||||
get() = curTag.first
|
||||
private var curTag: Pair<Int, Int> = -1 to -1
|
||||
|
||||
init {
|
||||
readTag()
|
||||
}
|
||||
|
||||
private fun readTag(): Pair<Int, Int> {
|
||||
val header = decode32(eofAllowed = true)
|
||||
curTag = if (header == -1) {
|
||||
-1 to -1
|
||||
} else {
|
||||
val wireType = header and 0b111
|
||||
val fieldId = header ushr 3
|
||||
fieldId to wireType
|
||||
}
|
||||
return curTag
|
||||
}
|
||||
|
||||
fun skipElement() {
|
||||
when (curTag.second) {
|
||||
VARINT -> nextInt(ProtoNumberType.DEFAULT)
|
||||
i64 -> nextLong(ProtoNumberType.FIXED)
|
||||
SIZE_DELIMITED -> nextObject()
|
||||
i32 -> nextInt(ProtoNumberType.FIXED)
|
||||
else -> throw ProtobufDecodingException("Unsupported start group or end group wire type")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun assertWireType(expected: Int) {
|
||||
if (curTag.second != expected) throw ProtobufDecodingException("Expected wire type $expected, but found ${curTag.second}")
|
||||
}
|
||||
|
||||
fun nextObject(): ByteArray {
|
||||
assertWireType(SIZE_DELIMITED)
|
||||
val len = decode32()
|
||||
check(len >= 0)
|
||||
val ans = inp.readExactNBytes(len)
|
||||
readTag()
|
||||
return ans
|
||||
}
|
||||
|
||||
fun nextInt(format: ProtoNumberType): Int {
|
||||
val wireType = if (format == ProtoNumberType.FIXED) i32 else VARINT
|
||||
assertWireType(wireType)
|
||||
val ans = decode32(format)
|
||||
readTag()
|
||||
return ans
|
||||
}
|
||||
|
||||
fun nextLong(format: ProtoNumberType): Long {
|
||||
val wireType = if (format == ProtoNumberType.FIXED) i64 else VARINT
|
||||
assertWireType(wireType)
|
||||
val ans = decode64(format)
|
||||
readTag()
|
||||
return ans
|
||||
}
|
||||
|
||||
fun nextFloat(): Float {
|
||||
assertWireType(i32)
|
||||
val ans = inp.readToByteBuffer(4).order(ByteOrder.LITTLE_ENDIAN).getFloat()
|
||||
readTag()
|
||||
return ans
|
||||
}
|
||||
|
||||
fun nextDouble(): Double {
|
||||
assertWireType(i64)
|
||||
val ans = inp.readToByteBuffer(8).order(ByteOrder.LITTLE_ENDIAN).getDouble()
|
||||
readTag()
|
||||
return ans
|
||||
}
|
||||
|
||||
fun nextString(): String {
|
||||
val bytes = this.nextObject()
|
||||
return stringFromUtf8Bytes(bytes)
|
||||
}
|
||||
|
||||
private fun decode32(format: ProtoNumberType = ProtoNumberType.DEFAULT, eofAllowed: Boolean = false): Int = when (format) {
|
||||
ProtoNumberType.DEFAULT -> decodeVarint(inp, 64, eofAllowed).toInt()
|
||||
ProtoNumberType.SIGNED -> decodeSignedVarintInt(inp)
|
||||
ProtoNumberType.FIXED -> inp.readToByteBuffer(4).order(ByteOrder.LITTLE_ENDIAN).getInt()
|
||||
}
|
||||
|
||||
private fun decode64(format: ProtoNumberType = ProtoNumberType.DEFAULT): Long = when (format) {
|
||||
ProtoNumberType.DEFAULT -> decodeVarint(inp, 64)
|
||||
ProtoNumberType.SIGNED -> decodeSignedVarintLong(inp)
|
||||
ProtoNumberType.FIXED -> inp.readToByteBuffer(8).order(ByteOrder.LITTLE_ENDIAN).getLong()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Source for all varint operations:
|
||||
* https://github.com/addthis/stream-lib/blob/master/src/main/java/com/clearspring/analytics/util/Varint.java
|
||||
@ -381,57 +211,10 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
internal fun decodeVarint(inp: InputStream, bitLimit: Int = 32, eofOnStartAllowed: Boolean = false): Long {
|
||||
var result = 0L
|
||||
var shift = 0
|
||||
var b: Int
|
||||
do {
|
||||
if (shift >= bitLimit) {
|
||||
// Out of range
|
||||
throw ProtobufDecodingException("Varint too long: exceeded $bitLimit bits")
|
||||
}
|
||||
// Get 7 bits from next byte
|
||||
b = inp.read()
|
||||
if (b == -1) {
|
||||
if (eofOnStartAllowed && shift == 0) return -1
|
||||
else throw IOException("Unexpected EOF")
|
||||
}
|
||||
result = result or (b.toLong() and 0x7FL shl shift)
|
||||
shift += 7
|
||||
} while (b and 0x80 != 0)
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun decodeSignedVarintInt(inp: InputStream): Int {
|
||||
val raw = decodeVarint(inp, 32).toInt()
|
||||
val temp = raw shl 31 shr 31 xor raw shr 1
|
||||
// This extra step lets us deal with the largest signed values by treating
|
||||
// negative results from read unsigned methods as like unsigned values.
|
||||
// Must re-flip the top bit if the original read value had it set.
|
||||
return temp xor (raw and (1 shl 31))
|
||||
}
|
||||
|
||||
internal fun decodeSignedVarintLong(inp: InputStream): Long {
|
||||
val raw = decodeVarint(inp, 64)
|
||||
val temp = raw shl 63 shr 63 xor raw shr 1
|
||||
// This extra step lets us deal with the largest signed values by treating
|
||||
// negative results from read unsigned methods as like unsigned values
|
||||
// Must re-flip the top bit if the original read value had it set.
|
||||
return temp xor (raw and (1L shl 63))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
companion object : BinaryFormat {
|
||||
public override val context: SerialModule get() = plain.context
|
||||
|
||||
// todo: make more memory-efficient
|
||||
private fun makeDelimited(decoder: ProtobufDecoder, parentTag: ProtoDesc?): ProtobufDecoder {
|
||||
if (parentTag == null) return decoder
|
||||
val bytes = decoder.nextObject()
|
||||
return ProtobufDecoder(ByteArrayInputStream(bytes))
|
||||
}
|
||||
override val context: SerialModule get() = plain.context
|
||||
|
||||
private fun SerialDescriptor.getProtoDesc(index: Int): ProtoDesc {
|
||||
return extractParameters(this, index)
|
||||
@ -457,9 +240,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
|
||||
val stream = ByteArrayInputStream(bytes)
|
||||
val reader = ProtobufReader(ProtobufDecoder(stream))
|
||||
return reader.decode(deserializer)
|
||||
return ProtoBuf.load(deserializer, bytes)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.serialization.SerialDescriptor
|
||||
|
||||
/*
|
||||
* Helper for kotlinx.serialization
|
||||
*/
|
||||
|
||||
internal inline fun <reified A: Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? {
|
||||
val candidates = getElementAnnotations(elementIndex).filterIsInstance<A>()
|
||||
return when (candidates.size) {
|
||||
0 -> null
|
||||
1 -> candidates[0]
|
||||
else -> throw IllegalStateException("There are duplicate annotations of type ${A::class} in the descriptor $this")
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialDescriptor
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.io.ProtoBuf
|
||||
@ -91,4 +92,27 @@ fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T
|
||||
*/
|
||||
fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
|
||||
return ProtoBufWithNullableSupport.load(serializer, this.readBytes())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 [RequestPacket] 的 [RequestPacket.sBuffer]
|
||||
*/
|
||||
fun <T : JceStruct> jceRequestSBuffer(name: String, serializer: SerializationStrategy<T>, jceStruct: T): ByteArray {
|
||||
return RequestDataVersion3(
|
||||
mapOf(
|
||||
name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0
|
||||
)
|
||||
).toByteArray(RequestDataVersion3.serializer())
|
||||
}
|
||||
|
||||
private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A)
|
||||
private val JCE_STRUCT_TAIL_OF_TAG_0 = byteArrayOf(0x0B)
|
||||
|
||||
internal inline fun <reified A : Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? {
|
||||
val candidates = getElementAnnotations(elementIndex).filterIsInstance<A>()
|
||||
return when (candidates.size) {
|
||||
0 -> null
|
||||
1 -> candidates[0]
|
||||
else -> throw IllegalStateException("There are duplicate annotations of type ${A::class} in the descriptor $this")
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendListPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
|
||||
@ -32,6 +31,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
|
||||
|
||||
override val coroutineContext: CoroutineContext = bot.coroutineContext + CoroutineExceptionHandler { _, throwable ->
|
||||
throwable.logStacktrace("Exception in NetworkHandler")
|
||||
}
|
||||
|
||||
private lateinit var channel: PlatformSocket
|
||||
|
||||
override suspend fun login() {
|
||||
@ -88,19 +91,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
|
||||
println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
|
||||
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>()
|
||||
println("登陆完成 开始尝试获取friendList")
|
||||
println("登陆完成 开始尝试获取friendList")
|
||||
println("登陆完成 开始尝试获取friendList")
|
||||
println("登陆完成 开始尝试获取friendList")
|
||||
println("登陆完成 开始尝试获取friendList")
|
||||
FriendListPacket(
|
||||
bot.client,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
0
|
||||
).sendAndExpect<FriendListPacket.GetFriendListResponse>()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,18 +158,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
private suspend inline fun <P : Packet> generifiedParsePacket(input: Input) {
|
||||
try {
|
||||
KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
|
||||
handlePacket(packetFactory, packet, commandName, sequenceId)
|
||||
if (packet is MultiPacket<*>) {
|
||||
packet.forEach {
|
||||
handlePacket(null, it, commandName, sequenceId)
|
||||
}
|
||||
KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
|
||||
handlePacket(packetFactory, packet, commandName, sequenceId)
|
||||
if (packet is MultiPacket<*>) {
|
||||
packet.forEach {
|
||||
handlePacket(null, it, commandName, sequenceId)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
println()
|
||||
println() // separate for debugging
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,7 +196,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
if (packet is Cancellable && packet.cancelled) return
|
||||
}
|
||||
|
||||
bot.logger.info(packet)
|
||||
bot.logger.info("Received packet: $packet")
|
||||
|
||||
packetFactory?.run {
|
||||
bot.handle(packet)
|
||||
@ -325,17 +310,19 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
/**
|
||||
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
|
||||
*/
|
||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timoutMillis: Long = 3000): E {
|
||||
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
|
||||
packetListeners.addLast(handler)
|
||||
bot.logger.info("Send: ${this.commandName}")
|
||||
channel.send(delegate)
|
||||
return withTimeoutOrNull(3000) {
|
||||
return withTimeoutOrNull(timoutMillis) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
handler.await() as E
|
||||
|
||||
// 不要 `withTimeout`. timeout 的异常会不知道去哪了.
|
||||
} ?: net.mamoe.mirai.qqandroid.utils.inline {
|
||||
packetListeners.remove(handler)
|
||||
error("timeout when sending ${this.commandName}")
|
||||
error("timeout when sending ${commandName}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,6 +338,4 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
override suspend fun awaitDisconnection() = supervisor.join()
|
||||
|
||||
override val coroutineContext: CoroutineContext = bot.coroutineContext
|
||||
}
|
@ -124,6 +124,7 @@ internal open class QQAndroidClient(
|
||||
@PublishedApi
|
||||
internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
|
||||
|
||||
var outgoingPacketUnknownValue: ByteArray = 0x02B05B8B.toByteArray()
|
||||
var loginState = 0
|
||||
|
||||
var t150: Tlv? = null
|
||||
|
@ -3,8 +3,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd50
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd6b
|
||||
|
||||
@Serializable
|
||||
internal class GetFriendListReq(
|
||||
|
@ -23,7 +23,7 @@ class RequestPacket(
|
||||
|
||||
@Serializable
|
||||
class RequestDataVersion3(
|
||||
@SerialId(0) val map: Map<String, ByteArray>
|
||||
@SerialId(0) val map: Map<String, ByteArray> // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的
|
||||
) : JceStruct
|
||||
|
||||
@Serializable
|
||||
|
@ -9,6 +9,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
@ -17,6 +18,7 @@ import net.mamoe.mirai.utils.cryptor.adjustToPublicKey
|
||||
import net.mamoe.mirai.utils.cryptor.decryptBy
|
||||
import net.mamoe.mirai.utils.io.*
|
||||
import net.mamoe.mirai.utils.unzip
|
||||
import net.mamoe.mirai.utils.withSwitch
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.jvm.JvmName
|
||||
@ -53,7 +55,7 @@ internal val DECRYPTER_16_ZERO = ByteArray(16)
|
||||
internal typealias PacketConsumer<T> = suspend (packetFactory: PacketFactory<T>, packet: T, commandName: String, ssoSequenceId: Int) -> Unit
|
||||
|
||||
@PublishedApi
|
||||
internal val PacketLogger: MiraiLogger = DefaultLogger("Packet")
|
||||
internal val PacketLogger: MiraiLogger = DefaultLogger("Packet").withSwitch(false)
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
@ -63,7 +65,8 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
MessageSvc.PushNotify,
|
||||
MessageSvc.PbGetMsg,
|
||||
MessageSvc.PushForceOffline,
|
||||
MessageSvc.PbSendMsg
|
||||
MessageSvc.PbSendMsg,
|
||||
FriendList.GetFriendGroupList
|
||||
) {
|
||||
// SvcReqMSFLoginNotify 自己的其他设备上限
|
||||
// MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机
|
||||
@ -193,8 +196,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
PacketLogger.verbose("sso(inner)extraData = ${extraData.toUHexString()}")
|
||||
|
||||
commandName = readString(readInt() - 4)
|
||||
val unknown = readBytes(readInt() - 4)
|
||||
if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
bot.client.outgoingPacketUnknownValue = readBytes(readInt() - 4)
|
||||
|
||||
dataCompressed = readInt()
|
||||
}
|
||||
@ -213,7 +215,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
// body
|
||||
val packetFactory = findPacketFactory(commandName)
|
||||
|
||||
bot.logger.info("Received: $commandName")
|
||||
bot.logger.debug("Received commandName: $commandName")
|
||||
return IncomingPacket(packetFactory, ssoSequenceId, packet)
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.writeFully
|
||||
import net.mamoe.mirai.data.MultiPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
@ -26,8 +25,6 @@ import net.mamoe.mirai.qqandroid.utils.toRichTextElems
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
import net.mamoe.mirai.utils.io.toReadPacket
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.random.Random
|
||||
|
||||
class MessageSvc {
|
||||
/**
|
||||
@ -145,11 +142,11 @@ class MessageSvc {
|
||||
richText = ImMsgBody.RichText(
|
||||
elems = message.toRichTextElems()
|
||||
)
|
||||
),
|
||||
msgSeq = 17041,
|
||||
msgRand = Random.nextInt().absoluteValue,
|
||||
syncCookie = client.c2cMessageSync.syncCookie.takeIf { it.isNotEmpty() } ?: "08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00".hexToBytes(),
|
||||
msgVia = 1
|
||||
)
|
||||
// msgSeq = 17041,
|
||||
// msgRand = Random.nextInt().absoluteValue,
|
||||
// syncCookie = client.c2cMessageSync.syncCookie.takeIf { it.isNotEmpty() } ?: "08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00".hexToBytes(),
|
||||
// msgVia = 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -4,11 +4,9 @@ import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.jceRequestSBuffer
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataStructSvcReqRegister
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.SvcReqRegister
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
@ -61,67 +59,65 @@ class StatSvc {
|
||||
RequestPacket(
|
||||
sServantName = "PushService",
|
||||
sFuncName = "SvcReqRegister",
|
||||
sBuffer = RequestDataVersion3(
|
||||
mapOf(
|
||||
"SvcReqRegister" to RequestDataStructSvcReqRegister(
|
||||
SvcReqRegister(
|
||||
cConnType = 0,
|
||||
lBid = 1 or 2 or 4,
|
||||
lUin = client.uin,
|
||||
iStatus = client.onlineStatus.id,
|
||||
bKikPC = 0, // 是否把 PC 踢下线
|
||||
bKikWeak = 0,
|
||||
timeStamp = 0,
|
||||
// timeStamp = currentTimeSeconds // millis or seconds??
|
||||
iLargeSeq = 1551, // ?
|
||||
bOpenPush = 1,
|
||||
iLocaleID = 2052,
|
||||
bRegType =
|
||||
(if (regPushReason == RegPushReason.appRegister ||
|
||||
regPushReason == RegPushReason.fillRegProxy ||
|
||||
regPushReason == RegPushReason.createDefaultRegInfo ||
|
||||
regPushReason == RegPushReason.setOnlineStatus
|
||||
) 0 else 1).toByte(),
|
||||
bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0,
|
||||
iOSVersion = client.device.version.sdk.toLong(),
|
||||
cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0,
|
||||
vecGuid = client.device.guid,
|
||||
strDevName = client.device.model.encodeToString(),
|
||||
strDevType = client.device.model.encodeToString(),
|
||||
strOSVer = client.device.version.release.encodeToString(),
|
||||
sBuffer = jceRequestSBuffer(
|
||||
"SvcReqRegister",
|
||||
SvcReqRegister.serializer(),
|
||||
SvcReqRegister(
|
||||
cConnType = 0,
|
||||
lBid = 1 or 2 or 4,
|
||||
lUin = client.uin,
|
||||
iStatus = client.onlineStatus.id,
|
||||
bKikPC = 0, // 是否把 PC 踢下线
|
||||
bKikWeak = 0,
|
||||
timeStamp = 0,
|
||||
// timeStamp = currentTimeSeconds // millis or seconds??
|
||||
iLargeSeq = 1551, // ?
|
||||
bOpenPush = 1,
|
||||
iLocaleID = 2052,
|
||||
bRegType =
|
||||
(if (regPushReason == RegPushReason.appRegister ||
|
||||
regPushReason == RegPushReason.fillRegProxy ||
|
||||
regPushReason == RegPushReason.createDefaultRegInfo ||
|
||||
regPushReason == RegPushReason.setOnlineStatus
|
||||
) 0 else 1).toByte(),
|
||||
bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0,
|
||||
iOSVersion = client.device.version.sdk.toLong(),
|
||||
cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0,
|
||||
vecGuid = client.device.guid,
|
||||
strDevName = client.device.model.encodeToString(),
|
||||
strDevType = client.device.model.encodeToString(),
|
||||
strOSVer = client.device.version.release.encodeToString(),
|
||||
|
||||
uOldSSOIp = 0,
|
||||
uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
|
||||
acc or ((s.toLong() shl (index * 16)))
|
||||
},
|
||||
strVendorName = "MIUI",
|
||||
strVendorOSName = "?ONEPLUS A5000_23_17",
|
||||
// register 时还需要
|
||||
/*
|
||||
var44.uNewSSOIp = field_127445;
|
||||
var44.uOldSSOIp = field_127444;
|
||||
var44.strVendorName = ROMUtil.getRomName();
|
||||
var44.strVendorOSName = ROMUtil.getRomVersion(20);
|
||||
*/
|
||||
bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump(
|
||||
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
|
||||
rpt_config_list = listOf(
|
||||
Oidb0x769.ConfigSeq(
|
||||
type = 46,
|
||||
version = 0
|
||||
),
|
||||
Oidb0x769.ConfigSeq(
|
||||
type = 283,
|
||||
version = 0
|
||||
)
|
||||
)
|
||||
uOldSSOIp = 0,
|
||||
uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
|
||||
acc or ((s.toLong() shl (index * 16)))
|
||||
},
|
||||
strVendorName = "MIUI",
|
||||
strVendorOSName = "?ONEPLUS A5000_23_17",
|
||||
// register 时还需要
|
||||
/*
|
||||
var44.uNewSSOIp = field_127445;
|
||||
var44.uOldSSOIp = field_127444;
|
||||
var44.strVendorName = ROMUtil.getRomName();
|
||||
var44.strVendorOSName = ROMUtil.getRomVersion(20);
|
||||
*/
|
||||
bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump(
|
||||
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
|
||||
rpt_config_list = listOf(
|
||||
Oidb0x769.ConfigSeq(
|
||||
type = 46,
|
||||
version = 0
|
||||
),
|
||||
Oidb0x769.ConfigSeq(
|
||||
type = 283,
|
||||
version = 0
|
||||
)
|
||||
),
|
||||
bSetMute = 0
|
||||
)
|
||||
)
|
||||
).toByteArray(RequestDataStructSvcReqRegister.serializer())
|
||||
),
|
||||
bSetMute = 0
|
||||
)
|
||||
).toByteArray(RequestDataVersion3.serializer())
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -219,7 +219,7 @@ private fun parseSsoFrame(input: ByteReadPacket): KnownPacketFactories.IncomingP
|
||||
commandName = readString(readInt() - 4)
|
||||
DebugLogger.warning("commandName=$commandName")
|
||||
val unknown = readBytes(readInt() - 4)
|
||||
if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
//if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
|
||||
check(readInt() == 0)
|
||||
}
|
||||
@ -261,7 +261,7 @@ private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingP
|
||||
commandName = readString(readInt() - 4)
|
||||
DebugLogger.warning("commandName=$commandName")
|
||||
val unknown = readBytes(readInt() - 4)
|
||||
if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
//if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
|
||||
|
||||
check(readInt() == 0)
|
||||
}
|
||||
|
@ -3,19 +3,23 @@ package test;
|
||||
import java.io.File
|
||||
|
||||
fun main(){
|
||||
val var9 = toJCEInfo(
|
||||
File(
|
||||
"""
|
||||
E:\Projects\QQAndroidFF\app\src\main\java\PushNotifyPack\RequestPushForceOffline.java
|
||||
""".trimIndent()
|
||||
).readText()
|
||||
)
|
||||
println(
|
||||
"import kotlinx.serialization.SerialId\n" +
|
||||
"import kotlinx.serialization.Serializable\n" +
|
||||
"import net.mamoe.mirai.qqandroid.io.JceStruct\n"
|
||||
)
|
||||
println(var9.toString())
|
||||
File(
|
||||
"""
|
||||
E:\Projects\QQAndroidFF\app\src\main\java\friendlist\
|
||||
""".trimIndent()
|
||||
).listFiles()!!.forEach {
|
||||
try {
|
||||
println(toJCEInfo(it.readText()).toString())
|
||||
} catch (e: Exception) {
|
||||
println("when processing ${it.path}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -91,7 +95,7 @@ class Property(
|
||||
}
|
||||
|
||||
fun toStringWithSpacing(maxIDLength:Int): String {
|
||||
val space = " ".repeat(maxIDLength - (jceID.toString().length))
|
||||
val space = " ".repeat((maxIDLength - (jceID.toString().length)).coerceAtLeast(0))
|
||||
var base = " @SerialId(" + jceID + ") " + space + "val " + name + ":" + type + ""
|
||||
if(!isRequired){
|
||||
if(defaultValue == null) {
|
||||
@ -114,7 +118,7 @@ fun toJCEInfo(source:String):JCEInfo{
|
||||
val info = JCEInfo()
|
||||
val allProperties = mutableMapOf<String,Property>()
|
||||
var inputStreamVariableRegix:String? = null
|
||||
println(source)
|
||||
// println(source)
|
||||
source.split("\n").forEach{
|
||||
when{
|
||||
it.contains("class") -> {
|
||||
|
@ -92,7 +92,7 @@ abstract class Bot : CoroutineScope {
|
||||
abstract val network: BotNetworkHandler
|
||||
|
||||
/**
|
||||
* 登录.
|
||||
* 登录, 或重新登录.
|
||||
*
|
||||
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login]
|
||||
*
|
||||
|
@ -7,6 +7,7 @@ import kotlinx.coroutines.CompletableJob
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
|
||||
|
||||
/**
|
||||
@ -40,7 +41,10 @@ abstract class BotNetworkHandler : CoroutineScope {
|
||||
/**
|
||||
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
|
||||
* 本函数将挂起直到登录成功.
|
||||
*
|
||||
* 不要使用这个 API. 请使用 [Bot.login]
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
abstract suspend fun login()
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user