diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/io/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/io/JceInput.kt index b778ecbac..2692c6d38 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/io/JceInput.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/io/JceInput.kt @@ -3,6 +3,8 @@ package net.mamoe.mirai.qqandroid.network.io import kotlinx.io.charsets.Charset import kotlinx.io.core.* import kotlinx.io.pool.ObjectPool +import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket +import net.mamoe.mirai.utils.io.DebugLogger import net.mamoe.mirai.utils.io.readIoBuffer import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.toIoBuffer @@ -19,9 +21,76 @@ inline class JceHead(private val value: Long) { } } -fun ByteArray.asJceInput(charset: Charset = CharsetGBK): JceInput = JceInput(this.toIoBuffer(), charset) +fun ByteArray.readJceStruct(factory: JceStruct.Factory, tag: Int = 0, charset: Charset = CharsetUTF8): J { + this.asJceInput(charset).use { + return it.readJceStruct(factory, tag) + } +} -fun ByteReadPacket.asJceInput(charset: Charset = CharsetGBK): JceInput = JceInput(this.readIoBuffer(), charset) +fun ByteReadPacket.readJceStruct(factory: JceStruct.Factory, tag: Int = 0, charset: Charset = CharsetUTF8): J { + this.asJceInput(charset).use { + return it.readJceStruct(factory, tag) + } +} + +fun ByteArray.asJceInput(charset: Charset = CharsetUTF8): JceInput = JceInput(this.toIoBuffer(), charset) + +fun ByteReadPacket.readJceRequestBufferMapVersion2ToJceStruct(factory: JceStruct.Factory, charset: Charset = CharsetUTF8): J { + this.use { + val bytes = + readJceRequestBufferMapVersion2(charset).values.also { if (it.size != 1) DebugLogger.debug("读取 jce RequestPacket 时发现多个包在 map 中") }.firstOrNull() + ?: error("empty request map") + return bytes.readJceStruct(factory, 0) + } +} + +fun ByteReadPacket.readJceRequestBufferMapVersion3ToJceStruct(factory: JceStruct.Factory, charset: Charset = CharsetUTF8): J { + this.use { + val bytes = readJceRequestBufferMapVersion3(charset).values.firstOrNull() ?: error("empty request map") + return bytes.readJceStruct(factory, 0, charset) + } +} + +fun ByteReadPacket.readJceRequestBufferMapVersion2(charset: Charset = CharsetUTF8): Map { + this.use { + discardExact(8) + val request = this.asJceInput(charset).use { RequestPacket.newInstanceFrom(it) } + val map = request.sBuffer.asJceInput(charset).withUse { + readNestedMap(0) + } + return map.mapValues { it.value.values.first() } + } +} + +fun ByteReadPacket.readJceRequestBufferMapVersion3(charset: Charset = CharsetUTF8): Map { + this.use { + discardExact(8) + val request = this.asJceInput(charset).use { RequestPacket.newInstanceFrom(it) } + return request.sBuffer.asJceInput(charset).withUse { readMap(0) } + } +} + +fun ByteReadPacket.asJceInput(charset: Charset = CharsetUTF8): JceInput = JceInput(this.readIoBuffer(), charset) + +inline fun IoBuffer.useIoBuffer(block: IoBuffer.() -> R): R { + return try { + block(this) + } catch (first: Throwable) { + throw first + } finally { + release(IoBuffer.Pool) + } +} + +inline fun C.withUse(block: C.() -> R): R { + return try { + block(this) + } catch (first: Throwable) { + throw first + } finally { + close() + } +} @Suppress("MemberVisibilityCanBePrivate") @UseExperimental(ExperimentalUnsignedTypes::class) @@ -87,7 +156,16 @@ class JceInput( fun readIntArray(tag: Int): IntArray = readIntArrayOrNull(tag) ?: error("cannot find tag $tag") fun readBooleanArray(tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: error("cannot find tag $tag") fun readMap(defaultKey: K, defaultValue: V, tag: Int): Map = readMapOrNull(defaultKey, defaultValue, tag) ?: error("cannot find tag $tag") + inline fun readMap(tag: Int): Map = readMapOrNull(tag) ?: error("cannot find tag $tag") + inline fun readNestedMap(tag: Int): Map> = + readNestedMapOrNull(tag) ?: error("cannot find tag $tag") + + inline fun readStringToJceStructMap(factory: JceStruct.Factory, tag: Int): Map = + readStringToJceStructMapOrNull(factory, tag) ?: error("cannot find tag $tag") + fun readList(defaultElement: T, tag: Int): List = readListOrNull(defaultElement, tag) ?: error("cannot find tag $tag") + inline fun readJceStructList(factory: JceStruct.Factory, tag: Int): List = readJceStructListOrNull( factory, tag) ?: error("cannot find tag $tag") + inline fun readList(tag: Int): List = readListOrNull( tag) ?: error("cannot find tag $tag") inline fun readSimpleArray(defaultElement: T, tag: Int): Array = readArrayOrNull(defaultElement, tag) ?: error("cannot find tag $tag") fun readJceStruct(factory: JceStruct.Factory, tag: Int): J = readJceStructOrNull(factory, tag) ?: error("cannot find tag $tag") fun readStringArray(tag: Int): Array = readArrayOrNull("", tag) ?: error("cannot find tag $tag") @@ -217,6 +295,17 @@ class JceInput( } } + inline fun readStringToJceStructMapOrNull(factory: JceStruct.Factory, tag: Int): Map? = skipToTagOrNull(tag) { + check(it.type.toInt() == 8) { "type mismatch: ${it.type}" } + val size = readInt(0) + val map = HashMap(size) + repeat(size) { + map[readString(0)] = readJceStruct(factory, 1) + } + return map + } + + fun readMapOrNull(defaultKey: K, defaultValue: V, tag: Int): Map? = skipToTagOrNull(tag) { check(it.type.toInt() == 8) { "type mismatch: ${it.type}" } val size = readInt(0) @@ -227,6 +316,16 @@ class JceInput( return map } + inline fun readNestedMapOrNull(tag: Int): Map>? = skipToTagOrNull(tag) { + check(it.type.toInt() == 8) { "type mismatch" } + val size = readInt(0) + val map = HashMap>(size) + repeat(size) { + map[readSimpleObject(0)] = readMap(1) + } + return map + } + inline fun readMapOrNull(tag: Int): Map? = skipToTagOrNull(tag) { check(it.type.toInt() == 8) { "type mismatch" } val size = readInt(0) @@ -237,6 +336,26 @@ class JceInput( return map } + inline fun readJceStructListOrNull(factory: JceStruct.Factory, tag: Int): List? = skipToTagOrNull(tag) { head -> + check(head.type.toInt() == 9) { "type mismatch" } + val size = readInt(0) + val list = ArrayList(size) + repeat(size) { + list.add(readJceStruct(factory, 0)) + } + return list + } + + inline fun readListOrNull(tag: Int): List? = skipToTagOrNull(tag) { head -> + check(head.type.toInt() == 9) { "type mismatch" } + val size = readInt(0) + val list = ArrayList(size) + repeat(size) { + list.add(readSimpleObject(0)) + } + return list + } + fun readListOrNull(defaultElement: T, tag: Int): List? = skipToTagOrNull(tag) { head -> check(head.type.toInt() == 9) { "type mismatch" } val size = readInt(0) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 1ea7655b4..275ab8842 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -6,8 +6,8 @@ import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.io.JceInput import net.mamoe.mirai.qqandroid.network.protocol.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.chat.receive.PushNotify 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 @@ -53,7 +53,7 @@ internal object KnownPacketFactories : List> by mutableListOf( LoginPacket, StatSvc.Register, OnlinePush.PbPushGroupMsg, - PushNotify + MessageSvc.PushNotify ) { fun findPacketFactory(commandName: String): PacketFactory<*>? = this.firstOrNull { it.commandName == commandName } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt new file mode 100644 index 000000000..55d4b4b1c --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt @@ -0,0 +1,164 @@ +package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data + +import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.qqandroid.network.io.JceInput +import net.mamoe.mirai.qqandroid.network.io.JceOutput +import net.mamoe.mirai.qqandroid.network.io.JceStruct +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +class RequestPushNotify( + val uin: Long, + val ctype: Byte, + val strService: String, + val strCmd: String, + val vNotifyCookie: ByteArray, + val usMsgType: Int, + val wUserActive: Int, + val wGeneralFlag: Int, + val bindedUin: Long, + val stMsgInfo: MsgInfo, + val msgCtrlBuf: String, + val serverBuf: ByteArray, + val pingFlag: Long, + val svrip: Int +) : Packet, JceStruct() { + override fun writeTo(builder: JceOutput) { + //not needed + } + + companion object : Factory { + override fun newInstanceFrom(input: JceInput): RequestPushNotify { + return RequestPushNotify( + input.read(0L, 0), + input.read(0.toByte(), 1), + input.readString(2), + input.readString(3), + input.read(EMPTY_BYTE_ARRAY, 4), + input.read(0, 5), + input.read(0, 6), + input.read(0, 7), + input.read(0L, 8), + input.readJceStruct(MsgInfo, 9), + input.readString(10), + input.readByteArray(11), + input.readLong(12), + input.readInt(13) + ) + } + } + +} + +class MsgInfo( + val lFromUin: Long, + val uMsgTime: Long, + val shMsgType: Short, + val shMsgSeq: Short, + val strMsg: String, + val uRealMsgTime: Int, + val vMsg: ByteArray, + val uAppShareID: Long, + val vMsgCookies: ByteArray, + val vAppShareCookie: ByteArray, + val lMsgUid: Long, + val lLastChangeTime: Long, + val vCPicInfo: List, + val stShareData: ShareData, + val lFromInstId: Long, + val vRemarkOfSender: ByteArray, + val strFromMobile: String, + val strFromName: String, + val vNickName: List, + val stC2CTmpMsgHead: TempMsgHead? +) : JceStruct() { + companion object : Factory { + override fun newInstanceFrom(input: JceInput): MsgInfo = with(input) { + return MsgInfo( + readLong(0), + readLong(1), + readShort(2), + readShort(3), + readString(4), + readInt(5), + readByteArray(6), + readLong(7), + readByteArray(8), + readByteArray(9), + readLong(10), + readLong(11), + readJceStructList(CPicInfo, 12), + readJceStruct(ShareData, 13), + readLong(14), + readByteArray(15), + readString(16), + readString(17), + readList(18), + readJceStructOrNull(TempMsgHead, 19) + ) + } + + } + + override fun writeTo(builder: JceOutput) { + // not needed + } +} + +class ShareData( + val pkgname: String = "", + val msgtail: String = "", + val picurl: String = "", + val url: String = "" +) : JceStruct() { + companion object : Factory { + override fun newInstanceFrom(input: JceInput): ShareData { + return ShareData( + input.readString(0), + input.readString(1), + input.readString(2), + input.readString(3) + ) + } + } + + override fun writeTo(builder: JceOutput) { + // not needed + } +} + +class TempMsgHead( + val c2c_type: Int, + val serviceType: Int +) : JceStruct() { + override fun writeTo(builder: JceOutput) { + + } + + companion object : Factory { + override fun newInstanceFrom(input: JceInput): TempMsgHead { + return TempMsgHead( + input.readInt(0), + input.readInt(1) + ) + } + } +} + +class CPicInfo( + val vPath: ByteArray, + val vHost: ByteArray? +) : JceStruct() { + override fun writeTo(builder: JceOutput) { + + } + + companion object : Factory { + override fun newInstanceFrom(input: JceInput): CPicInfo { + return CPicInfo( + input.readByteArray(0), + input.readByteArray(1) + ) + } + } + +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt index 12ae416b5..1a060a0c3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt @@ -1,88 +1,19 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.QQAndroidBot -import net.mamoe.mirai.qqandroid.network.io.JceInput -import net.mamoe.mirai.qqandroid.network.io.JceOutput -import net.mamoe.mirai.qqandroid.network.io.JceStruct -import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.qqandroid.network.io.readJceRequestBufferMapVersion2ToJceStruct import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory -import net.mamoe.mirai.utils.io.discardExact -import net.mamoe.mirai.utils.io.readIoBuffer -import net.mamoe.mirai.utils.io.toUHexString +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify +import net.mamoe.mirai.utils.cryptor.contentToString -internal object PushNotify : PacketFactory("MessageSvc.PushNotify") { - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MessageNotification { - return MessageNotification.newInstanceFrom(JceInput(this.apply { discardExact(4) }.readIoBuffer())) - } - - class MessageNotification( - val luni: Long, - val ctype: Byte, - val strService: String, - val strCmd: String, - val vNotifyCookie: ByteArray, - val usMsgType: Int, - val wUserActive: Int, - val wGeneralFlag: Int, - val lBindedUni: Long - ) : Packet, JceStruct() { - init { - println(this.luni) - println(this.ctype) - println(this.strService) - println(this.strCmd) - println(this.vNotifyCookie.toUHexString()) - println(this.usMsgType) - println(this.wUserActive) - println(this.wGeneralFlag) - println(this.lBindedUni) - } - - override fun writeTo(builder: JceOutput) { - //not needed - } - - companion object : Factory { - override fun newInstanceFrom(input: JceInput): MessageNotification { - return MessageNotification( - input.read(0L, 0), - input.read(0.toByte(), 1), - input.readString(2), - input.readString(3), - input.read(EMPTY_BYTE_ARRAY, 4), - input.read(0, 5), - input.read(0, 6), - input.read(0, 7), - input.read(0L, 8) - ) - } +class MessageSvc { + internal object PushNotify : PacketFactory("MessageSvc.PushNotify") { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify { + val messageNotification = readJceRequestBufferMapVersion2ToJceStruct(RequestPushNotify) + println(messageNotification.contentToString()) + TODO() } } - - class MsgInfo( - val lFromUin: Long, - val uMsgTime: Long, - val shMsgType: Short, - val shMsgSeq: Short, - val strMsg: String, - val uRealMsgTime: Int, - val vMsg: ByteArray, - val uAppShareID: Long, - val vMsgCookies: ByteArray, - val vAppShareCookie: ByteArray, - val lMsgUid: Long, - val lLastChangeTime: Long, - //val vCPicInfo: List, - //val stShareData: shareData, - val lFromInstId: Long, - val vRemarkOfSender: ByteArray, - val strFromMobile: String, - val strFromName: String, - val vNickName: List - //val stC2CTmpMsgHead: TempMsgHead - ) - } \ No newline at end of file