Commit 55e6c934 authored by Him188's avatar Him188

JceStruct serialization

parent 2f4a4a48
package net.mamoe.mirai.qqandroid.io
import kotlinx.io.charsets.Charset
import kotlinx.io.core.*
import kotlin.experimental.or
import kotlin.reflect.KClass
@PublishedApi
internal val CharsetGBK = Charset.forName("GBK")
@PublishedApi
internal val CharsetUTF8 = Charset.forName("UTF8")
inline fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
return JceOutput(stringCharset).apply(block).build()
}
inline fun BytePacketBuilder.writeJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit) {
return this.writePacket(buildJcePacket(stringCharset, block))
}
fun jceStruct(tag: Int, struct: JceStruct): ByteArray{
return buildJcePacket {
writeJceStruct(struct, tag)
}.readBytes()
}
fun <K, V> jceMap(tag: Int, vararg entries: Pair<K, V>): ByteArray {
return buildJcePacket {
writeMap(mapOf(*entries), tag)
}.readBytes()
}
/**
*
* From: com.qq.taf.jce.JceOutputStream
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalIoApi::class)
class JceOutput(
private val stringCharset: Charset = CharsetGBK
) {
private val output: BytePacketBuilder = BytePacketBuilder()
fun build(): ByteReadPacket = output.build()
fun close() = output.close()
fun flush() = output.flush()
fun writeByte(v: Byte, tag: Int) {
if (v.toInt() == 0) {
writeHead(ZERO_TYPE, tag)
} else {
writeHead(BYTE, tag)
output.writeByte(v)
}
}
fun writeDouble(v: Double, tag: Int) {
writeHead(DOUBLE, tag)
output.writeDouble(v)
}
fun writeFloat(v: Float, tag: Int) {
writeHead(FLOAT, tag)
output.writeFloat(v)
}
fun writeFully(src: ByteArray, tag: Int) {
writeHead(SIMPLE_LIST, tag)
writeHead(BYTE, 0)
writeInt(src.size, 0)
output.writeFully(src)
}
fun writeFully(src: DoubleArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeDouble(it, 0)
}
}
fun writeFully(src: FloatArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeFloat(it, 0)
}
}
fun writeFully(src: IntArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeInt(it, 0)
}
}
fun writeFully(src: LongArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeLong(it, 0)
}
}
fun writeFully(src: ShortArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeShort(it, 0)
}
}
fun writeFully(src: BooleanArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeBoolean(it, 0)
}
}
fun <T> writeFully(src: Array<T>, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeObject(it, 0)
}
}
fun writeInt(v: Int, tag: Int) {
if (v in Short.MIN_VALUE..Short.MAX_VALUE) {
writeShort(v.toShort(), tag)
} else {
writeHead(INT, tag)
output.writeInt(v)
}
}
fun writeLong(v: Long, tag: Int) {
if (v in Int.MIN_VALUE..Int.MAX_VALUE) {
writeInt(v.toInt(), tag)
} else {
writeHead(LONG, tag)
output.writeLong(v)
}
}
fun writeShort(v: Short, tag: Int) {
if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
writeByte(v.toByte(), tag)
} else {
writeHead(SHORT, tag)
output.writeShort(v)
}
}
fun writeBoolean(v: Boolean, tag: Int) {
this.writeByte(if (v) 1 else 0, tag)
}
fun writeString(v: String, tag: Int) {
val array = v.toByteArray(stringCharset)
if (array.size > 255) {
writeHead(STRING4, tag)
output.writeInt(array.size)
output.writeFully(array)
} else {
writeHead(STRING1, tag)
output.writeByte(array.size.toByte())
output.writeFully(array)
}
}
fun <K, V> writeMap(map: Map<K, V>, tag: Int) {
writeHead(MAP, tag)
if (map.isEmpty()) {
writeInt(0, 0)
} else {
writeInt(map.size, 0)
map.forEach { (key, value) ->
writeObject(key, 0)
writeObject(value, 1)
}
}
}
fun writeCollection(collection: Collection<*>?, tag: Int) {
writeHead(LIST, tag)
if (collection == null || collection.isEmpty()) {
writeInt(0, 0)
} else {
writeInt(collection.size, 0)
collection.forEach {
writeObject(it, 0)
}
}
}
fun writeJceStruct(v: JceStruct, tag: Int) {
writeHead(STRUCT_BEGIN, tag)
v.writeTo(this)
writeHead(STRUCT_END, 0)
}
fun <T> writeObject(v: T, tag: Int) {
when (v) {
is Byte -> writeByte(v, tag)
is Short -> writeShort(v, tag)
is Int -> writeInt(v, tag)
is Long -> writeLong(v, tag)
is Float -> writeFloat(v, tag)
is Double -> writeDouble(v, tag)
is JceStruct -> writeJceStruct(v, tag)
is ByteArray -> writeFully(v, tag)
is Collection<*> -> writeCollection(v, tag)
is Boolean -> writeBoolean(v, tag)
is Map<*, *> -> writeMap(v, tag)
is IntArray -> writeFully(v, tag)
is ShortArray -> writeFully(v, tag)
is BooleanArray -> writeFully(v, tag)
is LongArray -> writeFully(v, tag)
is FloatArray -> writeFully(v, tag)
is DoubleArray -> writeFully(v, tag)
is Array<*> -> writeFully(v, tag)
is String -> writeString(v, tag)
//
// is ByteReadPacket -> ByteArrayPool.useInstance {
// v.readAvailable(it)
// writeFully(it, tag)
// }
else -> error("unsupported type: ${v.getClassName()}")
}
}
fun write(v: Int, tag: Int) = writeInt(v, tag)
fun write(v: Byte, tag: Int) = writeByte(v, tag)
fun write(v: Short, tag: Int) = writeShort(v, tag)
fun write(v: Long, tag: Int) = writeLong(v, tag)
fun write(v: Float, tag: Int) = writeFloat(v, tag)
fun write(v: Double, tag: Int) = writeDouble(v, tag)
fun write(v: String, tag: Int) = writeString(v, tag)
fun write(v: Boolean, tag: Int) = writeBoolean(v, tag)
fun write(v: Collection<*>, tag: Int) = writeCollection(v, tag)
fun write(v: Map<*, *>, tag: Int) = writeMap(v, tag)
fun write(v: ByteArray, tag: Int) = writeFully(v, tag)
fun write(v: IntArray, tag: Int) = writeFully(v, tag)
fun write(v: BooleanArray, tag: Int) = writeFully(v, tag)
fun write(v: LongArray, tag: Int) = writeFully(v, tag)
fun write(v: ShortArray, tag: Int) = writeFully(v, tag)
fun write(v: Array<*>, tag: Int) = writeFully(v, tag)
fun write(v: FloatArray, tag: Int) = writeFully(v, tag)
fun write(v: DoubleArray, tag: Int) = writeFully(v, tag)
@PublishedApi
internal companion object {
const val BYTE: Int = 0
const val DOUBLE: Int = 5
const val FLOAT: Int = 4
const val INT: Int = 2
const val JCE_MAX_STRING_LENGTH = 104857600
const val LIST: Int = 9
const val LONG: Int = 3
const val MAP: Int = 8
const val SHORT: Int = 1
const val SIMPLE_LIST: Int = 13
const val STRING1: Int = 6
const val STRING4: Int = 7
const val STRUCT_BEGIN: Int = 10
const val STRUCT_END: Int = 11
const val ZERO_TYPE: Int = 12
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
}
@PublishedApi
internal fun writeHead(type: Int, tag: Int) {
if (tag < 15) {
this.output.writeByte(((tag shl 4) or type).toByte())
return
}
if (tag < 256) {
this.output.writeByte((type.toByte() or 0xF0.toByte()))
this.output.writeByte(tag.toByte())
return
}
throw JceEncodeException("tag is too large: $tag")
}
}
class JceEncodeException(message: String) : RuntimeException(message)
\ No newline at end of file
package net.mamoe.mirai.qqandroid.io
interface JceStruct {
}
\ No newline at end of file
interface JceStruct
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.jce
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
private val EMPTY_MAP = mapOf<String, String>()
private val EMPTY_SBUFFER_MAP = mapOf<Int, ByteArray>()
......@@ -15,8 +18,23 @@ class RequestPacket(
@SerialId(4) val iRequestId: Int = 0,
@SerialId(5) val sServantName: String = "",
@SerialId(6) val sFuncName: String = "",
@SerialId(7) val sBuffer: Map<Int, ByteArray> = EMPTY_SBUFFER_MAP,
@SerialId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val iTimeout: Int = 0,
@SerialId(9) val context: Map<String, String> = EMPTY_MAP,
@SerialId(10) val status: Map<String, String> = EMPTY_MAP
) : JceStruct
@Serializable
class RequestDataVersion3(
@SerialId(0) val map: Map<String, ByteArray>
) : JceStruct
@Serializable
class RequestDataVersion2(
@SerialId(0) val map: Map<String, Map<String, ByteArray>>
) : JceStruct
@Serializable
class RequestDataStructSvcReqRegister(
@SerialId(0) val struct: SvcReqRegister
) : JceStruct
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.jce
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceStruct
@Serializable
class SvcReqRegister(
@SerialId(6) var bIsOnline: Byte = 0,
@SerialId(34) var bIsSetStatus: Byte = 0,
@SerialId(7) var bIsShowOnline: Byte = 0,
@SerialId(8) var bKikPC: Byte = 0,
@SerialId(9) var bKikWeak: Byte = 0,
@SerialId(5) var bOnlinePush: Byte = 0,
@SerialId(22) var bOpenPush: Byte = 1,
@SerialId(14) var bRegType: Byte = 0,
@SerialId(36) var bSetMute: Byte = 0,
@SerialId(18) var bSlientPush: Byte = 0,
@SerialId(33) var bytes_0x769_reqbody: ByteArray? = null,
@SerialId(2) var cConnType: Byte = 0,
@SerialId(12) var cNetType: Byte = 0,
@SerialId(23) var iLargeSeq: Long = 0L,
@SerialId(24) var iLastWatchStartTime: Long = 0L,
@SerialId(17) var iLocaleID: Int = 2052,
@SerialId(11) var iOSVersion: Long = 0L,
@SerialId(4) var iStatus: Int = 11,
@SerialId(1) var lBid: Long = 0L,
@SerialId(29) var lCpId: Long = 0L,
@SerialId(0) var lUin: Long = 0L,
@SerialId(13) var sBuildVer: String? = "",
@SerialId(28) var sChannelNo: String? = "",
@SerialId(3) var sOther: String = "",
@SerialId(19) var strDevName: String? = null,
@SerialId(20) var strDevType: String? = null,
@SerialId(32) var strIOSIdfa: String? = "",
@SerialId(21) var strOSVer: String? = null,
@SerialId(30) var strVendorName: String? = null,
@SerialId(31) var strVendorOSName: String? = null,
@SerialId(10) var timeStamp: Long = 0L,
@SerialId(27) var uNewSSOIp: Long = 0L,
@SerialId(26) var uOldSSOIp: Long = 0L,
@SerialId(15) var vecDevParam: ByteArray? = null,
@SerialId(16) var vecGuid: ByteArray? = null,
@SerialId(35) var vecServerBuf: ByteArray? = null
@SerialId(0) val lUin: Long = 0L,
@SerialId(1) val lBid: Long = 0L,
@SerialId(2) val cConnType: Byte = 0,
@SerialId(3) val sOther: String = "",
@SerialId(4) val iStatus: Int = 11,
@SerialId(5) val bOnlinePush: Byte = 0,
@SerialId(6) val bIsOnline: Byte = 0,
@SerialId(7) val bIsShowOnline: Byte = 0,
@SerialId(8) val bKikPC: Byte = 0,
@SerialId(9) val bKikWeak: Byte = 0,
@SerialId(10) val timeStamp: Long = 0L,
@SerialId(11) val iOSVersion: Long = 0L,
@SerialId(12) val cNetType: Byte = 0,
@SerialId(13) val sBuildVer: String? = "",
@SerialId(14) val bRegType: Byte = 0,
@SerialId(15) val vecDevParam: ByteArray? = null,
@SerialId(16) val vecGuid: ByteArray? = null,
@SerialId(17) val iLocaleID: Int = 2052,
@SerialId(18) val bSlientPush: Byte = 0,
@SerialId(19) val strDevName: String? = null,
@SerialId(20) val strDevType: String? = null,
@SerialId(21) val strOSVer: String? = null,
@SerialId(22) val bOpenPush: Byte = 1,
@SerialId(23) val iLargeSeq: Long = 0L,
@SerialId(24) val iLastWatchStartTime: Long = 0L,
@SerialId(26) val uOldSSOIp: Long = 0L,
@SerialId(27) val uNewSSOIp: Long = 0L,
@SerialId(28) val sChannelNo: String? = "",
@SerialId(29) val lCpId: Long = 0L,
@SerialId(30) val strVendorName: String? = null,
@SerialId(31) val strVendorOSName: String? = null,
@SerialId(32) val strIOSIdfa: String? = "",
@SerialId(33) val bytes_0x769_reqbody: ByteArray? = null,
@SerialId(34) val bIsSetStatus: Byte = 0,
@SerialId(35) val vecServerBuf: ByteArray? = null,
@SerialId(36) val bSetMute: Byte = 0
// @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型
) : JceStruct
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.jce
import kotlinx.io.core.BytePacketBuilder
import net.mamoe.mirai.qqandroid.io.JceOutput
import net.mamoe.mirai.qqandroid.io.buildJcePacket
import net.mamoe.mirai.qqandroid.io.writeJcePacket
inline fun BytePacketBuilder.writeUniRequestPacket(requestPacket: RequestPacket.() -> Unit) {
writeJcePacket {
RequestPacket().apply(requestPacket).writeTo(this)
}
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.JceInput
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
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
......@@ -248,7 +248,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
}
private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) {
val uni = RequestPacket.newInstanceFrom(JceInput(readIoBuffer(readInt() - 4)))
val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer())
PacketLogger.verbose(uni.toString())
consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
}
......
......@@ -4,6 +4,9 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.Jce
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
......@@ -19,7 +22,7 @@ class MessageSvc {
)
val messageNotification = Jce.UTF8.load(
RequestPushNotify.serializer(),
req.sBuffer[0]!!
req.sBuffer.loadAs(RequestDataVersion2.serializer()).map.entries.first().value.entries.first().value
)
println(messageNotification.contentToString())
TODO()
......
......@@ -219,7 +219,11 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
sealed class LoginPacketResponse : Packet {
object Success : LoginPacketResponse()
object Success : LoginPacketResponse(){
override fun toString(): String {
return "LoginPacketResponse.Success"
}
}
data class Error(
val title: String,
val message: String,
......
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.io.jceMap
import net.mamoe.mirai.qqandroid.io.jceStruct
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.writeUniRequestPacket
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.buildLoginOutgoingPacket
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.io.debugPrint
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.localIpAddress
......@@ -55,72 +56,73 @@ class StatSvc {
client, subAppId = subAppId, commandName = commandName,
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
) {
writeUniRequestPacket {
sServantName = "PushService"
sFuncName = "SvcReqRegister"
sBuffer = jceMap(
0,
"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()
writeFully(
RequestPacket(
sServantName = "PushService",
sFuncName = "SvcReqRegister",
sBuffer = RequestDataVersion3(
mapOf(
"SvcReqRegister" to RequestDataStructSvcReqRegister(
SvcReqRegister(
cConnType = 0,
lBid = 1 or 2 or 4,
lUin = client.uin,
iStatus = client.onlineStatus.id,
bKikPC = 0, // 是否把 PC 踢下线
bKikWeak = 0,
timeStamp = 0,
// timeStamp = currentTimeSeconds // millis or seconds??
iLargeSeq = 1551, // ?
bOpenPush = 1,
iLocaleID = 2052,
bRegType =
(if (regPushReason == RegPushReason.appRegister ||
regPushReason == RegPushReason.fillRegProxy ||
regPushReason == RegPushReason.createDefaultRegInfo ||
regPushReason == RegPushReason.setOnlineStatus
) 0 else 1).toByte(),
bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0,
iOSVersion = client.device.version.sdk.toLong(),
cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0,
vecGuid = client.device.guid,
strDevName = client.device.model.encodeToString(),
strDevType = client.device.model.encodeToString(),
strOSVer = client.device.version.release.encodeToString(),
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
)
)
bSetMute = 0
}
)
)
}
this.writePacket(this.build().debugPrint("sso body"))
).toByteArray(RequestDataStructSvcReqRegister.serializer())
)
).toByteArray(RequestDataVersion3.serializer())
).toByteArray(RequestPacket.serializer())
)
}
}
......
package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.core.readBytes
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.CharsetUTF8
import net.mamoe.mirai.qqandroid.io.JceOutput
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.buildJcePacket
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.cryptor.contentToString
import kotlin.test.Test
import kotlin.test.assertEquals
class JceEncoderTest {
class JceDecoderTest {
@Serializable
class TestSimpleJceStruct(
......@@ -23,51 +18,16 @@ class JceEncoderTest {
@SerialId(4) val long: Long = 123,
@SerialId(5) val float: Float = 123f,
@SerialId(6) val double: Double = 123.0
) : JceStruct() {
override fun writeTo(builder: JceOutput) = builder.run {
writeString("123", 0)
writeByte(123, 1)
writeShort(123, 2)
writeInt(123, 3)
writeLong(123, 4)
writeFloat(123f, 5)
writeDouble(123.0, 6)
}
}
) : JceStruct
@Test
fun testEncoder() {
assertEquals(
buildJcePacket {
writeString("123", 0)
writeByte(123, 1)
writeShort(123, 2)
writeInt(123, 3)
writeLong(123, 4)
writeFloat(123f, 5)
writeDouble(123.0, 6)
}.readBytes().toUHexString(),
Jce.GBK.dump(
TestSimpleJceStruct.serializer(),
TestSimpleJceStruct()
).toUHexString()
)
println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexJceStruct.serializer()).contentToString())
}
@Test
fun testEncoder2() {
assertEquals(
buildJcePacket(stringCharset = CharsetUTF8) {
writeFully(byteArrayOf(1, 2, 3), 7)
writeCollection(listOf(1, 2, 3), 8)
writeMap(mapOf("哈哈" to "嘿嘿"), 9)
writeJceStruct(TestSimpleJceStruct(), 10)
}.readBytes().toUHexString(),
Jce.UTF8.dump(
TestComplexJceStruct.serializer(),
TestComplexJceStruct()
).toUHexString()
)
}
@Serializable
......@@ -76,5 +36,5 @@ class JceEncoderTest {
@SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3),
@SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"),
@SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct()
)
) : JceStruct
}
\ No newline at end of file
......@@ -389,7 +389,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
close()
return
}
val code = configuration.loginSolver(bot, captchaCache!!)
val code = configuration.loginSolver.onSolvePicCaptcha(bot, captchaCache!!)
this.captchaCache = null
if (code == null || code.length != 4) {
......
package net.mamoe.mirai.utils
/**
* 直接抛出异常. 需自行处理验证码, 在 [BotConfiguration.captchaSolver] 中调整
*/
actual var DefaultCaptchaSolver: CaptchaSolver = {
error("No CaptchaSolver found. BotConfiguration.captchaSolver should be assigned manually")
}
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
/**
* 在各平台实现的默认的验证码处理器.
*/
actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
error("should be implemented manually by you")
}
override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? {
error("should be implemented manually by you")
}
override suspend fun onGetPhoneNumber(): String {
error("should be implemented manually by you")
}
override suspend fun onGetSMSVerifyCode(): String {
error("should be implemented manually by you")
}
}
\ No newline at end of file
......@@ -28,7 +28,7 @@ import kotlin.coroutines.CoroutineContext
actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
class DefaultLoginSolver(): LoginSolver(){
class DefaultLoginSolver : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
loginSolverLock.withLock {
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
......
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)
}
}
}
\ No newline at end of file
package jceTest
import io.ktor.util.InternalAPI
import jce.jce.JceInputStream
import jce.jce.JceOutputStream
import jce.jce.JceStruct
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.JceOutput
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
import net.mamoe.mirai.utils.io.toUHexString
import org.junit.Test
private infix fun ByteReadPacket.shouldEqualTo(another: ByteArray) {
this.readBytes().let {
check(it.contentEquals(another)) {
"""actual: ${it.toUHexString()}
|required: ${another.toUHexString()}
""".trimMargin()
}
}
}
@UseExperimental(InternalAPI::class)
private fun qqJce(block: JceOutputStream.() -> Unit): ByteArray {
return JceOutputStream().apply(block).toByteArray()
}
internal class JceOutputTest {
@Test
fun writeByte() {
buildJcePacket {
writeByte(1, 1)
writeByte(-128, 2)
} shouldEqualTo qqJce {
write(1.toByte(), 1)
write((-128).toByte(), 2)
}
}
@Test
fun writeDouble() {
buildJcePacket {
writeDouble(1.0, 1)
writeDouble(-128.0, 2)
} shouldEqualTo qqJce {
write(1.toDouble(), 1)
write((-128).toDouble(), 2)
}
}
@Test
fun writeFloat() {
buildJcePacket {
writeFloat(1.0f, 1)
writeFloat(-128.0f, 2)
} shouldEqualTo qqJce {
write(1.toFloat(), 1)
write((-128).toFloat(), 2)
}
}
@Test
fun writeFully() {
buildJcePacket {
writeFully(byteArrayOf(1, 2), 1)
writeFully(byteArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(byteArrayOf(1, 2), 1)
write(byteArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully() {
buildJcePacket {
writeFully(intArrayOf(1, 2), 1)
writeFully(intArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(intArrayOf(1, 2), 1)
write(intArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully1() {
buildJcePacket {
writeFully(shortArrayOf(1, 2), 1)
writeFully(shortArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(shortArrayOf(1, 2), 1)
write(shortArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully2() {
buildJcePacket {
writeFully(booleanArrayOf(true, false), 1)
writeFully(booleanArrayOf(true, false), 2)
} shouldEqualTo qqJce {
write(booleanArrayOf(true, false), 1)
write(booleanArrayOf(true, false), 2)
}
}
@Test
fun testWriteFully3() {
buildJcePacket {
writeFully(longArrayOf(1, 2), 1)
writeFully(longArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(longArrayOf(1, 2), 1)
write(longArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully4() {
buildJcePacket {
writeFully(floatArrayOf(1f, 2f), 1)
writeFully(floatArrayOf(1f, 2f), 2)
} shouldEqualTo qqJce {
write(floatArrayOf(1f, 2f), 1)
write(floatArrayOf(1f, 2f), 2)
}
}
@Test
fun testWriteFully5() {
buildJcePacket {
writeFully(doubleArrayOf(1.0, 2.0), 1)
writeFully(doubleArrayOf(1.0, 2.0), 2)
} shouldEqualTo qqJce {
write(doubleArrayOf(1.0, 2.0), 1)
write(doubleArrayOf(1.0, 2.0), 2)
}
}
@Test
fun testWriteFully6() {
buildJcePacket {
writeFully(arrayOf("123", "哈哈"), 1)
writeFully(arrayOf("123", "哈哈"), 2)
} shouldEqualTo qqJce {
write(arrayOf("123", "哈哈"), 1)
write(arrayOf("123", "哈哈"), 2)
}
}
@Test
fun writeInt() {
buildJcePacket {
writeInt(1, 1)
writeInt(-128, 2)
} shouldEqualTo qqJce {
write(1, 1)
write(-128, 2)
}
}
@Test
fun writeLong() {
buildJcePacket {
writeLong(1, 1)
writeLong(-128, 2)
} shouldEqualTo qqJce {
write(1L, 1)
write(-128L, 2)
}
}
@Test
fun writeShort() {
buildJcePacket {
writeShort(1, 1)
writeShort(-128, 2)
} shouldEqualTo qqJce {
write(1.toShort(), 1)
write((-128).toShort(), 2)
}
}
@Test
fun writeBoolean() {
buildJcePacket {
writeBoolean(true, 1)
writeBoolean(false, 2)
} shouldEqualTo qqJce {
write(true, 1)
write(false, 2)
}
}
@Test
fun writeString() {
buildJcePacket {
writeString("1", 1)
writeString("哈啊", 2)
} shouldEqualTo qqJce {
write("1", 1)
write("哈啊", 2)
}
}
@Test
fun writeMap() {
buildJcePacket {
writeMap(mapOf("" to ""), 1)
writeMap(mapOf("" to 123), 2)
writeMap(mapOf(123.0 to "Hello"), 3)
} shouldEqualTo qqJce {
write(mapOf("" to ""), 1)
write(mapOf("" to 123), 2)
write(mapOf(123.0 to "Hello"), 3)
}
}
@Test
fun writeCollection() {
buildJcePacket {
writeCollection(listOf("啊", "333", "1"), 1)
} shouldEqualTo qqJce {
write(listOf("啊", "333", "1"), 1)
}
}
data class TestMiraiStruct(
val message: String
) : net.mamoe.mirai.qqandroid.network.io.JceStruct() {
override fun writeTo(builder: JceOutput) {
builder.writeString(message, 0)
}
companion object : Factory<TestMiraiStruct> {
override fun newInstanceFrom(input: JceInput): TestMiraiStruct {
return TestMiraiStruct(input.readString(0))
}
}
}
class TestQQStruct(
private var message: String
) : JceStruct() {
constructor() : this("")
override fun readFrom(var1: JceInputStream) {
message = var1.read("", 0, true)
}
override fun writeTo(var1: JceOutputStream) {
var1.write(message, 0)
}
override fun toString(): String {
return "TestMiraiStruct(message=$message)"
}
}
@Test
fun writeJceStruct() {
buildJcePacket {
writeJceStruct(TestMiraiStruct("Hello"), 0)
writeJceStruct(TestMiraiStruct("嗨"), 1)
} shouldEqualTo qqJce {
write(TestQQStruct("Hello"), 0)
write(TestQQStruct("嗨"), 1)
}
}
@Test
fun writeObject() {
buildJcePacket {
writeObject(0.toByte(), 1)
writeObject(0.toShort(), 2)
writeObject(0, 3)
writeObject(0L, 4)
writeObject(0f, 5)
writeObject(0.0, 6)
writeObject("hello", 7)
writeObject(TestMiraiStruct("Hello"), 8)
} shouldEqualTo qqJce {
write(0.toByte(), 1)
write(0.toShort(), 2)
write(0, 3)
write(0L, 4)
write(0f, 5)
write(0.0, 6)
write("hello", 7)
write(TestQQStruct("Hello"), 8)
}
}
}
\ No newline at end of file
......@@ -10,11 +10,13 @@ import android.os.IBinder
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.utils.LoginFailedException
import net.mamoe.mirai.utils.LoginSolver
import java.lang.ref.WeakReference
class MiraiService : Service() {
......@@ -42,12 +44,27 @@ class MiraiService : Service() {
private fun login(qq: Long, pwd: String) {
GlobalScope.launch {
mBot = TIMPC.Bot(qq, pwd) {
captchaSolver = {
val bytes = it.readBytes()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
mCaptchaDeferred = CompletableDeferred()
mCallback?.get()?.onCaptcha(bitmap)
mCaptchaDeferred.await()
loginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
val bytes = data.readBytes()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
mCaptchaDeferred = CompletableDeferred()
mCallback?.get()?.onCaptcha(bitmap)
return mCaptchaDeferred.await()
}
override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? {
TODO("not implemented")
}
override suspend fun onGetPhoneNumber(): String {
TODO("not implemented")
}
override suspend fun onGetSMSVerifyCode(): String {
TODO("not implemented")
}
}
}.apply {
try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment