mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-23 14:20:24 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt
This commit is contained in:
commit
23e6ce4e23
@ -1,510 +0,0 @@
|
||||
package net.mamoe.mirai.qqandroid.io
|
||||
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.io.pool.ObjectPool
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce
|
||||
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
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
inline class JceHead(private val value: Long) {
|
||||
constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
|
||||
|
||||
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)"
|
||||
}Z
|
||||
}
|
||||
|
||||
fun <J : JceStruct> ByteArray.readJceStruct(
|
||||
deserializer: DeserializationStrategy<J>,
|
||||
tag: Int = 0,
|
||||
charset: Charset = CharsetUTF8
|
||||
): J {
|
||||
this.asJceInput(charset).use {
|
||||
return Jce.byCharSet(charset).load(deserializer, this.)
|
||||
}
|
||||
}
|
||||
|
||||
fun <J : JceStruct> ByteReadPacket.readJceStruct(factory: JceStruct.Factory<J>, 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 <J : JceStruct> ByteReadPacket.readJceRequestBufferMapVersion2ToJceStruct(factory: JceStruct.Factory<J>, 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 <J : JceStruct> ByteReadPacket.readJceRequestBufferMapVersion3ToJceStruct(factory: JceStruct.Factory<J>, 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<String, ByteArray> {
|
||||
this.use {
|
||||
discardExact(8)
|
||||
val request = this.asJceInput(charset).use {
|
||||
Jce
|
||||
RequestPacket.serializer()
|
||||
}
|
||||
val map = request.sBuffer.asJceInput(charset).withUse {
|
||||
readNestedMap<String, String, ByteArray>(0)
|
||||
}
|
||||
return map.mapValues { it.value.values.first() }
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteReadPacket.readJceRequestBufferMapVersion3(charset: Charset = CharsetUTF8): Map<String, ByteArray> {
|
||||
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 <R> IoBuffer.useIoBuffer(block: IoBuffer.() -> R): R {
|
||||
return try {
|
||||
block(this)
|
||||
} catch (first: Throwable) {
|
||||
throw first
|
||||
} finally {
|
||||
release(IoBuffer.Pool)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
|
||||
return try {
|
||||
block(this)
|
||||
} catch (first: Throwable) {
|
||||
throw first
|
||||
} finally {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
@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() {
|
||||
input.release(pool)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun readHead(): JceHead = input.readHead()
|
||||
|
||||
@PublishedApi
|
||||
internal fun peakHead(): JceHead = input.makeView().readHead()
|
||||
|
||||
private fun IoBuffer.readHead(): JceHead {
|
||||
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
|
||||
fun read(default: Short, tag: Int): Short = readShortOrNull(tag) ?: default
|
||||
fun read(default: Int, tag: Int): Int = readIntOrNull(tag) ?: default
|
||||
fun read(default: Long, tag: Int): Long = readLongOrNull(tag) ?: default
|
||||
fun read(default: Float, tag: Int): Float = readFloatOrNull(tag) ?: default
|
||||
fun read(default: Double, tag: Int): Double = readDoubleOrNull(tag) ?: default
|
||||
fun read(default: Boolean, tag: Int): Boolean = readBooleanOrNull(tag) ?: default
|
||||
|
||||
fun read(default: ByteArray, tag: Int): ByteArray = readByteArrayOrNull(tag) ?: default
|
||||
fun read(default: ShortArray, tag: Int): ShortArray = readShortArrayOrNull(tag) ?: default
|
||||
fun read(default: IntArray, tag: Int): IntArray = readIntArrayOrNull(tag) ?: default
|
||||
fun read(default: LongArray, tag: Int): LongArray = readLongArrayOrNull(tag) ?: default
|
||||
fun read(default: FloatArray, tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: default
|
||||
fun read(default: DoubleArray, tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: default
|
||||
fun read(default: BooleanArray, tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: default
|
||||
|
||||
fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag")
|
||||
|
||||
fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag")
|
||||
|
||||
fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readShortArray(tag: Int): ShortArray = readShortArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readLongArray(tag: Int): LongArray = readLongArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readFloatArray(tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readDoubleArray(tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
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 <K, V> readMap(defaultKey: K, defaultValue: V, tag: Int): Map<K, V> = readMapOrNull(defaultKey, defaultValue, tag) ?: error("cannot find tag $tag")
|
||||
inline fun <reified K, reified V> readMap(tag: Int): Map<K, V> = readMapOrNull(tag) ?: error("cannot find tag $tag")
|
||||
inline fun <reified K, reified InnerK, reified InnerV> readNestedMap(tag: Int): Map<K, Map<InnerK, InnerV>> =
|
||||
readNestedMapOrNull(tag) ?: error("cannot find tag $tag")
|
||||
|
||||
inline fun <reified J : JceStruct> readStringToJceStructMap(factory: JceStruct.Factory<J>, tag: Int): Map<String, J> =
|
||||
readStringToJceStructMapOrNull(factory, tag) ?: error("cannot find tag $tag")
|
||||
|
||||
fun <T> readList(defaultElement: T, tag: Int): List<T> = readListOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
|
||||
inline fun <reified J: JceStruct> readJceStructList(factory: JceStruct.Factory<J>, tag: Int): List<J> = readJceStructListOrNull( factory, tag) ?: error("cannot find tag $tag")
|
||||
inline fun <reified T> readList(tag: Int): List<T> = readListOrNull( 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")
|
||||
|
||||
fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toLong()
|
||||
1 -> input.readShort().toLong()
|
||||
2 -> input.readInt().toLong()
|
||||
3 -> input.readLong()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toShort()
|
||||
1 -> input.readShort()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toInt()
|
||||
1 -> input.readShort().toInt()
|
||||
2 -> input.readInt()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte()
|
||||
else -> error("type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0f
|
||||
4 -> input.readFloat()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0.0
|
||||
4 -> input.readFloat().toDouble()
|
||||
5 -> input.readDouble()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
|
||||
|
||||
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
|
||||
when (it.type.toInt()) {
|
||||
9 -> ByteArray(readInt(0)) { readByte(0) }
|
||||
13 -> {
|
||||
val head = readHead()
|
||||
check(head.type.toInt() == 0) { "type mismatch" }
|
||||
input.readBytes(readInt(0))
|
||||
}
|
||||
else -> error("type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
ShortArray(readInt(0)) { readShort(0) }
|
||||
}
|
||||
|
||||
fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
DoubleArray(readInt(0)) { readDouble(0) }
|
||||
}
|
||||
|
||||
fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
FloatArray(readInt(0)) { readFloat(0) }
|
||||
}
|
||||
|
||||
fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
IntArray(readInt(0)) { readInt(0) }
|
||||
}
|
||||
|
||||
fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
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(readInt(0)) { readSimpleObject<T>(0) }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> readArrayOrNull(defaultElement: T, tag: Int): Array<T>? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
Array(readInt(0)) { readObject(defaultElement, 0) }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified J : JceStruct> readJceStructArrayOrNull(factory: JceStruct.Factory<J>, tag: Int): Array<J>? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
Array(readInt(0)) { readJceStruct(factory, 0) }
|
||||
}
|
||||
|
||||
fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
BooleanArray(readInt(0)) { readBoolean(0) }
|
||||
}
|
||||
|
||||
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
|
||||
return when (head.type.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}")
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified J : JceStruct> readStringToJceStructMapOrNull(factory: JceStruct.Factory<J>, tag: Int): Map<String, J>? = skipToTagOrNull(tag) {
|
||||
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
|
||||
val size = readInt(0)
|
||||
val map = HashMap<String, J>(size)
|
||||
repeat(size) {
|
||||
map[readString(0)] = readJceStruct(factory, 1)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
|
||||
fun <K, V> readMapOrNull(defaultKey: K, defaultValue: V, tag: Int): Map<K, V>? = skipToTagOrNull(tag) {
|
||||
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
|
||||
val size = readInt(0)
|
||||
val map = HashMap<K, V>(size)
|
||||
repeat(size) {
|
||||
map[readObject(defaultKey, 0)] = readObject(defaultValue, 1)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
inline fun <reified K, reified InnerK, reified InnerV> readNestedMapOrNull(tag: Int): Map<K, Map<InnerK, InnerV>>? = skipToTagOrNull(tag) {
|
||||
check(it.type.toInt() == 8) { "type mismatch" }
|
||||
val size = readInt(0)
|
||||
val map = HashMap<K, Map<InnerK, InnerV>>(size)
|
||||
repeat(size) {
|
||||
map[readSimpleObject(0)] = readMap(1)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
inline fun <reified K, reified V> readMapOrNull(tag: Int): Map<K, V>? = skipToTagOrNull(tag) {
|
||||
check(it.type.toInt() == 8) { "type mismatch" }
|
||||
val size = readInt(0)
|
||||
val map = HashMap<K, V>(size)
|
||||
repeat(size) {
|
||||
map[readSimpleObject(0)] = readSimpleObject(1)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
inline fun <reified J : JceStruct> readJceStructListOrNull(factory: JceStruct.Factory<J>, tag: Int): List<J>? = skipToTagOrNull(tag) { head ->
|
||||
check(head.type.toInt() == 9) { "type mismatch" }
|
||||
val size = readInt(0)
|
||||
val list = ArrayList<J>(size)
|
||||
repeat(size) {
|
||||
list.add(readJceStruct(factory, 0))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
inline fun <reified T> readListOrNull(tag: Int): List<T>? = skipToTagOrNull(tag) { head ->
|
||||
check(head.type.toInt() == 9) { "type mismatch" }
|
||||
val size = readInt(0)
|
||||
val list = ArrayList<T>(size)
|
||||
repeat(size) {
|
||||
list.add(readSimpleObject(0))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun <T> readListOrNull(defaultElement: T, tag: Int): List<T>? = skipToTagOrNull(tag) { head ->
|
||||
check(head.type.toInt() == 9) { "type mismatch" }
|
||||
val size = readInt(0)
|
||||
val list = ArrayList<T>(size)
|
||||
repeat(size) {
|
||||
list.add(readObject(defaultElement, 0))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun <J : JceStruct> readJceStructOrNull(factory: JceStruct.Factory<J>, tag: Int): J? = skipToTagOrNull(tag) { head ->
|
||||
check(head.type.toInt() == 10) { "type mismatch" }
|
||||
return factory.newInstanceFrom(this).also { skipToStructEnd() }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> readArrayOrNull(default: Array<T>, tag: Int): Array<T>? = skipToTagOrNull(tag) { head ->
|
||||
val defaultElement = default[0]
|
||||
check(head.type.toInt() == 9) { "type mismatch" }
|
||||
return Array(readInt(0)) { readObject(defaultElement, tag) as Any } as Array<T>
|
||||
}
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
||||
fun <T> readObject(default: T, tag: Int): T = when (default) {
|
||||
is Byte -> readByte(tag)
|
||||
is Boolean -> readBoolean(tag)
|
||||
is Short -> readShort(tag)
|
||||
is Int -> readInt(tag)
|
||||
is Long -> readLong(tag)
|
||||
is Float -> readFloat(tag)
|
||||
is Double -> readDouble(tag)
|
||||
is String -> readString(tag)
|
||||
is BooleanArray -> readBooleanArray(tag)
|
||||
is ShortArray -> readShortArray(tag)
|
||||
is IntArray -> readIntArray(tag)
|
||||
is LongArray -> readLongArray(tag)
|
||||
is ByteArray -> readByteArray(tag)
|
||||
is FloatArray -> readByteArray(tag)
|
||||
is DoubleArray -> readDoubleArrayOrNull(tag)
|
||||
is List<*> -> {
|
||||
readList(default, tag)
|
||||
}
|
||||
is Map<*, *> -> {
|
||||
val entry = default.entries.first()
|
||||
readMap(entry.key, entry.value, tag)
|
||||
}
|
||||
is Array<*> -> readSimpleArray(default, tag)
|
||||
else -> error("unsupported type")
|
||||
} as T
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
||||
inline fun <reified T> readSimpleObject(tag: Int): T = when (T::class) {
|
||||
Byte::class -> readByte(tag)
|
||||
Boolean::class -> readBoolean(tag)
|
||||
Short::class -> readShort(tag)
|
||||
Int::class -> readInt(tag)
|
||||
Long::class -> readLong(tag)
|
||||
Float::class -> readFloat(tag)
|
||||
Double::class -> readDouble(tag)
|
||||
|
||||
String::class -> readString(tag)
|
||||
|
||||
BooleanArray::class -> readBooleanArray(tag)
|
||||
ShortArray::class -> readShortArray(tag)
|
||||
IntArray::class -> readIntArray(tag)
|
||||
LongArray::class -> readLongArray(tag)
|
||||
ByteArray::class -> readByteArray(tag)
|
||||
FloatArray::class -> readByteArray(tag)
|
||||
DoubleArray::class -> readDoubleArrayOrNull(tag)
|
||||
else -> error("Type is not supported: ${T::class.simpleName}")
|
||||
} as T
|
||||
|
||||
private fun skipField() {
|
||||
skipField(readHead().type)
|
||||
}
|
||||
|
||||
private fun skipToStructEnd() {
|
||||
var head: JceHead
|
||||
do {
|
||||
head = readHead()
|
||||
skipField(head.type)
|
||||
} while (head.type.toInt() != 11)
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
@PublishedApi
|
||||
internal fun skipField(type: Byte) = when (type.toInt()) {
|
||||
0 -> this.input.discardExact(1)
|
||||
1 -> this.input.discardExact(2)
|
||||
2 -> this.input.discardExact(4)
|
||||
3 -> this.input.discardExact(8)
|
||||
4 -> this.input.discardExact(4)
|
||||
5 -> this.input.discardExact(8)
|
||||
6 -> this.input.discardExact(this.input.readUByte().toInt())
|
||||
7 -> this.input.discardExact(this.input.readInt())
|
||||
8 -> { // map
|
||||
repeat(this.readInt(0) * 2) {
|
||||
skipField()
|
||||
}
|
||||
}
|
||||
9 -> { // list
|
||||
repeat(this.readInt(0)) {
|
||||
skipField()
|
||||
}
|
||||
}
|
||||
10 -> this.skipToStructEnd()
|
||||
11, 12 -> {
|
||||
|
||||
}
|
||||
13 -> {
|
||||
val head = readHead()
|
||||
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
|
||||
this.input.discardExact(this.readInt(0))
|
||||
}
|
||||
else -> error("invalid type: $type")
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <R> JceInput.skipToTag(tag: Int, block: (JceHead) -> R): R {
|
||||
return skipToTagOrNull(tag) { block(it) } ?: error("cannot find required tag $tag")
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal inline fun <R> JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
|
||||
while (true) {
|
||||
if (this.input.endOfInput) {
|
||||
println("endOfInput")
|
||||
return null
|
||||
}
|
||||
|
||||
val head = peakHead()
|
||||
if (head.tag > tag) {
|
||||
return null
|
||||
}
|
||||
readHead()
|
||||
if (head.tag == tag) {
|
||||
return block(head)
|
||||
}
|
||||
this.skipField(head.type)
|
||||
}
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
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)
|
@ -1,5 +1,3 @@
|
||||
package net.mamoe.mirai.qqandroid.io
|
||||
|
||||
interface JceStruct {
|
||||
|
||||
}
|
||||
interface JceStruct
|
@ -0,0 +1,653 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.io.ByteArrayOutputStream
|
||||
import kotlinx.io.ByteBuffer
|
||||
import kotlinx.io.ByteOrder
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.internal.*
|
||||
import kotlinx.serialization.modules.EmptyModule
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
|
||||
import net.mamoe.mirai.utils.io.readString
|
||||
import net.mamoe.mirai.utils.io.toIoBuffer
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@PublishedApi
|
||||
internal val CharsetGBK = Charset.forName("GBK")
|
||||
@PublishedApi
|
||||
internal val CharsetUTF8 = Charset.forName("UTF8")
|
||||
|
||||
fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
|
||||
return Jce.byCharSet(c).load(deserializer, this)
|
||||
}
|
||||
|
||||
fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.UTF8): ByteArray = Jce.byCharSet(c).dump(serializer, this)
|
||||
|
||||
enum class JceCharset(val kotlinCharset: Charset) {
|
||||
GBK(Charset.forName("GBK")),
|
||||
UTF8(Charset.forName("UTF8"))
|
||||
}
|
||||
|
||||
|
||||
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
|
||||
|
||||
class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
|
||||
|
||||
private inner class ListWriter(
|
||||
private val count: Int,
|
||||
private val tag: Int,
|
||||
private val parentEncoder: JceEncoder
|
||||
) : JceEncoder(ByteArrayOutputStream()) {
|
||||
override fun SerialDescriptor.getTag(index: Int): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
parentEncoder.writeHead(LIST, this.tag)
|
||||
parentEncoder.encodeTaggedInt(0, count)
|
||||
parentEncoder.output.write(this.output.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class JceMapWriter(
|
||||
output: ByteArrayOutputStream
|
||||
) : JceEncoder(output) {
|
||||
override fun SerialDescriptor.getTag(index: Int): Int {
|
||||
return if (index % 2 == 0) 0 else 1
|
||||
}
|
||||
|
||||
/*
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
parentEncoder.writeHead(MAP, this.tag)
|
||||
parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
|
||||
println(this.output.toByteArray().toUHexString())
|
||||
parentEncoder.output.write(this.output.toByteArray())
|
||||
}*/
|
||||
|
||||
override fun beginCollection(desc: SerialDescriptor, collectionSize: Int, vararg typeParams: KSerializer<*>): CompositeEncoder {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From: com.qq.taf.jce.JceOutputStream
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
@UseExperimental(ExperimentalIoApi::class)
|
||||
private open inner class JceEncoder(
|
||||
internal val output: ByteArrayOutputStream
|
||||
) : TaggedEncoder<Int>() {
|
||||
override val context get() = this@Jce.context
|
||||
|
||||
override fun SerialDescriptor.getTag(index: Int): Int {
|
||||
return getSerialId(this, index) ?: error("cannot find @SerialId")
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化最开始的时候的
|
||||
*/
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) {
|
||||
StructureKind.LIST -> this
|
||||
StructureKind.MAP -> this
|
||||
StructureKind.CLASS, UnionKind.OBJECT -> this
|
||||
is PolymorphicKind -> this
|
||||
else -> throw SerializationException("Primitives are not supported at top-level")
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
|
||||
is MapLikeDescriptor -> {
|
||||
println("hello")
|
||||
val entries = (value as Map<*, *>).entries
|
||||
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
|
||||
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
|
||||
|
||||
this.writeHead(MAP, currentTag)
|
||||
this.encodeTaggedInt(0, entries.count())
|
||||
HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
|
||||
}
|
||||
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
|
||||
is PrimitiveArrayDescriptor -> {
|
||||
if (value is ByteArray) {
|
||||
this.encodeTaggedByteArray(popTag(), value)
|
||||
} else {
|
||||
serializer.serialize(
|
||||
ListWriter(
|
||||
when (value) {
|
||||
is ShortArray -> value.size
|
||||
is IntArray -> value.size
|
||||
is LongArray -> value.size
|
||||
is FloatArray -> value.size
|
||||
is DoubleArray -> value.size
|
||||
is CharArray -> value.size
|
||||
else -> error("unknown array type: ${value.getClassName()}")
|
||||
}, popTag(), this
|
||||
),
|
||||
value
|
||||
)
|
||||
}
|
||||
}
|
||||
is ArrayClassDesc -> {
|
||||
serializer.serialize(
|
||||
ListWriter((value as Array<*>).size, popTag(), this),
|
||||
value
|
||||
)
|
||||
}
|
||||
is ListLikeDescriptor -> {
|
||||
serializer.serialize(
|
||||
ListWriter((value as Collection<*>).size, popTag(), this),
|
||||
value
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
if (value is JceStruct) {
|
||||
if (currentTagOrNull == null) {
|
||||
serializer.serialize(this, value)
|
||||
} else {
|
||||
this.writeHead(STRUCT_BEGIN, currentTag)
|
||||
serializer.serialize(this, value)
|
||||
this.writeHead(STRUCT_END, 0)
|
||||
}
|
||||
} else serializer.serialize(this, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedByte(tag: Int, value: Byte) {
|
||||
if (value.toInt() == 0) {
|
||||
writeHead(ZERO_TYPE, tag)
|
||||
} else {
|
||||
writeHead(BYTE, tag)
|
||||
output.write(value.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedShort(tag: Int, value: Short) {
|
||||
if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) {
|
||||
encodeTaggedByte(tag, value.toByte())
|
||||
} else {
|
||||
writeHead(SHORT, tag)
|
||||
output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedInt(tag: Int, value: Int) {
|
||||
if (value in Short.MIN_VALUE..Short.MAX_VALUE) {
|
||||
encodeTaggedShort(tag, value.toShort())
|
||||
} else {
|
||||
writeHead(INT, tag)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedFloat(tag: Int, value: Float) {
|
||||
writeHead(FLOAT, tag)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
|
||||
}
|
||||
|
||||
override fun encodeTaggedDouble(tag: Int, value: Double) {
|
||||
writeHead(DOUBLE, tag)
|
||||
output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
|
||||
}
|
||||
|
||||
override fun encodeTaggedLong(tag: Int, value: Long) {
|
||||
if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
|
||||
encodeTaggedInt(tag, value.toInt())
|
||||
} else {
|
||||
writeHead(LONG, tag)
|
||||
output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedBoolean(tag: Int, value: Boolean) {
|
||||
encodeTaggedByte(tag, if (value) 1 else 0)
|
||||
}
|
||||
|
||||
override fun encodeTaggedChar(tag: Int, value: Char) {
|
||||
encodeTaggedByte(tag, value.toByte())
|
||||
}
|
||||
|
||||
override fun encodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor, ordinal: Int) {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun encodeTaggedNull(tag: Int) {
|
||||
}
|
||||
|
||||
override fun encodeTaggedUnit(tag: Int) {
|
||||
encodeTaggedNull(tag)
|
||||
}
|
||||
|
||||
fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) {
|
||||
writeHead(SIMPLE_LIST, tag)
|
||||
writeHead(BYTE, 0)
|
||||
encodeTaggedInt(0, bytes.size)
|
||||
output.write(bytes)
|
||||
}
|
||||
|
||||
override fun encodeTaggedString(tag: Int, value: String) {
|
||||
val array = value.toByteArray(charset.kotlinCharset)
|
||||
if (array.size > 255) {
|
||||
writeHead(STRING4, tag)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
|
||||
output.write(array)
|
||||
} else {
|
||||
writeHead(STRING1, tag)
|
||||
output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
|
||||
output.write(array)
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedValue(tag: Int, value: Any) {
|
||||
when (value) {
|
||||
is Byte -> encodeTaggedByte(tag, value)
|
||||
is Short -> encodeTaggedShort(tag, value)
|
||||
is Int -> encodeTaggedInt(tag, value)
|
||||
is Long -> encodeTaggedLong(tag, value)
|
||||
is Float -> encodeTaggedFloat(tag, value)
|
||||
is Double -> encodeTaggedDouble(tag, value)
|
||||
is Boolean -> encodeTaggedBoolean(tag, value)
|
||||
is String -> encodeTaggedString(tag, value)
|
||||
is Unit -> encodeTaggedUnit(tag)
|
||||
else -> error("unsupported type: ${value.getClassName()}")
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun writeHead(type: Int, tag: Int) {
|
||||
if (tag < 15) {
|
||||
this.output.write((tag shl 4) or type)
|
||||
return
|
||||
}
|
||||
if (tag < 256) {
|
||||
this.output.write(type or 0xF0)
|
||||
this.output.write(tag)
|
||||
return
|
||||
}
|
||||
error("tag is too large: $tag")
|
||||
}
|
||||
}
|
||||
|
||||
private open inner class JceDecoder(
|
||||
internal val input: JceInput
|
||||
) : TaggedDecoder<Int>() {
|
||||
override fun SerialDescriptor.getTag(index: Int): Int {
|
||||
return getSerialId(this, index) ?: error("cannot find tag")
|
||||
}
|
||||
|
||||
override fun decodeTaggedByte(tag: Int): Byte = input.readByte(tag)
|
||||
override fun decodeTaggedShort(tag: Int): Short = input.readShort(tag)
|
||||
override fun decodeTaggedInt(tag: Int): Int = input.readInt(tag)
|
||||
override fun decodeTaggedLong(tag: Int): Long = input.readLong(tag)
|
||||
override fun decodeTaggedFloat(tag: Int): Float = input.readFloat(tag)
|
||||
override fun decodeTaggedDouble(tag: Int): Double = input.readDouble(tag)
|
||||
override fun decodeTaggedChar(tag: Int): Char = input.readByte(tag).toChar()
|
||||
override fun decodeTaggedString(tag: Int): String = input.readString(tag)
|
||||
override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when (deserializer.descriptor) {
|
||||
is MapLikeDescriptor -> {
|
||||
deserializer as MapLikeSerializer<Any?, Any?, T, *>
|
||||
|
||||
val tag = popTag()
|
||||
this.input.skipToTagOrNull(tag) {
|
||||
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
|
||||
val size = this.input.readInt(0)
|
||||
val map = HashMap<Any?, Any?>(size)
|
||||
repeat(size) {
|
||||
pushTag(0)
|
||||
val key = deserializer.keySerializer.deserialize(this)
|
||||
pushTag(1)
|
||||
val value = deserializer.valueSerializer.deserialize(this)
|
||||
map[key] = value
|
||||
}
|
||||
return map as T
|
||||
} ?: error("cannot find tag $tag")
|
||||
}
|
||||
ByteArraySerializer.descriptor -> input.readByteArray(popTag()) as T
|
||||
ShortArraySerializer.descriptor -> input.readShortArray(popTag()) as T
|
||||
IntArraySerializer.descriptor -> input.readIntArray(popTag()) as T
|
||||
LongArraySerializer.descriptor -> input.readLongArray(popTag()) as T
|
||||
FloatArraySerializer.descriptor -> input.readFloatArray(popTag()) as T
|
||||
DoubleArraySerializer.descriptor -> input.readDoubleArray(popTag()) as T
|
||||
CharArraySerializer.descriptor -> input.readByteArray(popTag()).map { it.toChar() }.toCharArray() as T
|
||||
BooleanArraySerializer.descriptor -> input.readBooleanArray(popTag()) as T
|
||||
|
||||
is ArrayClassDesc -> {
|
||||
deserializer as ArrayListSerializer<Any?>
|
||||
|
||||
val tag = popTag()
|
||||
input.skipToTagOrNull(tag) { head ->
|
||||
return Array(input.readInt(0)) {
|
||||
input.readHead()
|
||||
deserializer.deserialize(this)
|
||||
} as T
|
||||
} ?: error("cannot find tag $tag")
|
||||
}
|
||||
is ListLikeDescriptor -> {
|
||||
deserializer as ListLikeSerializer<Any?, T, *>
|
||||
|
||||
val tag = currentTag
|
||||
input.skipToTagOrNull(tag) { head ->
|
||||
val size = input.readInt(0)
|
||||
val list = ArrayList<Any?>(size)
|
||||
|
||||
repeat(size) {
|
||||
//input.readHead()
|
||||
this.pushTag( 0)
|
||||
list.add(deserializer.typeParams[0].also { println(it.getClassName()) }.deserialize(this))
|
||||
}
|
||||
|
||||
return list as T
|
||||
} ?: error("cannot find tag $tag")
|
||||
}
|
||||
else -> {
|
||||
if (input.peakHead().type.toInt() == STRUCT_BEGIN) {
|
||||
input.readHead()
|
||||
deserializer.deserialize(this).also { input.readHead() }
|
||||
} else deserializer.deserialize(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int =
|
||||
TODO()
|
||||
}
|
||||
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private inner class JceInput(
|
||||
@PublishedApi
|
||||
internal val input: IoBuffer
|
||||
) : Closeable {
|
||||
override fun close() {
|
||||
input.close()
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun readHead(): JceHead = input.readHead()
|
||||
|
||||
@PublishedApi
|
||||
internal fun peakHead(): JceHead = input.makeView().readHead()
|
||||
|
||||
private fun IoBuffer.readHead(): JceHead {
|
||||
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 readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag")
|
||||
|
||||
fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag")
|
||||
|
||||
fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readShortArray(tag: Int): ShortArray = readShortArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readLongArray(tag: Int): LongArray = readLongArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readFloatArray(tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
fun readDoubleArray(tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: error("cannot find tag $tag")
|
||||
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 readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
ShortArray(readInt(0)) { readShort(0) }
|
||||
}
|
||||
|
||||
fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
DoubleArray(readInt(0)) { readDouble(0) }
|
||||
}
|
||||
|
||||
fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
FloatArray(readInt(0)) { readFloat(0) }
|
||||
}
|
||||
|
||||
fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
IntArray(readInt(0)) { readInt(0) }
|
||||
}
|
||||
|
||||
fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
LongArray(readInt(0)) { readLong(0) }
|
||||
}
|
||||
|
||||
fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
BooleanArray(readInt(0)) { readBoolean(0) }
|
||||
}
|
||||
|
||||
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
|
||||
when (it.type.toInt()) {
|
||||
9 -> ByteArray(readInt(0)) { readByte(0) }
|
||||
13 -> {
|
||||
val head = readHead()
|
||||
check(head.type.toInt() == 0) { "type mismatch" }
|
||||
input.readBytes(readInt(0))
|
||||
}
|
||||
else -> error("type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
||||
fun <T> readObject(default: T, tag: Int): T = when (default) {
|
||||
is Byte -> readByte(tag)
|
||||
is Boolean -> readBoolean(tag)
|
||||
is Short -> readShort(tag)
|
||||
is Int -> readInt(tag)
|
||||
is Long -> readLong(tag)
|
||||
is Float -> readFloat(tag)
|
||||
is Double -> readDouble(tag)
|
||||
is String -> readString(tag)
|
||||
else -> error("unsupported type")
|
||||
} as T
|
||||
|
||||
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
|
||||
return when (head.type.toInt()) {
|
||||
6 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
|
||||
7 -> input.readString(
|
||||
input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } },
|
||||
charset = charset.kotlinCharset
|
||||
)
|
||||
else -> error("type mismatch: ${head.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toLong()
|
||||
1 -> input.readShort().toLong()
|
||||
2 -> input.readInt().toLong()
|
||||
3 -> input.readLong()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toShort()
|
||||
1 -> input.readShort()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte().toInt()
|
||||
1 -> input.readShort().toInt()
|
||||
2 -> input.readInt()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0
|
||||
0 -> input.readByte()
|
||||
else -> error("type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0f
|
||||
4 -> input.readFloat()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
|
||||
return when (it.type.toInt()) {
|
||||
12 -> 0.0
|
||||
4 -> input.readFloat().toDouble()
|
||||
5 -> input.readDouble()
|
||||
else -> error("type mismatch: ${it.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
|
||||
|
||||
|
||||
private fun skipField() {
|
||||
skipField(readHead().type)
|
||||
}
|
||||
|
||||
private fun skipToStructEnd() {
|
||||
var head: JceHead
|
||||
do {
|
||||
head = readHead()
|
||||
skipField(head.type)
|
||||
} while (head.type.toInt() != 11)
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
@PublishedApi
|
||||
internal fun skipField(type: Byte) = when (type.toInt()) {
|
||||
0 -> this.input.discardExact(1)
|
||||
1 -> this.input.discardExact(2)
|
||||
2 -> this.input.discardExact(4)
|
||||
3 -> this.input.discardExact(8)
|
||||
4 -> this.input.discardExact(4)
|
||||
5 -> this.input.discardExact(8)
|
||||
6 -> this.input.discardExact(this.input.readUByte().toInt())
|
||||
7 -> this.input.discardExact(this.input.readInt())
|
||||
8 -> { // map
|
||||
repeat(this.readInt(0) * 2) {
|
||||
skipField()
|
||||
}
|
||||
}
|
||||
9 -> { // list
|
||||
repeat(this.readInt(0)) {
|
||||
skipField()
|
||||
}
|
||||
}
|
||||
10 -> this.skipToStructEnd()
|
||||
11, 12 -> {
|
||||
|
||||
}
|
||||
13 -> {
|
||||
val head = readHead()
|
||||
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
|
||||
this.input.discardExact(this.readInt(0))
|
||||
}
|
||||
else -> error("invalid type: $type")
|
||||
}
|
||||
|
||||
internal inline fun <R> skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
|
||||
while (true) {
|
||||
if (this.input.endOfInput) {
|
||||
println("endOfInput")
|
||||
return null
|
||||
}
|
||||
|
||||
val head = peakHead()
|
||||
if (head.tag > tag) {
|
||||
return null
|
||||
}
|
||||
readHead()
|
||||
if (head.tag == tag) {
|
||||
return block(head)
|
||||
}
|
||||
this.skipField(head.type)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UTF8 = Jce(JceCharset.UTF8)
|
||||
val GBK = Jce(JceCharset.GBK)
|
||||
|
||||
fun byCharSet(c: JceCharset): Jce {
|
||||
return if (c === JceCharset.UTF8) {
|
||||
UTF8
|
||||
} else {
|
||||
GBK
|
||||
}
|
||||
}
|
||||
|
||||
internal const val BYTE: Int = 0
|
||||
internal const val DOUBLE: Int = 5
|
||||
internal const val FLOAT: Int = 4
|
||||
internal const val INT: Int = 2
|
||||
internal const val JCE_MAX_STRING_LENGTH = 104857600
|
||||
internal const val LIST: Int = 9
|
||||
internal const val LONG: Int = 3
|
||||
internal const val MAP: Int = 8
|
||||
internal const val SHORT: Int = 1
|
||||
internal const val SIMPLE_LIST: Int = 13
|
||||
internal const val STRING1: Int = 6
|
||||
internal const val STRING4: Int = 7
|
||||
internal const val STRUCT_BEGIN: Int = 10
|
||||
internal const val STRUCT_END: Int = 11
|
||||
internal const val ZERO_TYPE: Int = 12
|
||||
|
||||
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
|
||||
}
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
|
||||
val encoder = ByteArrayOutputStream()
|
||||
val dumper = JceEncoder(encoder)
|
||||
dumper.encode(serializer, obj)
|
||||
return encoder.toByteArray()
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
|
||||
return bytes.toIoBuffer().withUse {
|
||||
val decoder = JceDecoder(JceInput(this))
|
||||
decoder.decode(deserializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
inline class JceHead(private val value: Long) {
|
||||
constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
|
||||
|
||||
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)"
|
||||
}
|
||||
}
|
@ -1,376 +0,0 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.io.ByteArrayOutputStream
|
||||
import kotlinx.io.ByteBuffer
|
||||
import kotlinx.io.ByteOrder
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.ExperimentalIoApi
|
||||
import kotlinx.io.core.toByteArray
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.internal.*
|
||||
import kotlinx.serialization.modules.EmptyModule
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import net.mamoe.mirai.qqandroid.io.CharsetUTF8
|
||||
import net.mamoe.mirai.qqandroid.io.JceEncodeException
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: Charset): T {
|
||||
return Jce.byCharSet(c).load(deserializer, this)
|
||||
}
|
||||
|
||||
|
||||
enum class JceCharset(val kotlinCharset: Charset) {
|
||||
GBK(Charset.forName("GBK")),
|
||||
UTF8(Charset.forName("UTF8"))
|
||||
}
|
||||
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SerialCharset(val charset: JceCharset)
|
||||
|
||||
internal object JceType {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
|
||||
|
||||
internal data class JceDesc(
|
||||
val id: Int,
|
||||
val charset: JceCharset
|
||||
) {
|
||||
companion object {
|
||||
val STUB_FOR_PRIMITIVE_NUMBERS_GBK = JceDesc(0, JceCharset.GBK)
|
||||
}
|
||||
}
|
||||
|
||||
class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
|
||||
|
||||
private inner class ListWriter(
|
||||
defaultStringCharset: JceCharset,
|
||||
private val count: Int,
|
||||
private val tag: JceDesc,
|
||||
private val parentEncoder: JceEncoder
|
||||
) : JceEncoder(defaultStringCharset, ByteArrayOutputStream()) {
|
||||
override fun SerialDescriptor.getTag(index: Int): JceDesc {
|
||||
return JceDesc(0, getCharset(index))
|
||||
}
|
||||
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
parentEncoder.writeHead(LIST, this.tag.id)
|
||||
parentEncoder.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
|
||||
parentEncoder.output.write(this.output.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class JceStructWriter(
|
||||
defaultStringCharset: JceCharset,
|
||||
private val tag: JceDesc,
|
||||
private val parentEncoder: JceEncoder,
|
||||
private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
|
||||
) : JceEncoder(defaultStringCharset, stream) {
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
parentEncoder.writeHead(STRUCT_BEGIN, this.tag.id)
|
||||
parentEncoder.output.write(stream.toByteArray())
|
||||
parentEncoder.writeHead(STRUCT_END, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class JceMapWriter(
|
||||
defaultStringCharset: JceCharset,
|
||||
output: ByteArrayOutputStream
|
||||
) : JceEncoder(defaultStringCharset, output) {
|
||||
override fun SerialDescriptor.getTag(index: Int): JceDesc {
|
||||
return if (index % 2 == 0) JceDesc(0, getCharset(index))
|
||||
else JceDesc(1, getCharset(index))
|
||||
}
|
||||
|
||||
/*
|
||||
override fun endEncode(desc: SerialDescriptor) {
|
||||
parentEncoder.writeHead(MAP, this.tag.id)
|
||||
parentEncoder.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
|
||||
println(this.output.toByteArray().toUHexString())
|
||||
parentEncoder.output.write(this.output.toByteArray())
|
||||
}*/
|
||||
|
||||
override fun beginCollection(desc: SerialDescriptor, collectionSize: Int, vararg typeParams: KSerializer<*>): CompositeEncoder {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From: com.qq.taf.jce.JceOutputStream
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
@UseExperimental(ExperimentalIoApi::class)
|
||||
private open inner class JceEncoder(
|
||||
/**
|
||||
* 标注在 class 上的 charset
|
||||
*/
|
||||
private val defaultStringCharset: JceCharset,
|
||||
internal val output: ByteArrayOutputStream
|
||||
) : TaggedEncoder<JceDesc>() {
|
||||
override val context get() = this@Jce.context
|
||||
|
||||
protected fun SerialDescriptor.getCharset(index: Int): JceCharset {
|
||||
return findAnnotation<SerialCharset>(index)?.charset ?: defaultStringCharset
|
||||
}
|
||||
|
||||
override fun SerialDescriptor.getTag(index: Int): JceDesc {
|
||||
return JceDesc(getSerialId(this, index) ?: error("cannot find @SerialId"), getCharset(index))
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化最开始的时候的
|
||||
*/
|
||||
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) {
|
||||
StructureKind.LIST -> this
|
||||
StructureKind.MAP -> this
|
||||
StructureKind.CLASS, UnionKind.OBJECT -> this
|
||||
is PolymorphicKind -> this
|
||||
else -> throw SerializationException("Primitives are not supported at top-level")
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
|
||||
is MapLikeDescriptor -> {
|
||||
println("hello")
|
||||
val entries = (value as Map<*, *>).entries
|
||||
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
|
||||
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
|
||||
|
||||
this.writeHead(MAP, currentTag.id)
|
||||
this.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, entries.count())
|
||||
HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(charset, this.output), entries)
|
||||
}
|
||||
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
|
||||
is PrimitiveArrayDescriptor -> {
|
||||
if (value is ByteArray) {
|
||||
this.encodeTaggedByteArray( popTag(), value)
|
||||
} else{
|
||||
serializer.serialize(
|
||||
ListWriter(charset, when(value){
|
||||
is ShortArray -> value.size
|
||||
is IntArray -> value.size
|
||||
is LongArray -> value.size
|
||||
is FloatArray -> value.size
|
||||
is DoubleArray -> value.size
|
||||
is CharArray -> value.size
|
||||
else -> error("unknown array type: ${value.getClassName()}")
|
||||
}, popTag(), this),
|
||||
value
|
||||
)
|
||||
}
|
||||
}
|
||||
is ArrayClassDesc-> {
|
||||
serializer.serialize(
|
||||
ListWriter(charset, (value as Array<*>).size, popTag(), this),
|
||||
value
|
||||
)
|
||||
}
|
||||
is ListLikeDescriptor -> {
|
||||
serializer.serialize(
|
||||
ListWriter(charset, (value as Collection<*>).size, popTag(), this),
|
||||
value
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
if (value is JceStruct) {
|
||||
if (currentTagOrNull == null) {
|
||||
serializer.serialize(this, value)
|
||||
} else {
|
||||
this.writeHead(STRUCT_BEGIN, currentTag.id)
|
||||
serializer.serialize(this, value)
|
||||
this.writeHead(STRUCT_END, 0)
|
||||
}
|
||||
} else serializer.serialize(this, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedByte(tag: JceDesc, value: Byte) {
|
||||
if (value.toInt() == 0) {
|
||||
writeHead(ZERO_TYPE, tag.id)
|
||||
} else {
|
||||
writeHead(BYTE, tag.id)
|
||||
output.write(value.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedShort(tag: JceDesc, value: Short) {
|
||||
if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) {
|
||||
encodeTaggedByte(tag, value.toByte())
|
||||
} else {
|
||||
writeHead(SHORT, tag.id)
|
||||
output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedInt(tag: JceDesc, value: Int) {
|
||||
if (value in Short.MIN_VALUE..Short.MAX_VALUE) {
|
||||
encodeTaggedShort(tag, value.toShort())
|
||||
} else {
|
||||
writeHead(INT, tag.id)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedFloat(tag: JceDesc, value: Float) {
|
||||
writeHead(FLOAT, tag.id)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
|
||||
}
|
||||
|
||||
override fun encodeTaggedDouble(tag: JceDesc, value: Double) {
|
||||
writeHead(DOUBLE, tag.id)
|
||||
output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
|
||||
}
|
||||
|
||||
override fun encodeTaggedLong(tag: JceDesc, value: Long) {
|
||||
if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
|
||||
encodeTaggedInt(tag, value.toInt())
|
||||
} else {
|
||||
writeHead(LONG, tag.id)
|
||||
output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedBoolean(tag: JceDesc, value: Boolean) {
|
||||
encodeTaggedByte(tag, if (value) 1 else 0)
|
||||
}
|
||||
|
||||
override fun encodeTaggedChar(tag: JceDesc, value: Char) {
|
||||
encodeTaggedByte(tag, value.toByte())
|
||||
}
|
||||
|
||||
override fun encodeTaggedEnum(tag: JceDesc, enumDescription: SerialDescriptor, ordinal: Int) {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun encodeTaggedNull(tag: JceDesc) {
|
||||
}
|
||||
|
||||
override fun encodeTaggedUnit(tag: JceDesc) {
|
||||
encodeTaggedNull(tag)
|
||||
}
|
||||
|
||||
fun encodeTaggedByteArray(tag: JceDesc, bytes: ByteArray) {
|
||||
writeHead(SIMPLE_LIST, tag.id)
|
||||
writeHead(BYTE, 0)
|
||||
encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, bytes.size)
|
||||
output.write(bytes)
|
||||
}
|
||||
|
||||
override fun encodeTaggedString(tag: JceDesc, value: String) {
|
||||
val array = value.toByteArray(defaultStringCharset.kotlinCharset)
|
||||
if (array.size > 255) {
|
||||
writeHead(STRING4, tag.id)
|
||||
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
|
||||
output.write(array)
|
||||
} else {
|
||||
writeHead(STRING1, tag.id)
|
||||
output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
|
||||
output.write(array)
|
||||
}
|
||||
}
|
||||
|
||||
override fun encodeTaggedValue(tag: JceDesc, value: Any) {
|
||||
when (value) {
|
||||
is Byte -> encodeTaggedByte(tag, value)
|
||||
is Short -> encodeTaggedShort(tag, value)
|
||||
is Int -> encodeTaggedInt(tag, value)
|
||||
is Long -> encodeTaggedLong(tag, value)
|
||||
is Float -> encodeTaggedFloat(tag, value)
|
||||
is Double -> encodeTaggedDouble(tag, value)
|
||||
is Boolean -> encodeTaggedBoolean(tag, value)
|
||||
is String -> encodeTaggedString(tag, value)
|
||||
is Unit -> encodeTaggedUnit(tag)
|
||||
else -> error("unsupported type: ${value.getClassName()}")
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun writeHead(type: Int, tag: Int) {
|
||||
if (tag < 15) {
|
||||
this.output.write((tag shl 4) or type)
|
||||
return
|
||||
}
|
||||
if (tag < 256) {
|
||||
this.output.write(type or 0xF0)
|
||||
this.output.write(tag)
|
||||
return
|
||||
}
|
||||
throw JceEncodeException("tag is too large: $tag")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UTF8 = Jce(JceCharset.UTF8)
|
||||
val GBK = Jce(JceCharset.GBK)
|
||||
|
||||
public fun byCharSet(c: Charset): Jce {
|
||||
return if (c === CharsetUTF8) {
|
||||
UTF8
|
||||
} else {
|
||||
GBK
|
||||
}
|
||||
}
|
||||
|
||||
internal const val BYTE: Int = 0
|
||||
internal const val DOUBLE: Int = 5
|
||||
internal const val FLOAT: Int = 4
|
||||
internal const val INT: Int = 2
|
||||
internal const val JCE_MAX_STRING_LENGTH = 104857600
|
||||
internal const val LIST: Int = 9
|
||||
internal const val LONG: Int = 3
|
||||
internal const val MAP: Int = 8
|
||||
internal const val SHORT: Int = 1
|
||||
internal const val SIMPLE_LIST: Int = 13
|
||||
internal const val STRING1: Int = 6
|
||||
internal const val STRING4: Int = 7
|
||||
internal const val STRUCT_BEGIN: Int = 10
|
||||
internal const val STRUCT_END: Int = 11
|
||||
internal const val ZERO_TYPE: Int = 12
|
||||
|
||||
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
|
||||
|
||||
internal const val VARINT = 0
|
||||
internal const val i64 = 1
|
||||
internal const val SIZE_DELIMITED = 2
|
||||
internal const val i32 = 5
|
||||
}
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
|
||||
val encoder = ByteArrayOutputStream()
|
||||
|
||||
val dumper = JceEncoder(charset, encoder)
|
||||
dumper.encode(serializer, obj)
|
||||
return encoder.toByteArray()
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun <T>
|
||||
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
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
|
@ -1,46 +1,47 @@
|
||||
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
|
@ -1,12 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ import kotlinx.io.core.discardExact
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
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
|
||||
@ -20,7 +23,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())
|
||||
ProtoBuf
|
||||
|
@ -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,
|
||||
|
@ -1,22 +1,22 @@
|
||||
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.JceInput
|
||||
import net.mamoe.mirai.qqandroid.io.jceMap
|
||||
import net.mamoe.mirai.qqandroid.io.jceStruct
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
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
|
||||
@ -56,73 +56,73 @@ class StatSvc {
|
||||
client, subAppId = subAppId, commandName = commandName,
|
||||
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
|
||||
) {
|
||||
JceInput
|
||||
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())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.io.JceStruct
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
import kotlin.test.Test
|
||||
|
||||
|
||||
class JceDecoderTest {
|
||||
|
||||
@Serializable
|
||||
class TestSimpleJceStruct(
|
||||
@SerialId(0) val string: String = "123",
|
||||
@SerialId(1) val byte: Byte = 123,
|
||||
@SerialId(2) val short: Short = 123,
|
||||
@SerialId(3) val int: Int = 123,
|
||||
@SerialId(4) val long: Long = 123,
|
||||
@SerialId(5) val float: Float = 123f,
|
||||
@SerialId(6) val double: Double = 123.0
|
||||
) : JceStruct
|
||||
|
||||
@Test
|
||||
fun testEncoder() {
|
||||
println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexJceStruct.serializer()).contentToString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEncoder2() {
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class TestComplexJceStruct(
|
||||
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
|
||||
@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
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
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 kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
class JceEncoderTest {
|
||||
|
||||
@Serializable
|
||||
class TestSimpleJceStruct(
|
||||
@SerialId(0) val string: String = "123",
|
||||
@SerialId(1) val byte: Byte = 123,
|
||||
@SerialId(2) val short: Short = 123,
|
||||
@SerialId(3) val int: Int = 123,
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
)
|
||||
}
|
||||
|
||||
@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
|
||||
class TestComplexJceStruct(
|
||||
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
|
||||
@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()
|
||||
)
|
||||
}
|
@ -0,0 +1,368 @@
|
||||
package test
|
||||
|
||||
import net.mamoe.mirai.utils.cryptor.ProtoType
|
||||
import net.mamoe.mirai.utils.cryptor.protoFieldNumber
|
||||
import java.io.File
|
||||
|
||||
fun main() {
|
||||
println(
|
||||
File("""E:\Projects\QQAndroidFF\app\src\main\java\msf\onlinepush""")
|
||||
.generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n")
|
||||
)
|
||||
}
|
||||
|
||||
class ArrangedClass(
|
||||
val name: String,
|
||||
val source: String,
|
||||
val innerClasses: MutableList<ArrangedClass>
|
||||
) {
|
||||
fun toKotlinProtoBufClass(): String {
|
||||
return if (innerClasses.isNotEmpty()) {
|
||||
"""
|
||||
$source {
|
||||
${innerClasses.joinToString("\n\n") { " ${it.toKotlinProtoBufClass()}" }}
|
||||
}
|
||||
""".trimIndent()
|
||||
} else source
|
||||
}
|
||||
|
||||
override fun toString(): String = toKotlinProtoBufClass()
|
||||
}
|
||||
|
||||
fun MutableList<GeneratedClass>.arrangeClasses(): List<ArrangedClass> {
|
||||
val tree: MutableMap<String, ArrangedClass> = mutableMapOf()
|
||||
|
||||
// 先处理所有没有父类的
|
||||
this.removeAll(this.filter { it.superclasses.isEmpty() }.onEach {
|
||||
tree[it.className] = it.toArrangedClass()
|
||||
})
|
||||
|
||||
// 一层继承的处理
|
||||
tree.forEach { (name, clazz) ->
|
||||
this.removeAll(this.filter { it.superclasses.size == 1 && it.superclasses[0] == name }.onEach {
|
||||
clazz.innerClasses.add(it.toArrangedClass())
|
||||
})
|
||||
}
|
||||
|
||||
// 两层继承的处理
|
||||
tree.filter { it.value.innerClasses.isNotEmpty() }.forEach { (_, clazz) ->
|
||||
clazz.innerClasses.forEach { innerClass ->
|
||||
this.removeAll(this.filter { it.superclasses[1] == innerClass.name }.onEach {
|
||||
innerClass.innerClasses.add(it.toArrangedClass())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return tree.values.toList()
|
||||
|
||||
// // 循环处理每个 class 的第一个父类
|
||||
// while (this.any { it.superclasses.isNotEmpty() }) {
|
||||
// this.forEach { generatedClass: GeneratedClass ->
|
||||
// generatedClass.superclasses.lastOrNull()?.let { superClassName ->
|
||||
// if (!tree.containsKey(superClassName)) {
|
||||
// tree[superClassName] = this@arrangeClasses.firstOrNull {
|
||||
// it.className == superClassName
|
||||
// } ?: inline {
|
||||
// println("${generatedClass.className} 继承了 $superClassName, 但找不到生成的这个 class, 将使用一个空的 class 替代")
|
||||
// GeneratedClass(mutableListOf(), superClassName, "class $superClassName")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// generatedClass.superclasses.remove(superClassName)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fun File.generateUnarrangedClasses(): List<GeneratedClass> {
|
||||
return this.listFiles()?.filter { it.isFile }?.map {
|
||||
it.readText().generateProtoBufDataClass()
|
||||
} ?: error("Not a folder")
|
||||
}
|
||||
|
||||
fun String.substringBetween(left: String, right: String): String {
|
||||
return this.substringAfter(left, "").substringBefore(right, "")
|
||||
}
|
||||
|
||||
data class GeneratedClass(
|
||||
/**
|
||||
* 带先后顺序
|
||||
*/
|
||||
val superclasses: MutableList<String>,
|
||||
val className: String,
|
||||
val source: String
|
||||
) {
|
||||
fun toArrangedClass(): ArrangedClass {
|
||||
return ArrangedClass(className, source, mutableListOf())
|
||||
}
|
||||
}
|
||||
|
||||
sealed class InferredType(val adjustKotlinAnnotationDeclaration: (String) -> String = { it }, val kotlinType: String) {
|
||||
object FIXED64 : InferredType({ "@ProtoType(ProtoNumberType.FIXED) $it" }, "Long")
|
||||
object FIXED32 : InferredType({ "@ProtoType(ProtoNumberType.FIXED) $it" }, "Int")
|
||||
object FIXED16 : InferredType({ "@ProtoType(ProtoNumberType.FIXED) $it" }, "Short")
|
||||
object FIXED8 : InferredType({ "@ProtoType(ProtoNumberType.FIXED) $it" }, "Byte")
|
||||
|
||||
object SIGNED64 : InferredType({ "@ProtoType(ProtoNumberType.SIGNED) $it" }, "Long")
|
||||
object SIGNED32 : InferredType({ "@ProtoType(ProtoNumberType.SIGNED) $it" }, "Int")
|
||||
object SIGNED16 : InferredType({ "@ProtoType(ProtoNumberType.SIGNED) $it" }, "Short")
|
||||
object SIGNED8 : InferredType({ "@ProtoType(ProtoNumberType.SIGNED) $it" }, "Byte")
|
||||
|
||||
object UNSIGNED64 : InferredType(kotlinType = "Long")
|
||||
object UNSIGNED32 : InferredType(kotlinType = "Int")
|
||||
object UNSIGNED16 : InferredType(kotlinType = "Short")
|
||||
object UNSIGNED8 : InferredType(kotlinType = "Byte")
|
||||
|
||||
object BYTES : InferredType(kotlinType = "ByteArray")
|
||||
object STRING : InferredType(kotlinType = "String")
|
||||
|
||||
object FLOAT : InferredType(kotlinType = "Float")
|
||||
object DOUBLE : InferredType(kotlinType = "Double")
|
||||
object BOOLEAN : InferredType(kotlinType = "Boolean")
|
||||
|
||||
object ENUM : InferredType(kotlinType = "Int /* enum */")
|
||||
|
||||
class REPEATED(kotlinType: String) : InferredType(kotlinType = "List<$kotlinType>")
|
||||
class CUSTOM(kotlinType: String) : InferredType(kotlinType = kotlinType)
|
||||
}
|
||||
|
||||
data class PBFieldInfo(
|
||||
val protoTag: Int,
|
||||
val name: String,
|
||||
val inferredType: InferredType,
|
||||
val defaultValue: String
|
||||
) {
|
||||
fun toKotlinProtoBufClassParam(): String {
|
||||
return "${inferredType.adjustKotlinAnnotationDeclaration("@SerialId($protoTag)")} val $name: ${inferredType.kotlinType}${if (defaultValue == "null") "?" else ""} = $defaultValue"
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
fun String.generateProtoBufDataClass(): GeneratedClass {
|
||||
if (this.indexOf("extends") == -1) {
|
||||
val javaClassname = substringBetween("class", "{")
|
||||
val superclasses = javaClassname.split("$").map { it.trim().adjustClassName() }.toMutableList().apply { removeAt(this.lastIndex) }
|
||||
val className = substringBetween("class", "{").substringAfterLast("$").trim().adjustClassName()
|
||||
return GeneratedClass(superclasses, className, "@Serializable\nclass $className")
|
||||
}
|
||||
|
||||
val superclasses = substringBetween("class", "extends").split("$").map { it.trim().adjustClassName() }.toMutableList()
|
||||
superclasses.removeAt(superclasses.lastIndex)
|
||||
val className = substringBetween("class", "extends").substringAfterLast("$").trim().adjustClassName()
|
||||
|
||||
|
||||
val ids = substringBetween("new int[]{", "}").split(",").map { it.trim() }
|
||||
|
||||
if (ids.all { it.isBlank() }) {
|
||||
return GeneratedClass(superclasses, className, "@Serializable\nclass $className")
|
||||
}
|
||||
|
||||
val names = substringBetween("new String[]{", "}").split(",").map { it.trim() }
|
||||
val defaultValues = substringBetween("new Object[]{", "}").split(",").map { it.trim() }
|
||||
|
||||
|
||||
// name to original declaration
|
||||
val pbTypedFields = lines()
|
||||
.asSequence()
|
||||
.map { it.trim() }
|
||||
.filter { it.startsWith("public final PB") }
|
||||
.filterNot { it.startsWith("public final class") }
|
||||
.map { it.substringAfter("public final PB") }
|
||||
.associateBy { it.substringBetween(" ", " ").takeIf { it.isNotBlank() } ?: it.substringBetween(" ", ";") }
|
||||
.mapKeys { it.key.trim() }
|
||||
|
||||
val customTypedFields = lines()
|
||||
.asSequence()
|
||||
.map { it.trim() }
|
||||
.filter { it.startsWith("public ") }
|
||||
.filterNot { it.startsWith("public final") }
|
||||
.filterNot { it.startsWith("public static") }
|
||||
.filterNot { it.startsWith("public ${substringBetween("class", "extends").trim()}()") }
|
||||
.map { it.substringAfter("public ") }
|
||||
.associateBy { it.substringBetween(" ", " ").takeIf { it.isNotBlank() } ?: it.substringBetween(" ", ";") }
|
||||
.mapKeys { it.key.trim() }
|
||||
|
||||
val source = buildString {
|
||||
append("@Serializable").append("\n")
|
||||
append("class $className(").append("\n")
|
||||
|
||||
ids.map { it.toUInt() }
|
||||
.map { protoFieldNumber(it) }
|
||||
.mapIndexed { index, tag ->
|
||||
var name = names[index].adjustName()
|
||||
var defaultValue = defaultValues[index].let {
|
||||
if (it.startsWith("var")) "EMPTY_BYTE_ARRAY" else it
|
||||
}
|
||||
|
||||
val originalName = names[index].substringBetween("\"", "\"")
|
||||
val javaDeclaration = pbTypedFields[originalName]
|
||||
|
||||
val inferredType: InferredType = if (javaDeclaration == null) {
|
||||
InferredType.CUSTOM(
|
||||
customTypedFields[originalName]?.substringBefore(" ")?.adjustClassName()?.replace("$", ".")
|
||||
?: error("找不到 customTypedFields for $originalName in class $className")
|
||||
)
|
||||
} else {
|
||||
when (val javaFieldType = javaDeclaration.substringBefore("Field")) {
|
||||
"Int8", "UInt8" -> InferredType.UNSIGNED8
|
||||
"Int16", "UInt16" -> InferredType.UNSIGNED16
|
||||
"Int32", "UInt32" -> InferredType.UNSIGNED32
|
||||
"Int64", "UInt64" -> InferredType.UNSIGNED64
|
||||
|
||||
"SInt8" -> InferredType.SIGNED8
|
||||
"SInt16" -> InferredType.SIGNED16
|
||||
"SInt32" -> InferredType.SIGNED32
|
||||
"SInt64" -> InferredType.SIGNED64
|
||||
|
||||
"Fixed8", "FInt8", "SFInt8" -> InferredType.FIXED8
|
||||
"Fixed16", "FInt16", "SFInt16" -> InferredType.FIXED16
|
||||
"Fixed32", "FInt32", "SFInt32" -> InferredType.FIXED32
|
||||
"Fixed64", "FInt64", "SFInt64" -> InferredType.FIXED64
|
||||
|
||||
"Bytes" -> InferredType.BYTES
|
||||
"String" -> InferredType.STRING
|
||||
"Double" -> InferredType.DOUBLE
|
||||
"Float" -> InferredType.FLOAT
|
||||
"Bool" -> InferredType.BOOLEAN
|
||||
"Enum" -> InferredType.ENUM
|
||||
|
||||
"Repeat", "RepeatMessage" -> InferredType.REPEATED(
|
||||
javaDeclaration.substringBetween("<", ">").adjustClassName().replace(
|
||||
"$",
|
||||
"."
|
||||
).replace("Integer", "Int")
|
||||
)
|
||||
else -> error("Unsupported type: $javaFieldType for $originalName in class $className")
|
||||
}
|
||||
}
|
||||
|
||||
fun adjustPropertyName(_name: String): String {
|
||||
var name = _name
|
||||
when {
|
||||
name.startsWith("str") -> {
|
||||
name = name.substringAfter("str").takeIf { it.isNotBlank() }?.adjustName() ?: "str"
|
||||
if (defaultValue == "EMPTY_BYTE_ARRAY")
|
||||
defaultValue = "\"\""
|
||||
}
|
||||
name.startsWith("uint32") -> {
|
||||
name = name.substringAfter("uint32").takeIf { it.isNotBlank() }?.adjustName() ?: "uint32"
|
||||
defaultValue = defaultValue.replace("D", "", ignoreCase = true)
|
||||
.replace("f", "", ignoreCase = true)
|
||||
.replace(".0", "", ignoreCase = true)
|
||||
.replace("l", "", ignoreCase = true)
|
||||
}
|
||||
name.startsWith("double") -> {
|
||||
name = name.substringAfter("double").takeIf { it.isNotBlank() }?.adjustName() ?: "double"
|
||||
defaultValue = defaultValue.replace("D", "", ignoreCase = true)
|
||||
.replace("f", "", ignoreCase = true)
|
||||
.replace("l", "", ignoreCase = true)
|
||||
}
|
||||
name.startsWith("float") -> {
|
||||
name = name.substringAfter("float").takeIf { it.isNotBlank() }?.adjustName() ?: "float"
|
||||
defaultValue = defaultValue.replace("D", "", ignoreCase = true)
|
||||
.replace("f", "", ignoreCase = true)
|
||||
.replace("l", "", ignoreCase = true) + "f"
|
||||
}
|
||||
name.startsWith("uint16") -> {
|
||||
name = name.substringAfter("uint16").takeIf { it.isNotBlank() }?.adjustName() ?: "uint16"
|
||||
defaultValue = defaultValue.replace("D", "", ignoreCase = true)
|
||||
.replace("f", "", ignoreCase = true)
|
||||
.replace(".0", "", ignoreCase = true)
|
||||
.replace("l", "", ignoreCase = true)
|
||||
}
|
||||
name.startsWith("uint8") -> {
|
||||
name = name.substringAfter("uint8").takeIf { it.isNotBlank() }?.adjustName() ?: "uint8"
|
||||
defaultValue = defaultValue.replace("D", "", ignoreCase = true)
|
||||
.replace("f", "", ignoreCase = true)
|
||||
.replace(".0", "", ignoreCase = true)
|
||||
.replace("l", "", ignoreCase = true)
|
||||
}
|
||||
name.startsWith("uint64") -> {
|
||||
name = name.substringAfter("uint64").takeIf { it.isNotBlank() }?.adjustName() ?: "uint64"
|
||||
defaultValue = defaultValue.replace("D", "", ignoreCase = true)
|
||||
.replace("f", "", ignoreCase = true)
|
||||
.replace(".0", "", ignoreCase = true)
|
||||
}
|
||||
name.startsWith("bytes") -> {
|
||||
name = name.substringAfter("bytes").takeIf { it.isNotBlank() }?.adjustName() ?: "bytes"
|
||||
}
|
||||
name.startsWith("rpt") -> {
|
||||
name = name.substringAfter("rpt").takeIf { it.isNotBlank() }?.substringAfter("_")?.let { adjustPropertyName(it) } ?: "rpt"
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
name = adjustPropertyName(name)
|
||||
|
||||
when (inferredType) {
|
||||
is InferredType.REPEATED -> {
|
||||
if (defaultValue.getNumericalValue() == 0 || defaultValue == "EMPTY_BYTE_ARRAY") {
|
||||
defaultValue = "null"
|
||||
}
|
||||
}
|
||||
is InferredType.STRING -> {
|
||||
if (defaultValue == "EMPTY_BYTE_ARRAY") {
|
||||
defaultValue = "\"\""
|
||||
}
|
||||
}
|
||||
is InferredType.BYTES -> {
|
||||
if (defaultValue == "\"\"") {
|
||||
defaultValue = "EMPTY_BYTE_ARRAY"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name = name.adjustName()
|
||||
if (name[0] in '0'..'9') {
|
||||
name = "_" + name
|
||||
}
|
||||
|
||||
append(PBFieldInfo(tag, name, inferredType, defaultValue).toKotlinProtoBufClassParam())
|
||||
|
||||
if (ids.size - 1 != index) {
|
||||
append(",")
|
||||
}
|
||||
|
||||
append("\n")
|
||||
}
|
||||
|
||||
append(")")
|
||||
}
|
||||
|
||||
return GeneratedClass(superclasses, className, source)
|
||||
}
|
||||
|
||||
fun String.getNumericalValue(): Int? {
|
||||
return this.filter { it in '0'..'9' }.toDoubleOrNull()?.toInt()
|
||||
}
|
||||
|
||||
fun ProtoType.mapToKotlinType(): String {
|
||||
return when (this) {
|
||||
ProtoType.VAR_INT -> "Int"
|
||||
ProtoType.BIT_64 -> "Long"
|
||||
ProtoType.LENGTH_DELIMI -> "String"
|
||||
ProtoType.BIT_32 -> "Float"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
fun String.adjustClassName(): String {
|
||||
when (this.trim()) {
|
||||
"ByteStringMicro" -> return "ByteArray"
|
||||
}
|
||||
return String(this.adjustName().toCharArray().apply { this[0] = this[0].toUpperCase() })
|
||||
}
|
||||
|
||||
fun String.adjustName(): String {
|
||||
val result = this.toCharArray()
|
||||
result[0] = result[0].toLowerCase()
|
||||
for (index in result.indices) {
|
||||
if (result[index] == '_') {
|
||||
if (index + 1 in result.indices) {
|
||||
result[index + 1] = result[index + 1].toUpperCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return String(result).replace("_", "").trim().removePrefix("\"").removeSuffix("\"")
|
||||
}
|
55
mirai-core-qqandroid/src/jvmTest/kotlin/test/QLogReader.kt
Normal file
55
mirai-core-qqandroid/src/jvmTest/kotlin/test/QLogReader.kt
Normal file
@ -0,0 +1,55 @@
|
||||
package test
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.util.zip.InflaterInputStream
|
||||
|
||||
object QLogReader {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
println(readQLog(File("C:\\Users\\Him18\\Desktop\\log\\wtlogin_20200101.log")))
|
||||
}
|
||||
|
||||
fun readQLog(file: File): String {
|
||||
return (decompress(file.readBytes()))
|
||||
}
|
||||
|
||||
|
||||
fun decompress(array: ByteArray): String {
|
||||
return buildString {
|
||||
if (array.isNotEmpty()) {
|
||||
var i = 0
|
||||
var n = 0
|
||||
while (array.size > n + 3) {
|
||||
val buf_to_int32: Int = buf_to_int32(array, n)
|
||||
if (array.size <= n + buf_to_int32 + 3) {
|
||||
break
|
||||
}
|
||||
val buf = ByteArray(buf_to_int32)
|
||||
System.arraycopy(array, n + 4, buf, 0, buf_to_int32)
|
||||
n += 4 + buf_to_int32
|
||||
++i
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val `in` = ByteArrayInputStream(buf)
|
||||
val inflaterInputStream = InflaterInputStream(`in`)
|
||||
val array2 = ByteArray(1024)
|
||||
while (true) {
|
||||
val read = inflaterInputStream.read(array2)
|
||||
if (read == -1) {
|
||||
break
|
||||
}
|
||||
byteArrayOutputStream.write(array2, 0, read)
|
||||
}
|
||||
append(byteArrayOutputStream.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun buf_to_int32(array: ByteArray, n: Int): Int {
|
||||
return (array[n].toInt() shl 24 and -0x1000000) + (array[n + 1].toInt() shl 16 and 0xFF0000) + (array[n + 2].toInt() shl 8 and 0xFF00) + (array[n + 3].toInt() shl 0 and 0xFF)
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -1,8 +0,0 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
/**
|
||||
* 直接抛出异常. 需自行处理验证码, 在 [BotConfiguration.captchaSolver] 中调整
|
||||
*/
|
||||
actual var DefaultCaptchaSolver: CaptchaSolver = {
|
||||
error("No CaptchaSolver found. BotConfiguration.captchaSolver should be assigned manually")
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
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")
|
||||
}
|
||||
|
||||
}
|
@ -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() }
|
||||
|
@ -1,287 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,295 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user