mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-23 22:00:10 +08:00
Merge branch 'master' of https://github.com/mamoe/mirai
This commit is contained in:
commit
2f1114d845
@ -33,7 +33,7 @@
|
||||
[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动Mirai并获得机器人服务
|
||||
|
||||
## Use as a library
|
||||
**把 Mirai 作为库内置于您的项目中使用.**
|
||||
**mirai-core 为独立设计, 可以作为库内置于您的任意 Java/Android 项目中使用.**
|
||||
Mirai 只上传在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库
|
||||
```kotlin
|
||||
repositories{
|
||||
@ -120,4 +120,4 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
|
||||
|
||||
## Acknowledgement
|
||||
特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 提供的免费 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 授权
|
||||
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)
|
||||
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)
|
||||
|
8
gradle/android.gradle
Normal file
8
gradle/android.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
apply plugin: "com.android.library"
|
||||
|
||||
android {
|
||||
compileSdkVersion(29)
|
||||
defaultConfig {
|
||||
minSdkVersion(15)
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("kotlinx-atomicfu")
|
||||
id("com.android.library")
|
||||
id("kotlinx-serialization")
|
||||
`maven-publish`
|
||||
id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME
|
||||
@ -32,15 +31,9 @@ version = rootProject.ext.get("mirai_version")!!.toString()
|
||||
|
||||
val isAndroidSDKAvailable: Boolean by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(29)
|
||||
defaultConfig {
|
||||
minSdkVersion(15)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
if (isAndroidSDKAvailable) {
|
||||
apply(from = rootProject.file("gradle/android.gradle"))
|
||||
android("android") {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ actual class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(
|
||||
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId
|
||||
}
|
||||
}.getOrElse { "" }
|
||||
override val ipAddress: String get() = localIpAddress()
|
||||
override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
|
||||
override val androidId: ByteArray get() = Build.ID.toByteArray()
|
||||
override val apn: ByteArray get() = "wifi".toByteArray()
|
||||
|
||||
|
@ -0,0 +1,356 @@
|
||||
package net.mamoe.mirai.qqandroid.network.io
|
||||
|
||||
import kotlinx.io.core.*
|
||||
import net.mamoe.mirai.utils.io.readString
|
||||
import kotlin.experimental.and
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
inline class JceHead(private val value: Long) {
|
||||
constructor(tag: Int, type: Byte) : this(tag.shl(32).toLong() and type.toLong())
|
||||
|
||||
val tag: Int get() = (value ushr 32).toInt()
|
||||
val type: Byte get() = value.toUInt().toByte()
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
class JceInput(
|
||||
@PublishedApi
|
||||
internal val input: Input
|
||||
) {
|
||||
|
||||
@PublishedApi
|
||||
internal fun readHead(): JceHead = input.run {
|
||||
val var2 = readByte()
|
||||
val type = var2 and 15
|
||||
var tag = (var2.toInt() and 240) shr 4
|
||||
if (tag == 15)
|
||||
tag = readByte().toInt() and 255
|
||||
return JceHead(tag = tag, type = type)
|
||||
}
|
||||
|
||||
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")
|
||||
fun <T> readList(defaultElement: T, tag: Int): List<T> = readListOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
|
||||
fun <T> readSimpleArray(defaultElement: T, tag: Int): Array<T> = readArrayOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
|
||||
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()
|
||||
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(input.readInt()) { readByte(tag) }
|
||||
13 -> {
|
||||
val head = readHead()
|
||||
check(head.type.toInt() == 0) { "type mismatch" }
|
||||
input.readBytes(input.readInt())
|
||||
}
|
||||
else -> error("type mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
ShortArray(input.readInt()) { readShort(tag) }
|
||||
}
|
||||
|
||||
fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
DoubleArray(input.readInt()) { readDouble(tag) }
|
||||
}
|
||||
|
||||
fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
FloatArray(input.readInt()) { readFloat(tag) }
|
||||
}
|
||||
|
||||
fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
IntArray(input.readInt()) { readInt(tag) }
|
||||
}
|
||||
|
||||
fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
LongArray(input.readInt()) { readLong(tag) }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> readArrayOrNull(tag: Int): Array<T>? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
Array(input.readInt()) { readSimpleObject<T>(tag) }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> readArrayOrNull(defaultElement: T, tag: Int): Array<T>? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
Array(input.readInt()) { readObject(defaultElement, tag) as Any } as Array<T>
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <J : JceStruct> readJceStructArrayOrNull(factory: JceStruct.Factory<J>, tag: Int): Array<J>? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
Array(input.readInt()) { readObject(factory, tag) as Any } as Array<J>
|
||||
}
|
||||
|
||||
fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
|
||||
require(it.type.toInt() == 9) { "type mismatch" }
|
||||
BooleanArray(input.readInt()) { readBoolean(tag) }
|
||||
}
|
||||
|
||||
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
|
||||
return when (head.type.toInt()) {
|
||||
6 -> input.readString(input.readUByte().toInt())
|
||||
7 -> input.readString(input.readUInt().also { require(it.toInt() in 1 until 104857600) { "bad string length: $it" } }.toInt())
|
||||
else -> error("type mismatch: ${head.type}")
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> readMapOrNull(defaultKey: K, defaultValue: V, 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[readObject(defaultKey, 0)] = readObject(defaultValue, 0)
|
||||
}
|
||||
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(0)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
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[it] = readObject(defaultElement, tag)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun <J : JceStruct> readJceStructOrNull(factory: JceStruct.Factory<J>, tag: Int): J? = skipToTagOrNull(tag) { head ->
|
||||
readHead()
|
||||
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 JceStruct.Factory<JceStruct> -> readJceStruct(default, tag) as T
|
||||
is List<*> -> {
|
||||
readList(default[0], 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
|
||||
val length = this.readInt(0)
|
||||
var read = 0
|
||||
while (read < length * 2) {
|
||||
skipField()
|
||||
++read
|
||||
}
|
||||
}
|
||||
9 -> { // list
|
||||
val length = this.readInt(0)
|
||||
var read = 0
|
||||
while (read < length) {
|
||||
skipField()
|
||||
++read
|
||||
}
|
||||
}
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return null
|
||||
|
||||
val head = readHead()
|
||||
if (head.tag == tag) {
|
||||
return block(head)
|
||||
}
|
||||
this.skipField(head.type)
|
||||
}
|
||||
}
|
@ -2,21 +2,31 @@ package net.mamoe.mirai.qqandroid.network.io
|
||||
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
||||
import net.mamoe.mirai.utils.io.readRemainingBytes
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
private val CharsetGBK = Charset.forName("GBK")
|
||||
@PublishedApi
|
||||
internal val CharsetGBK = Charset.forName("GBK")
|
||||
|
||||
fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
|
||||
inline fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
|
||||
return JceOutput(stringCharset).apply(block).build()
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.writeJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit) {
|
||||
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
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.mamoe.mirai.qqandroid.network.io
|
||||
|
||||
import kotlinx.io.core.BytePacketBuilder
|
||||
|
||||
abstract class JceStruct {
|
||||
abstract fun writeTo(builder: JceOutput)
|
||||
|
||||
interface Factory<out T : JceStruct> {
|
||||
fun newInstanceFrom(input: JceInput): T
|
||||
}
|
||||
}
|
@ -1,22 +1,63 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.jce
|
||||
|
||||
import net.mamoe.mirai.qqandroid.network.io.JceInput
|
||||
import net.mamoe.mirai.qqandroid.network.io.JceOutput
|
||||
import net.mamoe.mirai.qqandroid.network.io.JceStruct
|
||||
|
||||
private val EMPTY_MAP = mapOf<String, String>()
|
||||
|
||||
class RequestPacket(
|
||||
val sBuffer: ByteArray,
|
||||
val cPacketType: Byte = 0,
|
||||
val iMessageType: Int = 0,
|
||||
val iRequestId: Int = 0,
|
||||
val iTimeout: Int = 3000,
|
||||
val iVersion: Short = 3,
|
||||
val context: Map<String, String> = EMPTY_MAP,
|
||||
val sFuncName: String = "",
|
||||
val sServantName: String = "",
|
||||
val status: Map<String, String> = EMPTY_MAP
|
||||
) : JceStruct() {
|
||||
class RequestPacket() : JceStruct() {
|
||||
lateinit var sBuffer: ByteArray
|
||||
var cPacketType: Byte = 0
|
||||
var iMessageType: Int = 0
|
||||
var iRequestId: Int = 0
|
||||
var iTimeout: Int = 3000
|
||||
var iVersion: Short = 3
|
||||
var context: Map<String, String> = EMPTY_MAP
|
||||
var sFuncName: String = ""
|
||||
var sServantName: String = ""
|
||||
var status: Map<String, String> = EMPTY_MAP
|
||||
|
||||
constructor(
|
||||
sBuffer: ByteArray,
|
||||
cPacketType: Byte = 0,
|
||||
iMessageType: Int = 0,
|
||||
iRequestId: Int = 0,
|
||||
iTimeout: Int = 3000,
|
||||
iVersion: Short = 3,
|
||||
context: Map<String, String> = EMPTY_MAP,
|
||||
sFuncName: String = "",
|
||||
sServantName: String = "",
|
||||
status: Map<String, String> = EMPTY_MAP
|
||||
) : this() {
|
||||
this.sBuffer = sBuffer
|
||||
this.cPacketType = cPacketType
|
||||
this.iMessageType = iMessageType
|
||||
this.iRequestId = iRequestId
|
||||
this.iTimeout = iTimeout
|
||||
this.iVersion = iVersion
|
||||
this.context = context
|
||||
this.sFuncName = sFuncName
|
||||
this.sServantName = sServantName
|
||||
this.status = status
|
||||
}
|
||||
|
||||
companion object : Factory<RequestPacket> {
|
||||
override fun newInstanceFrom(input: JceInput): RequestPacket {
|
||||
val iVersion = input.readShort(1)
|
||||
val cPacketType = input.readByte(2)
|
||||
val iMessageType = input.readInt(3)
|
||||
val iRequestId = input.readInt(4)
|
||||
val sServantName = input.readString(5)
|
||||
val sFuncName = input.readString(6)
|
||||
val sBuffer = input.readByteArray(7)
|
||||
val iTimeout = input.readInt(8)
|
||||
val context = input.readMap("", "", 9)
|
||||
val status = input.readMap("", "", 10)
|
||||
return RequestPacket(sBuffer, cPacketType, iMessageType, iRequestId, iTimeout, iVersion, context, sFuncName, sServantName, status)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeTo(builder: JceOutput) {
|
||||
builder.write(this.iVersion, 1)
|
||||
builder.write(this.cPacketType, 2)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.jce
|
||||
|
||||
import net.mamoe.mirai.qqandroid.network.io.JceInput
|
||||
import net.mamoe.mirai.qqandroid.network.io.JceOutput
|
||||
import net.mamoe.mirai.qqandroid.network.io.JceStruct
|
||||
|
||||
@ -25,15 +26,15 @@ class SvcReqRegister(
|
||||
val lBid: Long = 0L,
|
||||
val lCpId: Long = 0L,
|
||||
val lUin: Long = 0L,
|
||||
val sBuildVer: String? = "",
|
||||
val sChannelNo: String? = "",
|
||||
val sBuildVer: String? = null,
|
||||
val sChannelNo: String? = null,
|
||||
val sOther: String = "",
|
||||
val strDevName: String? = "",
|
||||
val strDevType: String? = "",
|
||||
val strIOSIdfa: String? = "",
|
||||
val strOSVer: String? = "",
|
||||
val strVendorName: String? = "",
|
||||
val strVendorOSName: String? = "",
|
||||
val strDevName: String? = null,
|
||||
val strDevType: String? = null,
|
||||
val strIOSIdfa: String? = null,
|
||||
val strOSVer: String? = null,
|
||||
val strVendorName: String? = null,
|
||||
val strVendorOSName: String? = null,
|
||||
val timeStamp: Long = 0L,
|
||||
val uNewSSOIp: Long = 0L,
|
||||
val uOldSSOIp: Long = 0L,
|
||||
@ -41,7 +42,12 @@ class SvcReqRegister(
|
||||
val vecGuid: ByteArray? = null,
|
||||
val vecServerBuf: ByteArray? = null
|
||||
) : JceStruct() {
|
||||
|
||||
companion object : Factory<RequestPacket> {
|
||||
override fun newInstanceFrom(input: JceInput): RequestPacket {
|
||||
TODO("not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeTo(builder: JceOutput) {
|
||||
builder.write(lUin, 0)
|
||||
builder.write(lBid, 1)
|
||||
|
@ -5,8 +5,8 @@ import net.mamoe.mirai.qqandroid.network.io.JceOutput
|
||||
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
|
||||
import net.mamoe.mirai.qqandroid.network.io.writeJcePacket
|
||||
|
||||
fun BytePacketBuilder.writeUniRequestPacket(requestPacket: RequestPacket) {
|
||||
inline fun BytePacketBuilder.writeUniRequestPacket(requestPacket: RequestPacket.() -> Unit) {
|
||||
writeJcePacket {
|
||||
requestPacket.writeTo(this)
|
||||
RequestPacket().apply(requestPacket).writeTo(this)
|
||||
}
|
||||
}
|
@ -8,8 +8,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.NullPacketId
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.NullPacketId.commandName
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
|
||||
import net.mamoe.mirai.utils.cryptor.Decrypter
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterType
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.SvcReqRegisterPacket
|
||||
import net.mamoe.mirai.utils.cryptor.adjustToPublicKey
|
||||
import net.mamoe.mirai.utils.cryptor.decryptBy
|
||||
import net.mamoe.mirai.utils.io.*
|
||||
@ -50,7 +49,8 @@ internal typealias PacketConsumer = suspend (packet: Packet, packetId: PacketId,
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
LoginPacket
|
||||
LoginPacket,
|
||||
SvcReqRegisterPacket
|
||||
) {
|
||||
|
||||
fun findPacketFactory(commandName: String): PacketFactory<*> = this.first { it.id.commandName == commandName }
|
||||
@ -59,12 +59,12 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
|
||||
// do not inline. Exceptions thrown will not be reported correctly
|
||||
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
|
||||
rawInput.debugPrintIfFail("Incoming packet") {
|
||||
rawInput.debugIfFail("Incoming packet") {
|
||||
require(remaining < Int.MAX_VALUE) { "rawInput is too long" }
|
||||
val expectedLength = readUInt().toInt() - 4
|
||||
if (expectedLength > 16e7) {
|
||||
bot.logger.warning("Detect incomplete packet, ignoring.")
|
||||
return@debugPrintIfFail
|
||||
return@debugIfFail
|
||||
}
|
||||
check(remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " }
|
||||
// login
|
||||
@ -81,13 +81,15 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
|
||||
}
|
||||
else -> error("Illegal flag2. Expected 0x02, got $flag2")
|
||||
}
|
||||
// 00 00 00 60 00 00 00 0B 02 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 CE 35 53 19 84 A8 1A B8 5B 48 E3 7C D0 A6 BA 58 6A EB CE 50 B9 A0 98 D5 B9 D0 1C 72 E2 86 24 FC 55 44 6C 6E E3 F9 15 6C EC 6C 6B 94 40 F7 B4 45 CF B4 D0 79 84 FE 30 EA 98 84 44 84 02 32 70 DD D7 07 07 72 DE 87 59 AC
|
||||
0x0B ->
|
||||
else -> error("Illegal flag1. Expected 0x0A or 0x0B, got $flag1")
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private suspend fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
|
||||
rawInput.debugPrintIfFail("Login sso packet") {
|
||||
rawInput.debugIfFail("Login sso packet") {
|
||||
val commandName: String
|
||||
val ssoSequenceId: Int
|
||||
readIoBuffer(readInt() - 4).withUse {
|
||||
|
@ -16,14 +16,15 @@ import kotlin.random.Random
|
||||
*/
|
||||
inline class Tlv(val value: ByteArray)
|
||||
|
||||
fun BytePacketBuilder.t1(uin: Long, ip: String) {
|
||||
fun BytePacketBuilder.t1(uin: Long, ip: ByteArray) {
|
||||
require(ip.size == 4) { "ip.size must == 4" }
|
||||
writeShort(0x1)
|
||||
writeShortLVPacket {
|
||||
writeShort(1) // _ip_ver
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(uin.toInt())
|
||||
writeTime()
|
||||
writeFully(ByteArray(4))
|
||||
writeFully(ip)
|
||||
writeShort(0)
|
||||
} shouldEqualsTo 20
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,13 +1,12 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.io.jceMap
|
||||
import net.mamoe.mirai.qqandroid.network.io.jceStruct
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.jce.writeUniRequestPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
@ -38,73 +37,63 @@ internal object SvcReqRegisterPacket : PacketFactory<SvcReqRegisterPacket.Respon
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
regPushReason: RegPushReason = RegPushReason.setOnlineStatus
|
||||
): OutgoingPacket = buildOutgingPacket(client, key = client.wLoginSigInfo.wtSessionTicketKey) {
|
||||
writeUniRequestPacket(
|
||||
RequestPacket(
|
||||
sServantName = "PushService",
|
||||
sFuncName = "SvcReqRegister",
|
||||
sBuffer = buildJcePacket {
|
||||
writeMap(
|
||||
mapOf(
|
||||
"SvcReqRegister" to buildJcePacket {
|
||||
writeJceStruct(
|
||||
SvcReqRegister(
|
||||
cConnType = 0,
|
||||
lBid = 1 or 2 or 4,
|
||||
lUin = client.uin,
|
||||
iStatus = client.onlineStatus.id,
|
||||
bKikPC = 0, // 是否把 PC 踢下线
|
||||
bKikWeak = 0,
|
||||
timeStamp = currentTimeSeconds, // millis or seconds??
|
||||
iLargeSeq = 0,
|
||||
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(),
|
||||
): OutgoingPacket = buildOutgingPacket(client, key = client.wLoginSigInfo.d2Key) {
|
||||
writeUniRequestPacket {
|
||||
sServantName = "PushService"
|
||||
sFuncName = "SvcReqRegister"
|
||||
sBuffer = jceMap(
|
||||
0,
|
||||
"SvcReqRegister" to jceStruct(
|
||||
0,
|
||||
SvcReqRegister(
|
||||
cConnType = 0,
|
||||
lBid = 1 or 2 or 4,
|
||||
lUin = client.uin,
|
||||
iStatus = client.onlineStatus.id,
|
||||
bKikPC = 0, // 是否把 PC 踢下线
|
||||
bKikWeak = 0,
|
||||
timeStamp = currentTimeSeconds, // millis or seconds??
|
||||
iLargeSeq = 0,
|
||||
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(),
|
||||
|
||||
// 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 = 4
|
||||
),
|
||||
Oidb0x769.ConfigSeq(
|
||||
type = 283,
|
||||
version = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
bSetMute = 0
|
||||
), 0
|
||||
// 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 = 4
|
||||
),
|
||||
Oidb0x769.ConfigSeq(
|
||||
type = 283,
|
||||
version = 0
|
||||
)
|
||||
)
|
||||
}.readBytes()
|
||||
), 0
|
||||
)
|
||||
),
|
||||
bSetMute = 0
|
||||
)
|
||||
}.readBytes()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
|
@ -3,10 +3,8 @@ package net.mamoe.mirai.qqandroid.utils
|
||||
import kotlinx.serialization.SerialId
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
|
||||
import net.mamoe.mirai.utils.cryptor.contentToString
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.io.encodeToString
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
|
||||
abstract class DeviceInfo(
|
||||
@ -42,7 +40,7 @@ abstract class DeviceInfo(
|
||||
abstract val imsiMd5: ByteArray
|
||||
abstract val imei: String
|
||||
|
||||
abstract val ipAddress: String
|
||||
abstract val ipAddress: ByteArray
|
||||
|
||||
abstract val androidId: ByteArray
|
||||
|
||||
|
@ -27,7 +27,9 @@ actual class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(
|
||||
override val imsiMd5: ByteArray
|
||||
get() = ubyteArrayOf(0xD4u, 0x1Du, 0x8Cu, 0xD9u, 0x8Fu, 0x00u, 0xB2u, 0x04u, 0xE9u, 0x80u, 0x09u, 0x98u, 0xECu, 0xF8u, 0x42u, 0x7Eu).toByteArray()
|
||||
override val imei: String get() = "858414369211993"
|
||||
override val ipAddress: String get() = localIpAddress()
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
override val ipAddress: ByteArray
|
||||
get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
|
||||
override val androidId: ByteArray get() = "QSR1.190920.001".toByteArray()
|
||||
override val apn: ByteArray get() = "wifi".toByteArray()
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("kotlinx-atomicfu")
|
||||
id("com.android.library")
|
||||
id("kotlinx-serialization")
|
||||
`maven-publish`
|
||||
id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME
|
||||
@ -32,18 +31,11 @@ version = rootProject.ext.get("mirai_version")!!.toString()
|
||||
|
||||
val isAndroidSDKAvailable: Boolean by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(29)
|
||||
defaultConfig {
|
||||
minSdkVersion(15)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
if (isAndroidSDKAvailable) {
|
||||
apply(from = rootProject.file("gradle/android.gradle"))
|
||||
android("android") {
|
||||
publishAllLibraryVariants()
|
||||
project.apply(plugin = "com.android.library")
|
||||
}
|
||||
} else {
|
||||
println(
|
||||
|
@ -10,7 +10,7 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.data.*
|
||||
import net.mamoe.mirai.utils.io.debugPrintIfFail
|
||||
import net.mamoe.mirai.utils.io.debugIfFail
|
||||
import net.mamoe.mirai.utils.io.readQQ
|
||||
import net.mamoe.mirai.utils.io.readRemainingBytes
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
@ -62,7 +62,7 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
|
||||
Unknown0x02DCPacketFlag0x0EMaybeMutePacket(readRemainingBytes())
|
||||
}
|
||||
|
||||
0x11u -> debugPrintIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回??
|
||||
0x11u -> debugIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回??
|
||||
// 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 27 0B 60 E7 11 00 33 08 07 20 E7 C1 AD B8 02 5A 29 08 A6 FE C0 A4 0A 1A 19 08 BC 15 10 C1 95 BC F0 05 18 CA CA 8F DE 04 20 00 28 00 30 A6 FE C0 A4 0A 2A 02 08 00 30 00 38 00
|
||||
// 失败
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("kotlinx-atomicfu")
|
||||
id("com.android.library")
|
||||
id("kotlinx-serialization")
|
||||
`maven-publish`
|
||||
id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME
|
||||
@ -31,16 +30,9 @@ description = "QQ protocol library"
|
||||
|
||||
val isAndroidSDKAvailable: Boolean by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(29)
|
||||
defaultConfig {
|
||||
minSdkVersion(15)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
if (isAndroidSDKAvailable) {
|
||||
project.apply(plugin = "com.android.library")
|
||||
apply(from = rootProject.file("gradle/android.gradle"))
|
||||
android("android") {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import kotlin.reflect.full.allSuperclasses
|
||||
|
||||
|
||||
actual fun Any.contentToStringReflectively(prefix: String): String {
|
||||
val newPrefix = prefix + ProtoMap.indent
|
||||
val newPrefix = prefix
|
||||
return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
|
||||
this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
|
||||
.distinctBy { it.name }
|
||||
|
@ -9,7 +9,7 @@ import net.mamoe.mirai.utils.io.*
|
||||
import net.mamoe.mirai.utils.unzip
|
||||
|
||||
internal fun IoBuffer.parseMessageFace(): Face {
|
||||
debugPrintIfFail("Analyzing Face") {
|
||||
debugIfFail("Analyzing Face") {
|
||||
discardExact(1)
|
||||
|
||||
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
|
||||
|
@ -8,6 +8,7 @@ import kotlin.annotation.AnnotationTarget.*
|
||||
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
|
||||
* 非常不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Experimental(level = Experimental.Level.ERROR)
|
||||
@Target(
|
||||
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
|
||||
@ -25,12 +26,26 @@ annotation class MiraiInternalAPI(
|
||||
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
||||
* 不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Experimental(level = Experimental.Level.ERROR)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Experimental(level = Experimental.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
annotation class MiraiExperimentalAPI(
|
||||
val message: String = ""
|
||||
)
|
||||
|
||||
/**
|
||||
* 标记这个类, 类型, 函数, 属性, 字段, 或构造器为仅供调试阶段使用的.
|
||||
*
|
||||
* 这些 API 不具有稳定性, 可能会在任意时刻更改, 并且效率非常低下.
|
||||
* 非常不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Experimental(level = Experimental.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
annotation class MiraiDebugAPI(
|
||||
val message: String = ""
|
||||
)
|
||||
|
||||
/**
|
||||
* 标记这个 API 是自 Mirai 某个版本起才受支持.
|
||||
*/
|
||||
@ -43,13 +58,13 @@ annotation class SinceMirai(val version: String)
|
||||
* 包的最后一次修改时间, 和分析时使用的 TIM 版本
|
||||
*/
|
||||
@MustBeDocumented
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
@Target(FUNCTION, CLASS, PROPERTY)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class PacketVersion(val date: String, val timVersion: String)
|
||||
|
||||
/**
|
||||
* 带有这个注解的 [Packet] 将不会被记录在 log 中.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Target(CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class NoLog
|
@ -163,7 +163,7 @@ fun Any?.contentToString(prefix: String = ""): String = when (this) {
|
||||
is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() }
|
||||
is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString() }
|
||||
is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() }
|
||||
is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString() + "=" + it.value.contentToString() }
|
||||
is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) }
|
||||
else -> {
|
||||
if (this == null) "null"
|
||||
else if (this::class.isData) this.toString()
|
||||
|
@ -2,27 +2,32 @@ package net.mamoe.mirai.utils.io
|
||||
|
||||
import kotlinx.io.core.*
|
||||
import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.withSwitch
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
|
||||
object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch()
|
||||
|
||||
fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
fun debugPrintln(any: Any?) = DebugLogger.debug(any)
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
fun String.debugPrint(name: String): String {
|
||||
DebugLogger.debug("$name=$this")
|
||||
return this
|
||||
}
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
fun ByteArray.debugPrint(name: String): ByteArray {
|
||||
DebugLogger.debug(name + "=" + this.toUHexString())
|
||||
return this
|
||||
}
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
fun IoBuffer.debugPrint(name: String): IoBuffer {
|
||||
ByteArrayPool.useInstance {
|
||||
val count = this.readAvailable(it)
|
||||
@ -31,6 +36,7 @@ fun IoBuffer.debugPrint(name: String): IoBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
|
||||
ByteArrayPool.useInstance {
|
||||
val count = this.readAvailable(it)
|
||||
@ -39,10 +45,12 @@ inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
fun Input.debugDiscardExact(n: Number, name: String = "") {
|
||||
DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
|
||||
}
|
||||
|
||||
@MiraiDebugAPI("Low efficiency.")
|
||||
fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
|
||||
ByteArrayPool.useInstance {
|
||||
val count = this.readAvailable(it)
|
||||
@ -51,12 +59,24 @@ fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <R> Input.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R {
|
||||
/**
|
||||
* 备份数据, 并在 [block] 失败后执行 [onFail].
|
||||
*
|
||||
* 此方法非常低效. 请仅在测试环境使用.
|
||||
*/
|
||||
@MiraiDebugAPI("Low efficiency")
|
||||
@UseExperimental(ExperimentalContracts::class)
|
||||
inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
callsInPlace(onFail, InvocationKind.UNKNOWN)
|
||||
}
|
||||
ByteArrayPool.useInstance {
|
||||
val count = this.readAvailable(it)
|
||||
try {
|
||||
return block(it.toReadPacket(0, count))
|
||||
} catch (e: Throwable) {
|
||||
onFail(it.take(count).toByteArray()).readAvailable(it)
|
||||
DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))
|
||||
throw e
|
||||
}
|
||||
|
@ -99,6 +99,8 @@ fun UByte.fixToUHex(): String = if (this.toInt() in 0..15) "0${this.toString(16)
|
||||
|
||||
/**
|
||||
* 将无符号 Hex 转为 [ByteArray], 有根据 hex 的 [hashCode] 建立的缓存.
|
||||
*
|
||||
* 这个方法很累, 不建议经常使用.
|
||||
*/
|
||||
fun String.hexToBytes(): ByteArray =
|
||||
this.split(" ")
|
||||
@ -110,12 +112,24 @@ fun String.hexToBytes(): ByteArray =
|
||||
|
||||
/**
|
||||
* 每 2 char 为一组, 转换 Hex 为 [ByteArray]
|
||||
*
|
||||
* 这个方法很累, 不建议经常使用.
|
||||
*/
|
||||
fun String.chunkedHexToBytes(): ByteArray =
|
||||
this.asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray()
|
||||
|
||||
/**
|
||||
* 删掉全部空格和换行后每 2 char 为一组, 转换 Hex 为 [ByteArray].
|
||||
*
|
||||
* 这个方法很累, 不建议经常使用.
|
||||
*/
|
||||
fun String.autoHexToBytes(): ByteArray =
|
||||
this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray()
|
||||
|
||||
/**
|
||||
* 将无符号 Hex 转为 [UByteArray], 有根据 hex 的 [hashCode] 建立的缓存.
|
||||
*
|
||||
* 这个方法很累, 不建议经常使用.
|
||||
*/
|
||||
fun String.hexToUBytes(): UByteArray =
|
||||
this.split(" ")
|
||||
|
@ -14,6 +14,8 @@ import kotlin.jvm.JvmSynthetic
|
||||
*
|
||||
* Some code from http://wiki.vg/Protocol.
|
||||
*
|
||||
* Source project: [Nukkit](http://github.com/nukkit/nukkit)
|
||||
*
|
||||
* @author MagicDroidX of Nukkit Project
|
||||
* @author lmlstarqaq of Nukkit Project
|
||||
*/
|
||||
|
@ -12,6 +12,7 @@ actual fun Any.contentToStringReflectively(prefix: String): String {
|
||||
this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
|
||||
.distinctBy { it.name }
|
||||
.filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" }
|
||||
.filterNot { it.isEnumConstant }
|
||||
.joinToStringPrefixed(
|
||||
prefix = newPrefix
|
||||
) {
|
||||
|
Loading…
Reference in New Issue
Block a user