mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-08 17:20:11 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
This commit is contained in:
commit
3ae1832ac1
@ -39,8 +39,7 @@ QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还
|
||||
- 完成 密码登录 (2020/1/23)
|
||||
- 完成 群消息解析 (2020/1/25)
|
||||
- 完成 图片验证码登录 (2020/1/26)
|
||||
- 进行中 免密登录
|
||||
- 进行中 SMS 登录
|
||||
- 完成 设备锁登录 (2020/1/29)
|
||||
- 进行中 消息解析和发送
|
||||
- 进行中 图片上传和下载
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.mamoe.mirai.qqandroid
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.GroupInfo
|
||||
@ -8,16 +7,19 @@ import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.message.data.ImageId
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
internal abstract class ContactImpl : Contact
|
||||
|
||||
internal class QQImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
|
||||
override val bot: Bot by bot.unsafeWeakRef()
|
||||
internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
TODO("not implemented")
|
||||
bot.network.run {
|
||||
MessageSvc.PbSendMsg.ToFriend(bot.client, id, message).sendAndExpect<MessageSvc.PbSendMsg.Response>()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun uploadImage(image: ExternalImage): ImageId {
|
||||
@ -38,11 +40,11 @@ internal class QQImpl(bot: Bot, override val coroutineContext: CoroutineContext,
|
||||
|
||||
}
|
||||
|
||||
internal class MemberImpl(bot: Bot, group: Group, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Member {
|
||||
internal class MemberImpl(bot: QQAndroidBot, group: Group, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Member {
|
||||
override val group: Group by group.unsafeWeakRef()
|
||||
override val permission: MemberPermission
|
||||
get() = TODO("not implemented")
|
||||
override val bot: Bot by bot.unsafeWeakRef()
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
|
||||
override suspend fun mute(durationSeconds: Int): Boolean {
|
||||
TODO("not implemented")
|
||||
@ -76,7 +78,7 @@ internal class MemberImpl(bot: Bot, group: Group, override val coroutineContext:
|
||||
|
||||
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Group {
|
||||
internal class GroupImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Group {
|
||||
override val internalId: GroupInternalId = GroupId(id).toInternalId()
|
||||
override val owner: Member
|
||||
get() = TODO("not implemented")
|
||||
@ -86,7 +88,8 @@ internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineConte
|
||||
get() = TODO("not implemented")
|
||||
override val members: ContactList<Member> = ContactList(LockFreeLinkedList())
|
||||
|
||||
override fun getMember(id: Long): Member = members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot, this, coroutineContext, id) })
|
||||
override fun getMember(id: Long): Member =
|
||||
members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot as QQAndroidBot, this, coroutineContext, id) })
|
||||
|
||||
override suspend fun updateGroupInfo(): GroupInfo {
|
||||
TODO("not implemented")
|
||||
@ -96,7 +99,7 @@ internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineConte
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override val bot: Bot by bot.unsafeWeakRef()
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
TODO("not implemented")
|
||||
|
@ -33,7 +33,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
|
||||
|
||||
override fun getQQ(id: Long): QQ {
|
||||
return qqs.delegate.filteringGetOrAdd({ it.id == id }, { QQImpl(this, coroutineContext, id) })
|
||||
return qqs.delegate.filteringGetOrAdd({ it.id == id }, { QQImpl(this as QQAndroidBot, coroutineContext, id) })
|
||||
}
|
||||
|
||||
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
|
||||
@ -43,17 +43,17 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
|
||||
|
||||
override suspend fun getGroup(id: GroupId): Group {
|
||||
return groups.delegate.filteringGetOrAdd({ it.id == id.value }, { GroupImpl(this, coroutineContext, id.value) })
|
||||
return groups.delegate.filteringGetOrAdd({ it.id == id.value }, { GroupImpl(this as QQAndroidBot, coroutineContext, id.value) })
|
||||
}
|
||||
|
||||
override suspend fun getGroup(internalId: GroupInternalId): Group {
|
||||
internalId.toId().value.let { id ->
|
||||
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this, coroutineContext, id) })
|
||||
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this as QQAndroidBot, coroutineContext, id) })
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getGroup(id: Long): Group {
|
||||
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this, coroutineContext, id) })
|
||||
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this as QQAndroidBot, coroutineContext, id) })
|
||||
}
|
||||
|
||||
override suspend fun Image.getLink(): ImageLink {
|
||||
|
@ -7,7 +7,7 @@ import net.mamoe.mirai.event.events.BotEvent
|
||||
/**
|
||||
* 被挤下线
|
||||
*/
|
||||
class ForceOfflineEvent(
|
||||
data class ForceOfflineEvent(
|
||||
override val bot: Bot,
|
||||
val title: String,
|
||||
val tips: String
|
||||
|
@ -6,6 +6,8 @@ import kotlinx.io.core.readBytes
|
||||
import kotlinx.io.core.writeFully
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
|
||||
/**
|
||||
* 仅有标示作用
|
||||
@ -13,26 +15,29 @@ import kotlinx.serialization.SerializationStrategy
|
||||
interface ProtoBuf
|
||||
|
||||
fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
|
||||
this.writeFully(v.toByteArray(serializer))
|
||||
|
||||
this.writeFully(v.toByteArray(serializer).also {
|
||||
println("发送 protobuf: ${it.toUHexString()}")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* dump
|
||||
*/
|
||||
fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray {
|
||||
return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this)
|
||||
return ProtoBufWithNullableSupport.dump(serializer, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* load
|
||||
*/
|
||||
fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
|
||||
return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this)
|
||||
return ProtoBufWithNullableSupport.load(deserializer, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* load
|
||||
*/
|
||||
fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
|
||||
return kotlinx.serialization.protobuf.ProtoBuf.load(serializer, this.readBytes())
|
||||
return ProtoBufWithNullableSupport.load(serializer, this.readBytes())
|
||||
}
|
@ -191,7 +191,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
|
||||
this.writeHead(STRUCT_END, 0)
|
||||
}
|
||||
} else if (value is ProtoBuf) {
|
||||
this.encodeTaggedByteArray(popTag(), kotlinx.serialization.protobuf.ProtoBuf.dump(value))
|
||||
this.encodeTaggedByteArray(popTag(), net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.dump(value))
|
||||
} else {
|
||||
serializer.serialize(this, value)
|
||||
}
|
||||
|
@ -0,0 +1,483 @@
|
||||
/*
|
||||
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
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.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>
|
||||
|
||||
internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefault: Boolean = false): ProtoDesc {
|
||||
val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1)
|
||||
val format = desc.findAnnotation<ProtoType>(index)?.type
|
||||
?: ProtoNumberType.DEFAULT
|
||||
return idx to format
|
||||
}
|
||||
|
||||
|
||||
class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
|
||||
|
||||
internal open inner class ProtobufWriter(val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() {
|
||||
public override val context
|
||||
get() = this@ProtoBufWithNullableSupport.context
|
||||
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) {
|
||||
StructureKind.LIST -> RepeatedWriter(encoder, currentTag)
|
||||
StructureKind.CLASS, UnionKind.OBJECT, is PolymorphicKind -> ObjectWriter(currentTagOrNull, encoder)
|
||||
StructureKind.MAP -> MapRepeatedWriter(currentTagOrNull, encoder)
|
||||
else -> throw SerializationException("Primitives are not supported at top-level")
|
||||
}
|
||||
|
||||
override fun encodeTaggedInt(tag: ProtoDesc, value: Int) = encoder.writeInt(value, tag.first, tag.second)
|
||||
override fun encodeTaggedByte(tag: ProtoDesc, value: Byte) = encoder.writeInt(value.toInt(), tag.first, tag.second)
|
||||
override fun encodeTaggedShort(tag: ProtoDesc, value: Short) = encoder.writeInt(value.toInt(), tag.first, tag.second)
|
||||
override fun encodeTaggedLong(tag: ProtoDesc, value: Long) = encoder.writeLong(value, tag.first, tag.second)
|
||||
override fun encodeTaggedFloat(tag: ProtoDesc, value: Float) = encoder.writeFloat(value, tag.first)
|
||||
override fun encodeTaggedDouble(tag: ProtoDesc, value: Double) = encoder.writeDouble(value, tag.first)
|
||||
override fun encodeTaggedBoolean(tag: ProtoDesc, value: Boolean) = encoder.writeInt(if (value) 1 else 0, tag.first, ProtoNumberType.DEFAULT)
|
||||
override fun encodeTaggedChar(tag: ProtoDesc, value: Char) = encoder.writeInt(value.toInt(), tag.first, tag.second)
|
||||
override fun encodeTaggedString(tag: ProtoDesc, value: String) = encoder.writeString(value, tag.first)
|
||||
override fun encodeTaggedEnum(
|
||||
tag: ProtoDesc,
|
||||
enumDescription: SerialDescriptor,
|
||||
ordinal: Int
|
||||
) = encoder.writeInt(
|
||||
extractParameters(enumDescription, ordinal, zeroBasedDefault = true).first,
|
||||
tag.first,
|
||||
ProtoNumberType.DEFAULT
|
||||
)
|
||||
|
||||
override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
|
||||
|
||||
// MIRAI MODIFY START:
|
||||
override fun encodeTaggedNull(tag: ProtoDesc) {
|
||||
|
||||
}
|
||||
|
||||
override fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?) {
|
||||
if (value == null) {
|
||||
encodeTaggedNull(popTag())
|
||||
} else encodeSerializableValue(serializer, value)
|
||||
}
|
||||
// MIRAI MODIFY END
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
|
||||
// encode maps as collection of map entries, not merged collection of key-values
|
||||
serializer.descriptor is MapLikeDescriptor -> {
|
||||
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
|
||||
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
|
||||
HashSetSerializer(mapEntrySerial).serialize(this, (value as Map<*, *>).entries)
|
||||
}
|
||||
serializer.descriptor == ByteArraySerializer.descriptor -> encoder.writeBytes(value as ByteArray, popTag().first)
|
||||
else -> serializer.serialize(this, value)
|
||||
}
|
||||
}
|
||||
|
||||
internal open inner class ObjectWriter(
|
||||
val parentTag: ProtoDesc?, private val parentEncoder: ProtobufEncoder,
|
||||
private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
|
||||
) : ProtobufWriter(ProtobufEncoder(stream)) {
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
if (parentTag != null) {
|
||||
parentEncoder.writeBytes(stream.toByteArray(), parentTag.first)
|
||||
} else {
|
||||
parentEncoder.out.write(stream.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class MapRepeatedWriter(parentTag: ProtoDesc?, parentEncoder: ProtobufEncoder) : ObjectWriter(parentTag, parentEncoder) {
|
||||
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 inner class RepeatedWriter(encoder: ProtobufEncoder, val curTag: ProtoDesc) : ProtobufWriter(encoder) {
|
||||
override fun SerialDescriptor.getTag(index: Int) = curTag
|
||||
}
|
||||
|
||||
internal class ProtobufEncoder(val out: ByteArrayOutputStream) {
|
||||
|
||||
fun writeBytes(bytes: ByteArray, tag: Int) {
|
||||
val header = encode32((tag shl 3) or SIZE_DELIMITED)
|
||||
val len = encode32(bytes.size)
|
||||
out.write(header)
|
||||
out.write(len)
|
||||
out.write(bytes)
|
||||
}
|
||||
|
||||
fun writeInt(value: Int, tag: Int, format: ProtoNumberType) {
|
||||
val wireType = if (format == ProtoNumberType.FIXED) i32 else VARINT
|
||||
val header = encode32((tag shl 3) or wireType)
|
||||
val content = encode32(value, format)
|
||||
out.write(header)
|
||||
out.write(content)
|
||||
}
|
||||
|
||||
fun writeLong(value: Long, tag: Int, format: ProtoNumberType) {
|
||||
val wireType = if (format == ProtoNumberType.FIXED) i64 else VARINT
|
||||
val header = encode32((tag shl 3) or wireType)
|
||||
val content = encode64(value, format)
|
||||
out.write(header)
|
||||
out.write(content)
|
||||
}
|
||||
|
||||
fun writeString(value: String, tag: Int) {
|
||||
val bytes = value.toUtf8Bytes()
|
||||
writeBytes(bytes, tag)
|
||||
}
|
||||
|
||||
fun writeDouble(value: Double, tag: Int) {
|
||||
val header = encode32((tag shl 3) or i64)
|
||||
val content = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble(value).array()
|
||||
out.write(header)
|
||||
out.write(content)
|
||||
}
|
||||
|
||||
fun writeFloat(value: Float, tag: Int) {
|
||||
val header = encode32((tag shl 3) or i32)
|
||||
val content = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putFloat(value).array()
|
||||
out.write(header)
|
||||
out.write(content)
|
||||
}
|
||||
|
||||
private fun encode32(number: Int, format: ProtoNumberType = ProtoNumberType.DEFAULT): ByteArray =
|
||||
when (format) {
|
||||
ProtoNumberType.FIXED -> ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(number).array()
|
||||
ProtoNumberType.DEFAULT -> encodeVarint(number.toLong())
|
||||
ProtoNumberType.SIGNED -> encodeVarint(((number shl 1) xor (number shr 31)))
|
||||
}
|
||||
|
||||
|
||||
private fun encode64(number: Long, format: ProtoNumberType = ProtoNumberType.DEFAULT): ByteArray =
|
||||
when (format) {
|
||||
ProtoNumberType.FIXED -> ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(number).array()
|
||||
ProtoNumberType.DEFAULT -> encodeVarint(number)
|
||||
ProtoNumberType.SIGNED -> encodeVarint((number shl 1) xor (number shr 63))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
internal object Varint {
|
||||
internal fun encodeVarint(inp: Int): ByteArray {
|
||||
var value = inp
|
||||
val byteArrayList = ByteArray(10)
|
||||
var i = 0
|
||||
while (value and 0xFFFFFF80.toInt() != 0) {
|
||||
byteArrayList[i++] = ((value and 0x7F) or 0x80).toByte()
|
||||
value = value ushr 7
|
||||
}
|
||||
byteArrayList[i] = (value and 0x7F).toByte()
|
||||
val out = ByteArray(i + 1)
|
||||
while (i >= 0) {
|
||||
out[i] = byteArrayList[i]
|
||||
i--
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
internal fun encodeVarint(inp: Long): ByteArray {
|
||||
var value = inp
|
||||
val byteArrayList = ByteArray(10)
|
||||
var i = 0
|
||||
while (value and 0x7FL.inv() != 0L) {
|
||||
byteArrayList[i++] = ((value and 0x7F) or 0x80).toByte()
|
||||
value = value ushr 7
|
||||
}
|
||||
byteArrayList[i] = (value and 0x7F).toByte()
|
||||
val out = ByteArray(i + 1)
|
||||
while (i >= 0) {
|
||||
out[i] = byteArrayList[i]
|
||||
i--
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
private fun SerialDescriptor.getProtoDesc(index: Int): ProtoDesc {
|
||||
return extractParameters(this, index)
|
||||
}
|
||||
|
||||
internal const val VARINT = 0
|
||||
internal const val i64 = 1
|
||||
internal const val SIZE_DELIMITED = 2
|
||||
internal const val i32 = 5
|
||||
|
||||
val plain = ProtoBufWithNullableSupport()
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray = plain.dump(serializer, obj)
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T = plain.load(deserializer, bytes)
|
||||
override fun install(module: SerialModule) = throw IllegalStateException("You should not install anything to global instance")
|
||||
}
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
|
||||
val encoder = ByteArrayOutputStream()
|
||||
val dumper = ProtobufWriter(ProtobufEncoder(encoder))
|
||||
dumper.encode(serializer, obj)
|
||||
return encoder.toByteArray()
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
|
||||
val stream = ByteArrayInputStream(bytes)
|
||||
val reader = ProtobufReader(ProtobufDecoder(stream))
|
||||
return reader.decode(deserializer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun InputStream.readExactNBytes(bytes: Int): ByteArray {
|
||||
val array = ByteArray(bytes)
|
||||
var read = 0
|
||||
while (read < bytes) {
|
||||
val i = this.read(array, read, bytes - read)
|
||||
if (i == -1) throw IOException("Unexpected EOF")
|
||||
read += i
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
internal fun InputStream.readToByteBuffer(bytes: Int): ByteBuffer {
|
||||
val arr = readExactNBytes(bytes)
|
||||
val buf = ByteBuffer.allocate(bytes)
|
||||
buf.put(arr).flip()
|
||||
return buf
|
||||
}
|
@ -314,10 +314,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
||||
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
|
||||
packetListeners.addLast(handler)
|
||||
bot.logger.info("Send: ${this.commandName}")
|
||||
channel.send(delegate)
|
||||
return withTimeout(3000) {
|
||||
return withTimeoutOrNull(3000) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
handler.await() as E
|
||||
} ?: net.mamoe.mirai.qqandroid.utils.inline {
|
||||
packetListeners.remove(handler)
|
||||
error("timeout when sending ${this.commandName}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,7 +469,7 @@ class ImMsgBody : ProtoBuf {
|
||||
@SerialId(16) val bubbleSubId: Int = 0,
|
||||
@SerialId(17) val pendantId: Long = 0L,
|
||||
@SerialId(18) val rpIndex: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
|
||||
@SerialId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY // 78 00 F8 01 00 C8 02 00
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
@ -609,7 +609,7 @@ class ImMsgBody : ProtoBuf {
|
||||
@SerialId(8) val reserved: Int = 0,
|
||||
@SerialId(9) val subcmd: Int = 0,
|
||||
@SerialId(10) val microCloud: Int = 0,
|
||||
@SerialId(11) val bytesFileUrls: List<ByteArray>? = null,
|
||||
@SerialId(11) val bytesFileUrls: List<ByteArray>? = listOf(),
|
||||
@SerialId(12) val downloadFlag: Int = 0,
|
||||
@SerialId(50) val dangerEvel: Int = 0,
|
||||
@SerialId(51) val lifeTime: Int = 0,
|
||||
@ -705,7 +705,7 @@ class ImMsgBody : ProtoBuf {
|
||||
@SerialId(20) val downPara: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(29) val format: Int = 0,
|
||||
@SerialId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@SerialId(31) val bytesPttUrls: List<ByteArray>? = null,
|
||||
@SerialId(31) val bytesPttUrls: List<ByteArray>? = listOf(),
|
||||
@SerialId(32) val downloadFlag: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@ -813,8 +813,8 @@ class ImMsgBody : ProtoBuf {
|
||||
class RichText(
|
||||
@SerialId(1) val attr: Attr? = null,
|
||||
@SerialId(2) val elems: MutableList<Elem> = mutableListOf(),
|
||||
@SerialId(3) val notOnlineFile: NotOnlineFile? = null,
|
||||
@SerialId(4) val ptt: Ptt? = null,
|
||||
@SerialId(3) val notOnlineFile: NotOnlineFile? =null,
|
||||
@SerialId(4) val ptt: Ptt? =null,
|
||||
@SerialId(5) val tmpPtt: TmpPtt? = null,
|
||||
@SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null
|
||||
) : ProtoBuf
|
||||
@ -1045,9 +1045,9 @@ class ImMsgHead : ProtoBuf {
|
||||
|
||||
@Serializable
|
||||
class InstCtrl(
|
||||
@SerialId(1) val msgSendToInst: List<InstInfo>? = null,
|
||||
@SerialId(2) val msgExcludeInst: List<InstInfo>? = null,
|
||||
@SerialId(3) val msgFromInst: InstInfo? = null
|
||||
@SerialId(1) val msgSendToInst: List<InstInfo>? = listOf(),
|
||||
@SerialId(2) val msgExcludeInst: List<InstInfo>? = listOf(),
|
||||
@SerialId(3) val msgFromInst: InstInfo? = InstInfo()
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
@ -1107,7 +1107,7 @@ class ImReceipt : ProtoBuf {
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class ReceiptInfo(
|
||||
data class ReceiptInfo(
|
||||
@SerialId(1) val readTime: Long = 0L
|
||||
) : ProtoBuf
|
||||
|
||||
@ -1118,7 +1118,7 @@ class ImReceipt : ProtoBuf {
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class ReceiptResp(
|
||||
data class ReceiptResp(
|
||||
@SerialId(1) val command: Int /* enum */ = 1,
|
||||
@SerialId(2) val receiptInfo: ReceiptInfo? = null
|
||||
) : ProtoBuf
|
||||
|
@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.proto
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.io.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
|
||||
@ -102,7 +103,7 @@ class MsgSvc : ProtoBuf {
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class MsgSendInfo(
|
||||
data class MsgSendInfo(
|
||||
@SerialId(1) val receiver: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@ -440,7 +441,7 @@ class MsgSvc : ProtoBuf {
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
class PbSendMsgResp(
|
||||
data class PbSendMsgResp(
|
||||
@SerialId(1) val result: Int = 0,
|
||||
@SerialId(2) val errmsg: String = "",
|
||||
@SerialId(3) val sendTime: Int = 0,
|
||||
@ -450,7 +451,7 @@ class MsgSvc : ProtoBuf {
|
||||
@SerialId(7) val transSvrInfo: TransSvrInfo? = null,
|
||||
@SerialId(8) val receiptResp: ImReceipt.ReceiptResp? = null,
|
||||
@SerialId(9) val textAnalysisResult: Int = 0
|
||||
) : ProtoBuf
|
||||
) : ProtoBuf, Packet
|
||||
|
||||
@Serializable
|
||||
class PbBindUinUnReadMsgNumResp(
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet
|
||||
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
|
||||
interface MessageMicro
|
||||
|
||||
|
||||
fun <T : MessageMicro> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray = ProtoBuf.dump(serializer, this)
|
||||
fun <T : MessageMicro> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray = ProtoBufWithNullableSupport.dump(serializer, this)
|
@ -62,7 +62,8 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
OnlinePush.PbPushGroupMsg,
|
||||
MessageSvc.PushNotify,
|
||||
MessageSvc.PbGetMsg,
|
||||
MessageSvc.PushForceOffline
|
||||
MessageSvc.PushForceOffline,
|
||||
MessageSvc.PbSendMsg
|
||||
) {
|
||||
// SvcReqMSFLoginNotify 自己的其他设备上限
|
||||
// MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机
|
||||
|
@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.writeFully
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
@ -19,7 +19,7 @@ internal object ImageDownPacket : PacketFactory<ImageDownPacket.ImageDownPacketR
|
||||
// TODO: 2020/1/24 测试: bodyType, subAppId
|
||||
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
|
||||
writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) {
|
||||
val data = ProtoBuf.dump(
|
||||
val data = ProtoBufWithNullableSupport.dump(
|
||||
Cmd0x352Packet.serializer(),
|
||||
Cmd0x352Packet.createByImageRequest(req)
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.writeFully
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
@ -19,7 +19,7 @@ internal object ImageUpPacket : PacketFactory<ImageUpPacket.ImageUpPacketRespons
|
||||
// TODO: 2020/1/24 测试: bodyType, subAppId
|
||||
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
|
||||
writeSsoPacket(client, subAppId = 0, commandName = ImageDownPacket.commandName, sequenceId = it) {
|
||||
val data = ProtoBuf.dump(
|
||||
val data = ProtoBufWithNullableSupport.dump(
|
||||
Cmd0x352Packet.serializer(),
|
||||
Cmd0x352Packet.createByImageRequest(req)
|
||||
)
|
||||
|
@ -2,8 +2,11 @@ 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
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
|
||||
import net.mamoe.mirai.qqandroid.io.readRemainingAsProtoBuf
|
||||
@ -12,18 +15,23 @@ import net.mamoe.mirai.qqandroid.io.writeProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
|
||||
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.buildOutgoingUniPacket
|
||||
import net.mamoe.mirai.qqandroid.utils.toMessageChain
|
||||
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 {
|
||||
/**
|
||||
* 告知要刷新消息
|
||||
* 告知要刷新好友消息
|
||||
*/
|
||||
internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
|
||||
@ -41,7 +49,7 @@ class MessageSvc {
|
||||
|
||||
|
||||
/**
|
||||
* 进行刷新消息
|
||||
* 获取好友消息和消息记录
|
||||
*/
|
||||
internal object PbGetMsg : PacketFactory<MultiPacket<FriendMessage>>("MessageSvc.PbGetMsg") {
|
||||
val EXTRA_DATA =
|
||||
@ -65,9 +73,10 @@ class MessageSvc {
|
||||
onlineSyncFlag = 1,
|
||||
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
|
||||
syncCookie = client.c2cMessageSync.syncCookie,
|
||||
syncFlag = client.c2cMessageSync.syncFlag,
|
||||
msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
|
||||
pubaccountCookie = client.c2cMessageSync.pubAccountCookie
|
||||
syncFlag = 1
|
||||
// syncFlag = client.c2cMessageSync.syncFlag,
|
||||
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
|
||||
//pubaccountCookie = client.c2cMessageSync.pubAccountCookie
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -112,5 +121,43 @@ class MessageSvc {
|
||||
return ForceOfflineEvent(bot, title = struct.title ?: "", tips = struct.tips ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
internal object PbSendMsg : PacketFactory<MsgSvc.PbSendMsgResp>("MessageSvc.PbSendMsg") {
|
||||
object Response : Packet
|
||||
|
||||
/**
|
||||
* 发送好友消息
|
||||
*/
|
||||
fun ToFriend(
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
message: MessageChain
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
|
||||
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 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 40 01".hexToBytes())
|
||||
|
||||
///return@buildOutgoingUniPacket
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
|
||||
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)),
|
||||
contentHead = MsgComm.ContentHead(pkgNum = 1),
|
||||
msgBody = ImMsgBody.MsgBody(
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MsgSvc.PbSendMsgResp {
|
||||
discardExact(4)
|
||||
return readRemainingAsProtoBuf(MsgSvc.PbSendMsgResp.serializer())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,11 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.contact.sendMessage
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
|
||||
@ -24,7 +25,7 @@ internal class OnlinePush {
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupMessage {
|
||||
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
|
||||
discardExact(4)
|
||||
val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
|
||||
val pbPushMsg = ProtoBufWithNullableSupport.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
|
||||
|
||||
val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
|
||||
|
||||
@ -48,5 +49,11 @@ internal class OnlinePush {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun QQAndroidBot.handle(packet: GroupMessage) {
|
||||
if (packet.senderName == "Him188moe") {
|
||||
this.getQQ(2978594313).sendMessage("FROM MIRAI")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -267,9 +267,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
|
||||
|
||||
sealed class LoginPacketResponse : Packet {
|
||||
object Success : LoginPacketResponse() {
|
||||
override fun toString(): String {
|
||||
return "LoginPacketResponse.Success"
|
||||
}
|
||||
override fun toString(): String = "LoginPacketResponse.Success"
|
||||
}
|
||||
|
||||
data class Error(
|
||||
@ -283,18 +281,22 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
|
||||
class Slider(
|
||||
val url: String
|
||||
) : Captcha() {
|
||||
override fun toString(): String {
|
||||
return "LoginPacketResponse.Captcha.Slider"
|
||||
}
|
||||
override fun toString(): String = "LoginPacketResponse.Captcha.Slider"
|
||||
}
|
||||
|
||||
class Picture(
|
||||
val data: IoBuffer,
|
||||
val sign: ByteArray
|
||||
) : Captcha() {
|
||||
override fun toString(): String {
|
||||
return "LoginPacketResponse.Captcha.Picture"
|
||||
}
|
||||
override fun toString(): String = "LoginPacketResponse.Captcha.Picture"
|
||||
}
|
||||
}
|
||||
|
||||
data class UnsafeLogin(val url: String) : LoginPacketResponse()
|
||||
|
||||
class SMSVerifyCodeNeeded(val t402: ByteArray, val t403: ByteArray) : LoginPacketResponse(){
|
||||
override fun toString(): String {
|
||||
return "LoginPacketResponse.SMSVerifyCodeNeeded"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
@ -103,7 +103,7 @@ class StatSvc {
|
||||
var44.strVendorName = ROMUtil.getRomName();
|
||||
var44.strVendorOSName = ROMUtil.getRomVersion(20);
|
||||
*/
|
||||
bytes_0x769_reqbody = ProtoBuf.dump(
|
||||
bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump(
|
||||
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
|
||||
rpt_config_list = listOf(
|
||||
Oidb0x769.ConfigSeq(
|
||||
|
@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.utils
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
@ -62,7 +62,7 @@ abstract class DeviceInfo(
|
||||
@SerialId(9) val innerVersion: ByteArray
|
||||
)
|
||||
|
||||
return ProtoBuf.dump(
|
||||
return ProtoBufWithNullableSupport.dump(
|
||||
DevInfo.serializer(), DevInfo(
|
||||
bootloader,
|
||||
procVersion,
|
||||
|
@ -3,16 +3,15 @@ package net.mamoe.mirai.qqandroid.utils
|
||||
import net.mamoe.mirai.data.ImageLink
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
|
||||
|
||||
|
||||
internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq {
|
||||
val request = MsgSvc.PbSendMsgReq()
|
||||
internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
|
||||
val elems = mutableListOf<ImMsgBody.Elem>()
|
||||
|
||||
this.forEach {
|
||||
when (it) {
|
||||
is PlainText -> {
|
||||
request.msgBody.richText.elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
|
||||
elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
|
||||
}
|
||||
is At -> {
|
||||
|
||||
@ -20,8 +19,7 @@ internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return request
|
||||
return elems
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@ import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
|
@ -200,9 +200,9 @@ fun Any?.contentToString(prefix: String = ""): String = when (this) {
|
||||
}
|
||||
|
||||
is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}"
|
||||
is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() }
|
||||
is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString() }
|
||||
is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() }
|
||||
is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
|
||||
is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
|
||||
is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
|
||||
is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) }
|
||||
else -> {
|
||||
if (this == null) "null"
|
||||
|
@ -1,23 +0,0 @@
|
||||
package test
|
||||
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
import net.mamoe.mirai.utils.io.read
|
||||
import net.mamoe.mirai.utils.io.readTLVMap
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun main() {
|
||||
val newMap =
|
||||
"4E 22 00 03 E5 AE 89 4E 25 00 06 35 31 31 34 39 35 4E 26 00 01 2D 4E 27 00 01 2D 4E 29 00 01 02 4E 2A 00 06 56 69 76 69 61 6E 4E 2B 00 10 31 35 36 31 34 38 39 31 33 40 71 71 2E 63 6F 6D 4E 2D 00 1D 68 74 74 70 3A 2F 2F 31 35 36 31 34 38 39 31 33 2E 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 4E 2E 00 02 33 00 4E 2F 00 04 33 33 39 00 4E 30 00 01 2D 4E 31 00 01 00 4E 33 00 2D E6 88 91 E7 95 99 E9 95 BF E7 9A 84 E5 A4 B4 E5 8F 91 EF BC 8C E6 98 AF E4 BD A0 E9 94 99 E8 BF 87 E7 9A 84 E5 B9 B4 E5 8D 8E 2E 2E 2E 4E 35 00 18 E5 B9 BF E4 B8 9C E6 8A 80 E6 9C AF E5 B8 88 E8 8C 83 E5 AD A6 E9 99 A2 4E 36 00 01 0A 4E 37 00 01 03 4E 38 00 01 01 4E 3F 00 04 07 C2 0B 02 4E 40 00 0C 00 00 00 31 00 00 34 34 00 00 00 33 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 21 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 13 88 02 02 52 0F 00 14 00 00 00 00 00 00 00 00 12 05 10 58 89 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 31 00 00 34 34 00 00 31 34 5D C8 00 1E E7 B4 A2 E5 B0 BC EF BC 88 E4 B8 AD E5 9B BD EF BC 89 E6 9C 89 E9 99 90 E5 85 AC E5 8F B8 65 97 00 01 11 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 00 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00"
|
||||
.hexToBytes().read {
|
||||
readTLVMap(tagSize = 2, expectingEOF = true)
|
||||
}
|
||||
newMap.forEach { (key, value) ->
|
||||
if (!(value.isEmpty() || value.all { it.toInt() == 0 })) {
|
||||
println(key.toUShort().toUHexString() + "=" + value.decodeToString())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
|
||||
|
||||
package test
|
||||
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import kotlinx.serialization.protobuf.ProtoNumberType
|
||||
import kotlinx.serialization.protobuf.ProtoType
|
||||
import kotlinx.serialization.serializer
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.cryptor.readProtoMap
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
import net.mamoe.mirai.utils.io.read
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Serializable
|
||||
data class ProtoTest(
|
||||
//@SerialId(1) val string: String,
|
||||
//@SerialId(1) val int: Int,
|
||||
//@SerialId(1) val boolean: Boolean,
|
||||
//@SerialId(1) val short: Short,
|
||||
//@SerialId(1) val byte: Byte,
|
||||
@SerialId(1) @ProtoType(ProtoNumberType.FIXED) val fixedByte: Byte
|
||||
)
|
||||
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
suspend fun main() {
|
||||
deserializeTest()
|
||||
}
|
||||
|
||||
suspend fun deserializeTest() {
|
||||
val bytes =
|
||||
"""
|
||||
08 02 1A 55 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 25 2F 34 35 35 38 66 39 30 38 2D 37 62 39 61 2D 34 65 32 66 2D 38 63 36 39 2D 34 61 35 32 61 66 62 33 36 35 61 37 20 01 30 04 38 05 40 09 48 01 58 00 60 01 6A 0A 38 2E 32 2E 30 2E 31 32 39 36 70 E0 8C B2 F0 05 78 01 50 03
|
||||
""".trimIndent()
|
||||
.replace("\n", " ")
|
||||
.replace("UVarInt", "", ignoreCase = true)
|
||||
.replace("uint", "", ignoreCase = true)
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("(", "")
|
||||
.replace(")", "")
|
||||
.replace(" ", " ")
|
||||
.replace(" ", " ")
|
||||
.replace(" ", " ")
|
||||
.replace("_", "")
|
||||
.replace(",", "")
|
||||
.hexToBytes()
|
||||
println(bytes.read { readProtoMap() })
|
||||
}
|
||||
|
||||
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||
fun <T : Any> KClass<T>.loadFrom(protoBuf: ByteArray): T = ProtoBuf.load(this.serializer(), protoBuf)
|
Loading…
Reference in New Issue
Block a user