Congratulations!! QQAndroid logon successful

This commit is contained in:
Him188 2020-01-23 23:13:24 +08:00
parent 434e65111e
commit 0e150cdc2f
14 changed files with 491 additions and 157 deletions

View File

@ -11,7 +11,6 @@ import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
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.login.LoginPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.RegPushReason
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.SvcReqRegisterPacket
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.*
@ -39,7 +38,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
println()
println()
println("Sending ReqRegister")
SvcReqRegisterPacket(bot.client, RegPushReason.setOnlineStatus).sendAndExpect<SvcReqRegisterPacket.Response>()
SvcReqRegisterPacket(bot.client).sendAndExpect<SvcReqRegisterPacket.Response>()
}
/**

View File

@ -4,7 +4,6 @@ import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.data.OnlineStatus
import net.mamoe.mirai.qqandroid.QQAndroidBot
@ -21,7 +20,10 @@ import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.cryptor.initialPublicKey
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readUShortLVByteArray
import net.mamoe.mirai.utils.io.readUShortLVString
import net.mamoe.mirai.utils.unsafeWeakRef
/*
@ -64,16 +66,11 @@ internal open class QQAndroidClient(
)
}
internal inline fun <R> tryDecryptOrNull(data: ByteReadPacket, mapper: (ByteArray) -> R): R? {
ByteArrayPool.useInstance {
data.readAvailable(it)
keys.forEach { (key, value) ->
kotlin.runCatching {
return mapper(it.decryptBy(value).also { PacketLogger.verbose("成功使用 $key 解密") })
}
internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? {
keys.forEach { (key, value) ->
kotlin.runCatching {
return mapper(data.decryptBy(value, size).also { PacketLogger.verbose("成功使用 $key 解密") })
}
}
return null
}

View File

@ -1,10 +1,10 @@
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.utils.io.readString
import net.mamoe.mirai.utils.io.toIoBuffer
import kotlin.experimental.and
@UseExperimental(ExperimentalUnsignedTypes::class)
inline class JceHead(private val value: Long) {
@ -12,17 +12,23 @@ inline class JceHead(private val value: Long) {
val tag: Int get() = (value ushr 32).toInt()
val type: Byte get() = value.toUInt().toByte()
override fun toString(): String {
return "JceHead(tag=$tag, type=$type)"
}
}
fun ByteArray.asJceInput(): JceInput = JceInput(this.toIoBuffer())
fun ByteArray.asJceInput(charset: Charset = CharsetGBK): JceInput = JceInput(this.toIoBuffer(), charset)
@Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalUnsignedTypes::class)
class JceInput(
@PublishedApi
internal val input: IoBuffer,
private val charset: Charset = CharsetGBK,
private val pool: ObjectPool<IoBuffer> = IoBuffer.Pool
) : Closeable {
constructor(input: Input) : this(IoBuffer.Pool.borrow().also { input.readAvailable(it) })
override fun close() {
@ -36,12 +42,12 @@ class JceInput(
internal fun peakHead(): JceHead = input.makeView().readHead()
private fun IoBuffer.readHead(): JceHead {
val var2 = readByte()
val type = var2 and 15
var tag = (var2.toInt() and 240) shr 4
if (tag == 15)
tag = readByte().toInt() and 255
return JceHead(tag = tag, type = type)
val var2 = readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u)
tag = readUByte().toUInt()
return JceHead(tag = tag.toInt(), type = type.toByte())
}
fun read(default: Byte, tag: Int): Byte = readByteOrNull(tag) ?: default
@ -79,7 +85,7 @@ class JceInput(
fun readBooleanArray(tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: error("cannot find tag $tag")
fun <K, V> readMap(defaultKey: K, defaultValue: V, tag: Int): Map<K, V> = readMapOrNull(defaultKey, defaultValue, tag) ?: error("cannot find tag $tag")
fun <T> readList(defaultElement: T, tag: Int): List<T> = readListOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
fun <T> readSimpleArray(defaultElement: T, tag: Int): Array<T> = readArrayOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
inline fun <reified T> readSimpleArray(defaultElement: T, tag: Int): Array<T> = readArrayOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
fun <J : JceStruct> readJceStruct(factory: JceStruct.Factory<J>, tag: Int): J = readJceStructOrNull(factory, tag) ?: error("cannot find tag $tag")
fun readStringArray(tag: Int): Array<String> = readArrayOrNull("", tag) ?: error("cannot find tag $tag")
@ -142,7 +148,7 @@ class JceInput(
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
when (it.type.toInt()) {
9 -> ByteArray(input.readInt()) { readByte(tag) }
9 -> ByteArray(readInt(0)) { readByte(0) }
13 -> {
val head = readHead()
check(head.type.toInt() == 0) { "type mismatch" }
@ -154,56 +160,56 @@ class JceInput(
fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
ShortArray(input.readInt()) { readShort(tag) }
ShortArray(readInt(0)) { readShort(0) }
}
fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
DoubleArray(input.readInt()) { readDouble(tag) }
DoubleArray(readInt(0)) { readDouble(0) }
}
fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
FloatArray(input.readInt()) { readFloat(tag) }
FloatArray(readInt(0)) { readFloat(0) }
}
fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
IntArray(input.readInt()) { readInt(tag) }
IntArray(readInt(0)) { readInt(0) }
}
fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
LongArray(input.readInt()) { readLong(tag) }
LongArray(readInt(0)) { readLong(0) }
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T> readArrayOrNull(tag: Int): Array<T>? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
Array(input.readInt()) { readSimpleObject<T>(tag) }
Array(readInt(0)) { readSimpleObject<T>(0) }
}
@Suppress("UNCHECKED_CAST")
fun <T> readArrayOrNull(defaultElement: T, tag: Int): Array<T>? = skipToTagOrNull(tag) {
inline fun <reified T> readArrayOrNull(defaultElement: T, tag: Int): Array<T>? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
Array(input.readInt()) { readObject(defaultElement, tag) as Any } as Array<T>
Array(readInt(0)) { readObject(defaultElement, 0) }
}
@Suppress("UNCHECKED_CAST")
fun <J : JceStruct> readJceStructArrayOrNull(factory: JceStruct.Factory<J>, tag: Int): Array<J>? = skipToTagOrNull(tag) {
inline fun <reified J : JceStruct> readJceStructArrayOrNull(factory: JceStruct.Factory<J>, tag: Int): Array<J>? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
Array(input.readInt()) { readObject(factory, tag) as Any } as Array<J>
Array(readInt(0)) { readJceStruct(factory, 0) }
}
fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
BooleanArray(input.readInt()) { readBoolean(tag) }
BooleanArray(readInt(0)) { readBoolean(0) }
}
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
return when (head.type.toInt()) {
6 -> input.readString(input.readUByte().toInt())
7 -> input.readString(input.readUInt().also { require(it.toInt() in 1 until 104857600) { "bad string length: $it" } }.toInt())
6 -> input.readString(input.readUByte().toInt(), charset = charset)
7 -> input.readString(input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, charset = charset)
else -> error("type mismatch: ${head.type}")
}
}
@ -233,7 +239,7 @@ class JceInput(
val size = readInt(0)
val list = ArrayList<T>(size)
repeat(size) {
list[it] = readObject(defaultElement, tag)
list.add(readObject(defaultElement, 0))
}
return list
}
@ -267,9 +273,8 @@ class JceInput(
is ByteArray -> readByteArray(tag)
is FloatArray -> readByteArray(tag)
is DoubleArray -> readDoubleArrayOrNull(tag)
is JceStruct.Factory<JceStruct> -> readJceStruct(default, tag) as T
is List<*> -> {
readList(default[0], tag)
readList(default, tag)
}
is Map<*, *> -> {
val entry = default.entries.first()
@ -325,19 +330,13 @@ class JceInput(
6 -> this.input.discardExact(this.input.readUByte().toInt())
7 -> this.input.discardExact(this.input.readInt())
8 -> { // map
val length = this.readInt(0)
var read = 0
while (read < length * 2) {
repeat(this.readInt(0) * 2) {
skipField()
++read
}
}
9 -> { // list
val length = this.readInt(0)
var read = 0
while (read < length) {
repeat(this.readInt(0)) {
skipField()
++read
}
}
10 -> this.skipToStructEnd()
@ -360,8 +359,10 @@ private inline fun <R> JceInput.skipToTag(tag: Int, block: (JceHead) -> R): R {
@PublishedApi
internal inline fun <R> JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
while (true) {
if (this.input.endOfInput)
if (this.input.endOfInput) {
println("endOfInput")
return null
}
val head = peakHead()
if (head.tag > tag) {

View File

@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.network.io
import kotlinx.io.charsets.Charset
import kotlinx.io.core.*
import kotlin.experimental.or
import kotlin.reflect.KClass
@PublishedApi
@ -45,7 +46,7 @@ class JceOutput(
fun writeByte(v: Byte, tag: Int) {
if (v.toInt() == 0) {
writeHead(ZERO_TAG, tag)
writeHead(ZERO_TYPE, tag)
} else {
writeHead(BYTE, tag)
output.writeByte(v)
@ -147,7 +148,7 @@ class JceOutput(
if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
writeByte(v.toByte(), tag)
} else {
writeHead(BYTE, tag)
writeHead(SHORT, tag)
output.writeShort(v)
}
}
@ -266,7 +267,7 @@ class JceOutput(
const val STRING4: Int = 7
const val STRUCT_BEGIN: Int = 10
const val STRUCT_END: Int = 11
const val ZERO_TAG: Int = 12
const val ZERO_TYPE: Int = 12
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
}
@ -278,7 +279,7 @@ class JceOutput(
return
}
if (tag < 256) {
this.output.writeByte((type or 0xF0).toByte())
this.output.writeByte((type.toByte() or 0xF0.toByte()))
this.output.writeByte(tag.toByte())
return
}

View File

@ -3,7 +3,7 @@ package net.mamoe.mirai.qqandroid.network.io
abstract class JceStruct {
abstract fun writeTo(builder: JceOutput)
interface Factory<out T : JceStruct> {
interface Factory<out T : JceStruct> {
fun newInstanceFrom(input: JceInput): T
}
}

View File

@ -26,12 +26,12 @@ class SvcReqRegister(
var lBid: Long = 0L,
var lCpId: Long = 0L,
var lUin: Long = 0L,
var sBuildVer: String? = null,
var sChannelNo: String? = null,
var sBuildVer: String? = "",
var sChannelNo: String? = "",
var sOther: String = "",
var strDevName: String? = null,
var strDevType: String? = null,
var strIOSIdfa: String? = null,
var strIOSIdfa: String? = "",
var strOSVer: String? = null,
var strVendorName: String? = null,
var strVendorOSName: String? = null,
@ -59,7 +59,7 @@ class SvcReqRegister(
this.timeStamp = input.readLong(10)
this.iOSVersion = input.readLong(11)
this.cNetType = input.readByte(12)
this.sBuildVer = input.readString(13)
this.sBuildVer = input.readStringOrNull(13)
this.bRegType = input.readByte(14)
this.vecDevParam = input.readByteArrayOrNull(15)
this.vecGuid = input.readByteArrayOrNull(16)

View File

@ -64,7 +64,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
* full packet without length
*/
// do not inline. Exceptions thrown will not be reported correctly
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer) {
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer): Unit {
rawInput.readBytes().let {
PacketLogger.verbose("开始处理包: ${it.toUHexString()}")
it.toReadPacket()
@ -89,28 +89,34 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
//debugPrint("remaining")
(if (flag2 == 2) {
PacketLogger.verbose("SSO, 尝试使用 16 zero 解密.")
kotlin.runCatching {
decryptBy(DECRYPTER_16_ZERO).also { PacketLogger.verbose("成功使用 16 zero 解密") }
ByteArrayPool.useInstance { data ->
val size = this.readAvailable(data)
(if (flag2 == 2) {
PacketLogger.verbose("SSO, 尝试使用 16 zero 解密.")
kotlin.runCatching {
data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose("成功使用 16 zero 解密") }
}
} else {
PacketLogger.verbose("Uni, 尝试使用 d2Key 解密.")
kotlin.runCatching {
data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose("成功使用 d2Key 解密") }
}
}).getOrElse {
PacketLogger.verbose("失败, 尝试其他各种key")
bot.client.tryDecryptOrNull(data) { it }
}?.toReadPacket()?.also { decryptedData ->
when (flag1) {
0x0A -> parseLoginSsoPacket(bot, decryptedData, consumer)
0x0B -> parseUniPacket(bot, decryptedData, consumer)
}
} ?: inline {
PacketLogger.error("任何key都无法解密: ${data.toUHexString()}")
return
}
} else {
PacketLogger.verbose("Uni, 尝试使用 d2Key 解密.")
kotlin.runCatching {
decryptBy(bot.client.wLoginSigInfo.d2Key).also { PacketLogger.verbose("成功使用 d2Key 解密") }
}
}).getOrElse {
PacketLogger.verbose("失败, 尝试其他各种key")
bot.client.tryDecryptOrNull(this) { it.toReadPacket() }
}?.also { decryptedData ->
when(flag1) {
0x0A -> parseLoginSsoPacket(bot, decryptedData, consumer)
0x0B -> parseUniPacket(bot, decryptedData, consumer)
}
} ?: inline {
PacketLogger.error("任何key都无法解密")
return
}
Unit
}
}
@ -155,6 +161,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
if (packetFactory == null) {
bot.logger.warning("找不到包 PacketFactory")
PacketLogger.verbose("最外层解密后的 body = ${this.readBytes().toUHexString()}")
return
}

View File

@ -1,12 +1,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
import net.mamoe.mirai.qqandroid.network.io.jceMap
import net.mamoe.mirai.qqandroid.network.io.jceStruct
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
@ -17,7 +15,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacke
import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket
@ -64,65 +61,64 @@ internal object SvcReqRegisterPacket : PacketFactory<SvcReqRegisterPacket.Respon
sFuncName = "SvcReqRegister"
sBuffer = jceMap(
0,
"SvcReqRegister" to buildJcePacket {
writeObject(jceStruct(
0,
SvcReqRegister(
cConnType = 0,
lBid = 1 or 2 or 4,
lUin = client.uin,
iStatus = client.onlineStatus.id,
bKikPC = 0, // 是否把 PC 踢下线
bKikWeak = 0,
timeStamp = currentTimeSeconds, // millis or seconds??
iLargeSeq = 1551, // ?
bOpenPush = 1,
iLocaleID = 2052,
bRegType =
"SvcReqRegister" to jceStruct(
0,
SvcReqRegister().apply {
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(),
) 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 = ProtoBuf.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 = ProtoBuf.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf(
Oidb0x769.ConfigSeq(
type = 46,
version = 0
),
Oidb0x769.ConfigSeq(
type = 283,
version = 0
)
)
),
bSetMute = 0
)
)
), 0)
}.readBytes()
bSetMute = 0
}
)
)
}
this.writePacket(this.build().debugPrint("sso body"))

View File

@ -152,11 +152,51 @@ fun Any?.contentToString(prefix: String = ""): String = when (this) {
is ByteArray -> {
if (this.size == 0) "<Empty ByteArray>"
else this.toUHexString()// + " (${this.encodeToString()})"
else this.toUHexString()
}
is UByteArray -> {
if (this.size == 0) "<Empty UByteArray>"
else this.toUHexString()// + " (${this.encodeToString()})"
else this.toUHexString()
}
is ShortArray -> {
if (this.size == 0) "<Empty ShortArray>"
else this.iterator().contentToString()
}
is IntArray -> {
if (this.size == 0) "<Empty IntArray>"
else this.iterator().contentToString()
}
is LongArray -> {
if (this.size == 0) "<Empty LongArray>"
else this.iterator().contentToString()
}
is FloatArray -> {
if (this.size == 0) "<Empty FloatArray>"
else this.iterator().contentToString()
}
is DoubleArray -> {
if (this.size == 0) "<Empty DoubleArray>"
else this.iterator().contentToString()
}
is UShortArray -> {
if (this.size == 0) "<Empty ShortArray>"
else this.iterator().contentToString()
}
is UIntArray -> {
if (this.size == 0) "<Empty IntArray>"
else this.iterator().contentToString()
}
is ULongArray -> {
if (this.size == 0) "<Empty LongArray>"
else this.iterator().contentToString()
}
is Array<*> -> {
if (this.size == 0) "<Empty Array>"
else this.iterator().contentToString()
}
is BooleanArray -> {
if (this.size == 0) "<Empty BooleanArray>"
else this.iterator().contentToString()
}
is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}"

View File

@ -3,6 +3,8 @@
package net.mamoe.mirai.utils.io
import kotlinx.io.OutputStream
import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.contact.GroupId
@ -180,13 +182,13 @@ internal inline fun illegalArgument(message: String? = null): Nothing = error(me
fun Map<Int, String>.printTLVMap(name: String = "") =
debugPrintln("TLVMap $name= " + this.mapKeys { it.key.toInt().toUShort().toUHexString() })
fun Input.readString(length: Int): String = String(this.readBytes(length))
fun Input.readString(length: Long): String = String(this.readBytes(length.toInt()))
fun Input.readString(length: Short): String = String(this.readBytes(length.toInt()))
fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset)
fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
@JvmSynthetic
fun Input.readString(length: UShort): String = String(this.readBytes(length.toInt()))
fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
fun Input.readString(length: Byte): String = String(this.readBytes(length.toInt()))
fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
@JvmSynthetic
fun Input.readStringUntil(stopSignalExclude: UByte, expectingEOF: Boolean = false): String = readStringUntil(stopSignalExclude.toByte(), expectingEOF)

View File

@ -353,6 +353,8 @@ public final class JceInputStream {
throw new JceDecodeException(var5.getMessage());
}
System.out.println(var1.toString());
var4 = new JceInputStream$HeadData();
this.readHead(var4);
if (var4.type != 10) {

View File

@ -400,18 +400,18 @@ public class JceOutputStream {
}
}
public void writeHead(byte var1, int var2) {
byte var3;
if (var2 < 15) {
var3 = (byte) (var2 << 4 | var1);
this.field_80728.put(var3);
} else if (var2 < 256) {
var3 = (byte) (var1 | 240);
this.field_80728.put(var3);
this.field_80728.put((byte) var2);
} else {
throw new JceEncodeException("tag is too large: " + var2);
}
public void writeHead(byte var1, int tag) {
byte var3;
if (tag < 15) {
var3 = (byte) (tag << 4 | var1);
this.field_80728.put(var3);
} else if (tag < 256) {
var3 = (byte) (var1 | 240);
this.field_80728.put(var3);
this.field_80728.put((byte) tag);
} else {
throw new JceEncodeException("tag is too large: " + tag);
}
}
public void writeStringByte(String var1, int var2) {

View File

@ -0,0 +1,287 @@
package jceTest
import io.ktor.util.InternalAPI
import jce.jce.JceInputStream
import jceTest.JceOutputTest.TestMiraiStruct
import jceTest.JceOutputTest.TestQQStruct
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.qqandroid.network.io.JceInput
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.io.toIoBuffer
import org.junit.Test
private infix fun <T> T.shouldEqualTo(another: T) {
if (this is Array<*>) {
this.contentEquals(another as Array<*>)
} else
check(this.contentToString() == another.contentToString()) {
"""actual: ${this.contentToString()}
|required: ${another.contentToString()}
""".trimMargin()
}
}
@UseExperimental(InternalAPI::class)
private fun <R> ByteArray.qqJce(block: JceInputStream.() -> R): R {
return JceInputStream(this).run(block)
}
private fun <R> ByteArray.read(block: JceInput.() -> R): R {
return JceInput(this.toIoBuffer()).run(block)
}
private fun ByteReadPacket.check(block: ByteArray.() -> Unit) {
this.readBytes().apply(block)
}
internal class JceInputTest {
@Test
fun readByte() = buildJcePacket {
writeByte(1, 1)
}.check {
read {
readByte(1)
} shouldEqualTo qqJce {
read(0.toByte(), 1, true)
}
}
@Test
fun readDouble() = buildJcePacket {
writeDouble(1.0, 1)
}.check {
read {
readDouble(1)
} shouldEqualTo qqJce {
read(0.toDouble(), 1, true)
}
}
@Test
fun readFloat() = buildJcePacket {
writeFloat(1.0f, 1)
}.check {
read {
readFloat(1)
} shouldEqualTo qqJce {
read(0.toFloat(), 1, true)
}
}
@Test
fun readFully() = buildJcePacket {
writeFully(byteArrayOf(1, 2, 3), 1)
}.check {
read {
readByteArray(1)
} shouldEqualTo qqJce {
read(byteArrayOf(), 1, true)
}
}
@Test
fun testWriteFully() = buildJcePacket {
writeFully(shortArrayOf(1, 2, 3), 1)
}.check {
read {
readShortArray(1)
} shouldEqualTo qqJce {
read(shortArrayOf(), 1, true)
}
}
@Test
fun testWriteFully1() = buildJcePacket {
writeFully(intArrayOf(1, 2, 3), 1)
}.check {
read {
readIntArray(1)
} shouldEqualTo qqJce {
read(intArrayOf(), 1, true)
}
}
@Test
fun testWriteFully2() = buildJcePacket {
writeFully(longArrayOf(1, 2, 3), 1)
}.check {
read {
readLongArray(1)
} shouldEqualTo qqJce {
read(longArrayOf(), 1, true)
}
}
@Test
fun testWriteFully3() = buildJcePacket {
writeFully(booleanArrayOf(true, false, true), 1)
}.check {
read {
readBooleanArray(1)
} shouldEqualTo qqJce {
read(booleanArrayOf(), 1, true)
}
}
@Test
fun testWriteFully4() = buildJcePacket {
writeFully(floatArrayOf(1f, 2f, 3f), 1)
}.check {
read {
readFloatArray(1)
} shouldEqualTo qqJce {
read(floatArrayOf(), 1, true)
}
}
@Test
fun testWriteFully5() = buildJcePacket {
writeFully(doubleArrayOf(1.0, 2.0, 3.0), 1)
}.check {
read {
readDoubleArray(1)
} shouldEqualTo qqJce {
read(doubleArrayOf(), 1, true)
}
}
@Test
fun testWriteFully6() = buildJcePacket {
writeFully(arrayOf("sss", "哈哈"), 1)
}.check {
read {
readSimpleArray("", 1)
} shouldEqualTo qqJce {
read(arrayOf(""), 1, true)
}
}
@Test
fun testWriteFully7() = buildJcePacket {
writeFully(arrayOf("sss", "哈哈"), 1)
}.check {
read {
readArrayOrNull("", 1)!!
} shouldEqualTo qqJce {
read(arrayOf(""), 1, true)
}
}
@Test
fun testWriteFully8() = buildJcePacket {
writeFully(arrayOf(TestMiraiStruct("Haha")), 1)
}.check {
read {
readJceStructArrayOrNull(TestMiraiStruct, 1)!!
} shouldEqualTo qqJce {
read(arrayOf(TestQQStruct("stub")), 1, true)
}
}
@Test
fun readInt() = buildJcePacket {
writeInt(1, 2)
}.check {
read {
readInt(2)
} shouldEqualTo qqJce {
read(0, 2, true)
}
}
@Test
fun readLong() = buildJcePacket {
writeLong(1, 2)
}.check {
read {
readLong(2)
} shouldEqualTo qqJce {
read(0L, 2, true)
}
}
@Test
fun readShort() = buildJcePacket {
writeShort(1, 2)
}.check {
read {
readShort(2)
} shouldEqualTo qqJce {
read(0.toShort(), 2, true)
}
}
@Test
fun readBoolean() = buildJcePacket {
writeBoolean(true, 2)
}.check {
read {
readBoolean(2)
} shouldEqualTo qqJce {
read(false, 2, true)
}
}
@Test
fun readString() = buildJcePacket {
writeString("", 2)
}.check {
read {
readString(2)
} shouldEqualTo qqJce {
read("", 2, true)
}
}
@Test
fun readMap() = buildJcePacket {
writeMap(mapOf(123.0 to "Hello"), 3)
}.check {
read {
readMap(0.0, "", 3)
} shouldEqualTo qqJce {
read(mapOf(0.0 to ""), 3, true)
}
}
@Test
fun readCollection() = buildJcePacket {
writeCollection(listOf("1", ""), 3)
}.check {
repeat(0) {
error("fuck kotlin")
}
read {
readList("", 3)
} shouldEqualTo qqJce {
read(listOf(""), 3, true)
}
}
@Test
fun readJceStruct() = buildJcePacket {
writeJceStruct(TestMiraiStruct("123"), 3)
}.check {
read {
readJceStruct(TestMiraiStruct, 3)
} shouldEqualTo qqJce {
read(TestQQStruct("stub"), 3, true)!!
}
}
@Test
fun readObject() = buildJcePacket {
writeObject(123, 3)
}.check {
read {
readObject(123, 3)
} shouldEqualTo qqJce {
read(123 as Any, 3, true)
}
}
}

View File

@ -221,13 +221,9 @@ internal class JceOutputTest {
@Test
fun writeCollection() {
buildJcePacket {
writeMap(mapOf("" to ""), 1)
writeMap(mapOf("" to 123), 2)
writeMap(mapOf(123.0 to "Hello"), 3)
writeCollection(listOf("", "333", "1"), 1)
} shouldEqualTo qqJce {
write(mapOf("" to ""), 1)
write(mapOf("" to 123), 2)
write(mapOf(123.0 to "Hello"), 3)
write(listOf("", "333", "1"), 1)
}
}
@ -248,6 +244,8 @@ internal class JceOutputTest {
class TestQQStruct(
private var message: String
) : JceStruct() {
constructor() : this("")
override fun readFrom(var1: JceInputStream) {
message = var1.read("", 0, true)
}
@ -255,6 +253,10 @@ internal class JceOutputTest {
override fun writeTo(var1: JceOutputStream) {
var1.write(message, 0)
}
override fun toString(): String {
return "TestMiraiStruct(message=$message)"
}
}
@Test