diff --git a/.gitignore b/.gitignore index d4e87ac68..3343c56f3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ mirai.iml /test -.gradle/ \ No newline at end of file +.gradle/ + + +local.properties \ No newline at end of file diff --git a/build.gradle b/build.gradle index a9f99a31b..73f2eff73 100644 --- a/build.gradle +++ b/build.gradle @@ -4,11 +4,12 @@ buildscript { repositories { mavenLocal() jcenter() - mavenCentral() + google() maven { url "https://mirrors.huaweicloud.com/repository/maven/" } } dependencies { + classpath 'com.android.tools.build:gradle:3.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version" } @@ -20,8 +21,8 @@ allprojects { repositories { mavenLocal() - mavenCentral() jcenter() + google() maven { url "https://mirrors.huaweicloud.com/repository/maven/" } } } \ No newline at end of file diff --git a/mirai-core/build.gradle b/mirai-core/build.gradle.backup similarity index 73% rename from mirai-core/build.gradle rename to mirai-core/build.gradle.backup index 23a113afa..135d783a9 100644 --- a/mirai-core/build.gradle +++ b/mirai-core/build.gradle.backup @@ -1,9 +1,7 @@ -apply plugin: 'kotlinx-atomicfu' -apply plugin: "kotlin-multiplatform" - kotlin { targets { fromPreset(presets.jvm, "jvm") + //fromPreset(presets.jvm, "android") //fromPreset(presets.mingwX64, "mingwX64") } jvm{ @@ -35,6 +33,7 @@ kotlin { implementation "com.soywiz.korlibs.klock:klock:$klock_version" api group: 'io.ktor', name: 'ktor-client-core', version: ktor_version + api group: 'io.ktor', name: 'ktor-network', version: ktor_version //api group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version //api group: 'io.ktor', name: 'ktor-client', version: ktor_version api group: 'io.ktor', name: 'ktor-http', version: ktor_version @@ -87,6 +86,28 @@ kotlin { apply plugin: 'java' } + + androidMain{ + dependencies{ + api 'com.google.android:android:4.1.1.4' + api 'com.android.support:support-annotations:26.1.0' + + api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_version + api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version + api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version + api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version + api group: 'org.jetbrains.kotlinx', name: 'atomicfu', version: atomicfu_version + api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version + // api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io-jvm', version: kotlinxio_version + api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-io', version: coroutinesio_version + + api group: 'io.ktor', name: 'ktor-http-cio', version: ktor_version + api group: 'io.ktor', name: 'ktor-http', version: ktor_version + api group: 'io.ktor', name: 'ktor-client-core-jvm', version: ktor_version + api group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version + } + } + all { languageSettings.enableLanguageFeature("InlineClasses") } diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts new file mode 100644 index 000000000..b3ed8f9e2 --- /dev/null +++ b/mirai-core/build.gradle.kts @@ -0,0 +1,101 @@ +plugins { + id("kotlinx-atomicfu") + kotlin("multiplatform") + //id("com.android.library") + //id("kotlin-android-extensions") +} + +val kotlinVersion = rootProject.ext["kotlin_version"].toString() +val atomicFuVersion = rootProject.ext["atomicfu_version"].toString() +val coroutinesVersion = rootProject.ext["coroutines_version"].toString() +val kotlinXIoVersion = rootProject.ext["kotlinxio_version"].toString() +val coroutinesIoVersion = rootProject.ext["coroutinesio_version"].toString() + +val klockVersion = rootProject.ext["klock_version"].toString() +val ktorVersion = rootProject.ext["ktor_version"].toString() +/* +//apply() +android { + compileSdkVersion(29) + buildToolsVersion("29.0.2") + defaultConfig { + applicationId = "com.youngfeng.kotlindsl" + minSdkVersion(15) + targetSdkVersion(27) + versionCode = 1 + versionName = "1.0" + // testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = true + //proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + } + + sourceSets.forEach { + println(it) + // it.languageSettings.enableLanguageFeature("InlineClasses") + } +} +*/ +kotlin { + // android("android") + jvm("jvm") + + sourceSets["commonMain"].apply { + dependencies { + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") + + implementation("com.soywiz.korlibs.klock:klock:$klockVersion") + + api("io.ktor:ktor-client-core:$ktorVersion") + api("io.ktor:ktor-network:$ktorVersion") + api("io.ktor:ktor-http:$ktorVersion") + + } + } + + /* + sourceSets["androidMain"].apply { + dependencies { + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") + + implementation("io.ktor:ktor-http-cio:$ktorVersion") + implementation("io.ktor:ktor-http:$ktorVersion") + implementation("io.ktor:ktor-client-core-jvm:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + + } + languageSettings.enableLanguageFeature("InlineClasses") + }*/ + + sourceSets["jvmMain"].apply { + dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7") + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") + + implementation("io.ktor:ktor-http-cio:$ktorVersion") + implementation("io.ktor:ktor-http:$ktorVersion") + implementation("io.ktor:ktor-client-core-jvm:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + + } + } + + sourceSets.forEach { + it.languageSettings.enableLanguageFeature("InlineClasses") + + it.dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib") + implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") + implementation("org.jetbrains.kotlinx:kotlinx-io:$kotlinXIoVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-io:$coroutinesIoVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + } + } +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index 3edb69c4d..4bba2428c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -183,7 +183,6 @@ class MessageSubscribersBuilder>( /** * 如果消息的前缀是 [prefix], 就执行 [onEvent] - * @param */ suspend fun startsWith(prefix: String, removePrefix: Boolean = false, onEvent: @MessageDsl suspend T.(String) -> Unit) = content({ it.startsWith(prefix) }) { @@ -258,4 +257,12 @@ class MessageSubscribersBuilder>( */ @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE) @DslMarker -internal annotation class MessageDsl \ No newline at end of file +internal annotation class MessageDsl + + +fun main() { + + println('B') + println("\u7154225") + println('B' - 'b') +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt index 8bcb81c3c..922d225be 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt @@ -5,7 +5,6 @@ package net.mamoe.mirai.message.internal import kotlinx.io.core.* import net.mamoe.mirai.message.* import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.toUHexString internal fun IoBuffer.parseMessageFace(): Face { //00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0 @@ -105,7 +104,7 @@ internal fun ByteReadPacket.readMessage(): Message? { //后面似乎还有一节? //discardExact(7)//02 00 04 00 00 00 23 - return PlainText(value.toUHexString()) + //return PlainText(value.toUHexString()) } 0x0E -> { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt index c749c7231..ed619ed53 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt @@ -23,8 +23,11 @@ import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket import net.mamoe.mirai.network.protocol.tim.packet.login.* import net.mamoe.mirai.network.session import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.BotNetworkConfiguration +import net.mamoe.mirai.utils.OnlineStatus import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.utils.log +import net.mamoe.mirai.utils.solveCaptcha import kotlin.coroutines.CoroutineContext /** @@ -35,7 +38,9 @@ import kotlin.coroutines.CoroutineContext internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : BotNetworkHandler, PacketHandlerList() { override val coroutineContext: CoroutineContext = - Dispatchers.Default + CoroutineExceptionHandler { _, e -> bot.logger.log(e) } + SupervisorJob() + Dispatchers.Default + CoroutineExceptionHandler { _, e -> + bot.logger.log(e) + } + SupervisorJob() override lateinit var socket: BotSocketAdapter @@ -56,13 +61,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : override suspend fun login(configuration: BotNetworkConfiguration): LoginResult = withContext(this.coroutineContext) { TIMProtocol.SERVER_IP.forEach { ip -> - bot.logger.logInfo("Connecting server $ip") + bot.logger.logPurple("Connecting server $ip") socket = BotSocketAdapter(ip, configuration) loginResult = CompletableDeferred() socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it } + println() bot.logger.logPurple("Timeout. Retrying next server") socket.close() @@ -124,6 +130,9 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : try { channel.read(buffer)// JVM: withContext(IO) + } catch (e: ClosedChannelException) { + close() + return } catch (e: ReadPacketInternalException) { bot.logger.logError("Socket channel read failed: ${e.message}") continue @@ -133,6 +142,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : continue } finally { if (!buffer.canRead() || buffer.readRemaining == 0) {//size==0 + //bot.logger.logDebug("processReceive: Buffer cannot be read") buffer.release(IoBuffer.Pool) continue }// sometimes exceptions are thrown without this `if` clause @@ -153,7 +163,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : } } - internal suspend fun resendTouch(): LoginResult = coroutineScope { + internal suspend fun resendTouch(): LoginResult /* = coroutineScope */ { if (::loginHandler.isInitialized) loginHandler.close() loginHandler = LoginHandler(configuration) @@ -168,7 +178,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : } sendPacket(TouchPacket(bot.qqAccount, serverIp)) - return@coroutineScope loginResult.await() + return loginResult.await() } private suspend inline fun expectPacket(): CompletableDeferred

{ @@ -183,7 +193,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : return receiving } - override suspend fun distributePacket(packet: ServerPacket) = coroutineScope { + override suspend fun distributePacket(packet: ServerPacket) { try { packet.decode() } catch (e: Exception) { @@ -213,7 +223,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : } if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled) { - return@coroutineScope + return } // They should be called in sequence because packet is lock-free @@ -267,11 +277,12 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : return@withContext } - packet.packet.use { build -> + packet.buildAndUse { build -> val buffer = IoBuffer.Pool.borrow() try { build.readAvailable(buffer) - channel.send(buffer)//JVM: withContext(IO) + val shouldBeSent = buffer.readRemaining + check(channel.send(buffer) == shouldBeSent) { "Buffer is not entirely sent. Required sent length=$shouldBeSent, but after channel.send, buffer remains ${buffer.readBytes().toUHexString()}" }//JVM: withContext(IO) } catch (e: SendPacketInternalException) { bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}") bot.reinitializeNetworkHandler(configuration, e) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt index 375d70f9c..85bc18a1c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt @@ -30,33 +30,22 @@ abstract class OutgoingPacket : Packet(), Closeable { internal fun atomicNextSequenceId() = sequenceIdInternal.getAndIncrement().toUShort() } - /** - * 务必 [ByteReadPacket.close] 或 [close] 或使用 [Closeable.use] - */ - var packet: ByteReadPacket = Uninitialized - get() { - if (field === Uninitialized) build() - return field - } - private set + inline fun buildAndUse(block: (ByteReadPacket) -> Unit) { + buildPacket().use(block) + } - private fun build(): ByteReadPacket { - packet = buildPacket { - writeHex(TIMProtocol.head) - writeHex(TIMProtocol.ver) - writePacketId() - encode(this) - writeHex(TIMProtocol.tail) - } - return packet + @PublishedApi + internal fun buildPacket(): ByteReadPacket = buildPacket { + writeHex(TIMProtocol.head) + writeHex(TIMProtocol.ver) + writePacketId() + encode(this) + writeHex(TIMProtocol.tail) } override fun toString(): String = packetToString() override fun close() { - if (this.packet !== Uninitialized) { - this.packet.close() - } } private fun BytePacketBuilder.writePacketId() { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt index 824753666..ad99ba372 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt @@ -2,7 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.packet -import net.mamoe.mirai.utils.toUHexString +import net.mamoe.mirai.utils.io.toUHexString /** * 数据包. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/ServerPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/ServerPacket.kt index 98f737240..c17371a52 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/ServerPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/ServerPacket.kt @@ -6,11 +6,11 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable import kotlinx.io.core.IoBuffer import kotlinx.io.core.readBytes -import net.mamoe.mirai.utils.TEA -import net.mamoe.mirai.utils.hexToBytes -import net.mamoe.mirai.utils.io.cutTail +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.decryptBy +import net.mamoe.mirai.utils.io.ByteArrayPool +import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.parseServerPacket -import net.mamoe.mirai.utils.io.readRemainingBytes import net.mamoe.mirai.utils.io.toReadPacket import kotlin.properties.Delegates @@ -41,16 +41,32 @@ fun S.applySequence(sequenceId: UShort): S { return this } -fun ServerPacket.decryptBy(key: ByteArray): ByteReadPacket = ByteReadPacket(decryptAsByteArray(key)) -fun ServerPacket.decryptBy(key: IoBuffer): ByteReadPacket = ByteReadPacket(decryptAsByteArray(key)) +fun ServerPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } +fun ServerPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } fun ServerPacket.decryptBy(keyHex: String): ByteReadPacket = this.decryptBy(keyHex.hexToBytes()) -fun ServerPacket.decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket = TEA.decrypt(this.decryptAsByteArray(key1), key2).toReadPacket() +fun ServerPacket.decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket = + this.decryptAsByteArray(key1) { data -> + data.decryptBy(key2).toReadPacket() + } + + fun ServerPacket.decryptBy(key1: String, key2: ByteArray): ByteReadPacket = this.decryptBy(key1.hexToBytes(), key2) fun ServerPacket.decryptBy(key1: String, key2: IoBuffer): ByteReadPacket = this.decryptBy(key1.hexToBytes(), key2.readBytes()) fun ServerPacket.decryptBy(key1: ByteArray, key2: String): ByteReadPacket = this.decryptBy(key1, key2.hexToBytes()) fun ServerPacket.decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket = this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes()) -fun ServerPacket.decryptAsByteArray(key: ByteArray): ByteArray = TEA.decrypt(input.readRemainingBytes().cutTail(1), key) -fun ServerPacket.decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes()) -fun ServerPacket.decryptAsByteArray(buffer: IoBuffer): ByteArray = this.decryptAsByteArray(buffer.readBytes()) \ No newline at end of file +inline fun ServerPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R = + ByteArrayPool.useInstance { + val length = input.remaining.toInt() - 1 + input.readFully(it, 0, length) + consumer(it.decryptBy(key, length)) + }.also { input.close() } + +inline fun ServerPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R = this.decryptAsByteArray(keyHex.hexToBytes(), consumer) +inline fun ServerPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R = + ByteArrayPool.useInstance { + val length = input.remaining.toInt() - 1 + input.readFully(it, 0, length) + consumer(it.decryptBy(key, length)) + }.also { input.close() } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt index 038525bb5..7a1ab091a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt @@ -10,7 +10,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.applySequence import net.mamoe.mirai.network.protocol.tim.packet.decryptBy import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.toByteArray /** * 事件的识别 ID. 在 [事件确认包][ServerEventPacket.ResponsePacket] 中被使用. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt index 82dcca8ba..98af480b8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt @@ -84,8 +84,8 @@ class SubmitCaptchaPacket( */ @PacketId(0x00_BAu) class OutgoingCaptchaRefreshPacket( - private val qq: UInt, - private val token0825: ByteArray + private val qq: UInt, + private val token0825: ByteArray ) : OutgoingPacket() { override fun encode(builder: BytePacketBuilder) = with(builder) { this.writeQQ(qq) @@ -164,14 +164,14 @@ abstract class ServerCaptchaPacket(input: ByteReadPacket) : ServerPacket(input) @PacketId(0x00_BAu) class Encrypted(input: ByteReadPacket) : ServerPacket(input) { fun decrypt(): ServerCaptchaPacket { - val data = this.decryptAsByteArray(TIMProtocol.key00BA) - - return when (data.size) { - 66, - 95 -> CaptchaCorrectPacket(data.toReadPacket()) - //66 -> ServerCaptchaUnknownPacket(data.toReadPacket()) - else -> CaptchaTransmissionResponsePacket(data.toReadPacket()) - }.applySequence(sequenceId) + return this.decryptAsByteArray(TIMProtocol.key00BA) { data -> + when (data.size) { + 66, + 95 -> CaptchaCorrectPacket(data.toReadPacket()) + //66 -> ServerCaptchaUnknownPacket(data.toReadPacket()) + else -> CaptchaTransmissionResponsePacket(data.toReadPacket()) + }.applySequence(sequenceId) + } } } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt index c78e8897f..ab5c5e2f1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt @@ -8,8 +8,7 @@ import kotlinx.io.core.writeFully import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.PacketId -import net.mamoe.mirai.utils.TEA -import net.mamoe.mirai.utils.hexToBytes +import net.mamoe.mirai.utils.encryptBy import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.writeCRC32 @@ -90,7 +89,7 @@ private fun BytePacketBuilder.writePart1( this.writeHex(TIMProtocol.passwordSubmissionTLV2) this.writeHex("00 1A")//tag this.writeHex("00 40")//length - this.writeFully(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey)) + this.writeFully(TIMProtocol.passwordSubmissionTLV2.hexToBytes().encryptBy(privateKey)) this.writeHex(TIMProtocol.constantData1) this.writeHex(TIMProtocol.constantData2) this.writeQQ(qq) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt index 0045d7fc7..a1cd783ab 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt @@ -4,12 +4,14 @@ package net.mamoe.mirai.network.protocol.tim.packet.login import kotlinx.io.core.* import net.mamoe.mirai.network.protocol.tim.TIMProtocol -import net.mamoe.mirai.network.protocol.tim.packet.* +import net.mamoe.mirai.network.protocol.tim.packet.PacketId +import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket +import net.mamoe.mirai.network.protocol.tim.packet.applySequence +import net.mamoe.mirai.network.protocol.tim.packet.decryptBy import net.mamoe.mirai.utils.Tested import net.mamoe.mirai.utils.io.readBoolean import net.mamoe.mirai.utils.io.readIoBuffer import net.mamoe.mirai.utils.io.readString -import net.mamoe.mirai.utils.io.toReadPacket import kotlin.properties.Delegates @PacketId(0x08_36u) @@ -82,18 +84,20 @@ class LoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePac discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6 - discardExact(when (readUByte().toUInt()) { - 0x00u -> when (readUByte().toUInt()) { - 0x33u -> 28 + discardExact( + when (readUByte().toUInt()) { + 0x00u -> when (readUByte().toUInt()) { + 0x33u -> 28 + else -> null + } + 0x01u -> when (readUByte().toUInt()) { + 0x07u -> 0 + 0x10u -> 64 + else -> null + } else -> null - } - 0x01u -> when (readUByte().toUInt()) { - 0x07u -> 0 - 0x10u -> 64 - else -> null - } - else -> null - } ?: error("Unknown length flag")) + } ?: error("Unknown length flag") + ) discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00 @@ -156,6 +160,6 @@ class LoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginRespons @PacketId(0x08_36u) class Encrypted(input: ByteReadPacket) : ServerPacket(input) { - fun decrypt(): LoginResponseCaptchaInitPacket = LoginResponseCaptchaInitPacket(decryptAsByteArray(TIMProtocol.shareKey).toReadPacket()).applySequence(sequenceId) + fun decrypt(): LoginResponseCaptchaInitPacket = LoginResponseCaptchaInitPacket(decryptBy(TIMProtocol.shareKey)).applySequence(sequenceId) } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt index e2fbaf2c0..3bf27e058 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt @@ -8,9 +8,7 @@ import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.packet.* -import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.toUHexString /** * The packet received when logging in, used to redirect server address diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ByteArrayPool.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ByteArrayPool.kt deleted file mode 100644 index 41b616e53..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ByteArrayPool.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.mamoe.mirai.utils - -import kotlinx.io.pool.DefaultPool -import kotlinx.io.pool.ObjectPool - -internal const val DEFAULT_BUFFER_SIZE = 4098 -internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 2048 - -/** - * The default ktor byte buffer pool - */ -val ByteArrayPool: ObjectPool = ByteBufferPool() - -class ByteBufferPool : DefaultPool(DEFAULT_BYTE_ARRAY_POOL_SIZE) { - override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BUFFER_SIZE) - - override fun clearInstance(instance: ByteArray): ByteArray = instance.apply { map { 0 } } -} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt index 61d6d0374..d3de2baa6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt @@ -3,6 +3,7 @@ package net.mamoe.mirai.utils import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.toByteArray import kotlinx.io.core.writeFully +import net.mamoe.mirai.utils.io.getRandomByteArray private const val GTK_BASE_VALUE: Int = 5381 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index a2e2a5fe1..40f5be795 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -12,6 +12,7 @@ import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.image import net.mamoe.mirai.message.sendTo import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage +import net.mamoe.mirai.utils.io.toUHexString @Suppress("FunctionName") fun ExternalImage( diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt index 51b363237..f6b0dfdb6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt @@ -17,7 +17,7 @@ interface MiraiLogger { */ companion object : MiraiLogger by DefaultLogger("TOP Level") - var identity: String? + val identity: String? /** * 随从. 在 this 中调用所有方法后都应继续往 [follower] 传递调用. @@ -74,7 +74,8 @@ interface MiraiLogger { /** * 平台基类. - * 实现了 [follower] 的调用传递 + * 实现了 [follower] 的调用传递. + * 若要自行实现日志记录, 请优先考虑继承 [PlatformLogger] */ abstract class MiraiLoggerPlatformBase : MiraiLogger { final override var follower: MiraiLogger? = null @@ -145,7 +146,7 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger { /** * 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, 如 [Bot], 都会通过这个函数构造日志记录器 */ -var DefaultLogger: (identity: String?) -> PlatformLogger = { PlatformLogger() } +var DefaultLogger: (identity: String?) -> MiraiLogger = { PlatformLogger() } /** * 当前平台的默认的日志记录器. @@ -153,7 +154,39 @@ var DefaultLogger: (identity: String?) -> PlatformLogger = { PlatformLogger() } * * 不应该直接构造这个类的实例. 需使用 [DefaultLogger] */ -expect class PlatformLogger @JvmOverloads internal constructor(identity: String? = null) : MiraiLoggerPlatformBase +expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = null) : MiraiLoggerPlatformBase + +/** + * 不作任何事情的 logger + */ +@Suppress("unused") +object NoLogger : PlatformLogger() { + override val identity: String? = null + + override fun log0(e: Throwable) { + } + + override fun log0(any: Any?) { + } + + override fun logError0(any: Any?) { + } + + override fun logDebug0(any: Any?) { + } + + override fun logCyan0(any: Any?) { + } + + override fun logPurple0(any: Any?) { + } + + override fun logGreen0(any: Any?) { + } + + override fun logBlue0(any: Any?) { + } +} /** * 在顶层日志记录这个异常 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt index deba2b168..698d2eda9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt @@ -22,6 +22,8 @@ enum class OnlineStatus( BUSY(0x32u); + // TODO: 2019/10/29 what is 0x20u + companion object { fun ofId(id: UByte): OnlineStatus? = values().firstOrNull { it.id == id } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt index bb01018cf..e43d59f7f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt @@ -1,30 +1,367 @@ package net.mamoe.mirai.utils +import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.IoBuffer -import kotlinx.io.core.readBytes -import net.mamoe.mirai.utils.io.toUHexString +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.io.* +import kotlin.experimental.and +import kotlin.experimental.xor import kotlin.jvm.JvmStatic +import kotlin.random.Random -internal expect object TEA { //TODO 优化为 buffer - internal fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray - @JvmStatic - fun encrypt(source: ByteArray, key: ByteArray): ByteArray +/** + * 解密错误 + */ +class DecryptionFailedException : Exception() - @JvmStatic - fun decrypt(source: ByteArray, key: ByteArray): ByteArray + +// region encrypt + +/** + * 使用 [key] 解密 [this] + * + * @param key 长度至少为 16 + * @throws DecryptionFailedException 解密错误时 + */ +fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length) + +/** + * 通过 [String.hexToBytes] 将 [keyHex] 转为 [ByteArray] 后用它解密 [this]. + * 将会使用 [HexCache] + * + * @param keyHex 长度至少为 16 bytes + * @throws DecryptionFailedException 解密错误时 + */ +fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = encryptBy(keyHex.hexToBytes(withCache = true), length = length) + +/** + * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密. + * + * @param key 长度至少为 16 + * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程 + * @throws DecryptionFailedException 解密错误时 + */ +inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) { + ByteArrayPool.useInstance { + this.readFully(it, offset, length) + consumer(it.encryptBy(key, length = length)) + } } -fun ByteArray.decryptBy(key: ByteArray): ByteArray = TEA.decrypt(checkLength(), key) -fun ByteArray.decryptBy(key: IoBuffer): ByteArray = TEA.decrypt(checkLength(), key.readBytes()) -fun ByteArray.decryptBy(keyHex: String): ByteArray = TEA.decrypt(checkLength(), keyHex.hexToBytes()) +// endregion -fun ByteArray.encryptBy(key: ByteArray): ByteArray = TEA.encrypt(checkLength(), key) -fun ByteArray.encryptBy(keyHex: String): ByteArray = TEA.encrypt(checkLength(), keyHex.hexToBytes()) -private fun ByteArray.checkLength(): ByteArray { - size.let { - require(it % 8 == 0 && it >= 16) { "data must len % 8 == 0 && len >= 16 but given (length=$it) ${this.toUHexString()}" } +// region decrypt + +/** + * 使用 [key] 解密 [this]. + * + * @param key 固定长度 16 + * @throws DecryptionFailedException 解密错误时 + */ +fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length) + +/** + * 使用 [key] 解密 [this]. + * [key] 将会被读取掉前 16 个字节 + * 将会使用 [ByteArrayPool] 来缓存 [key]. + * + * @param key 长度至少为 16 + * @throws DecryptionFailedException 解密错误时 + */ +fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray { + checkDataLengthAndReturnSelf(length) + return ByteArrayPool.useInstance { keyBuffer -> + key.readFully(keyBuffer, 0, key.readRemaining) + TEA.decrypt(this, keyBuffer, sourceLength = length) } +} + +/** + * 通过 [String.hexToBytes] 将 [keyHex] 转为 [ByteArray] 后用它解密 [this] + * 将会使用 [HexCache] + * + * @param keyHex 长度至少为 16 bytes + * @throws DecryptionFailedException 解密错误时 + */ +fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = decryptBy(keyHex.hexToBytes(withCache = true), length = length) + +/** + * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 解密. + * + * @param key 长度至少为 16 + * @throws DecryptionFailedException 解密错误时 + */ +fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray { + return ByteArrayPool.useInstance { + this.readFully(it, offset, length) + it.checkDataLengthAndReturnSelf(length) + TEA.decrypt(it, key, length) + } +} + +/** + * 在 [ByteArrayPool] 缓存 [this], 然后使用 [keyHex] 解密. + * + * @param keyHex 长度至少为 16 + * @throws DecryptionFailedException 解密错误时 + */ +fun IoBuffer.decryptBy(keyHex: String, offset: Int = 0, length: Int = readRemaining - offset): ByteArray = + decryptBy(keyHex.hexToBytes(withCache = true), offset = offset, length = length) + + +// endregion + +private object TEA { + private const val UINT32_MASK = 0xffffffffL + + private fun doOption(data: ByteArray, key: ByteArray, length: Int, encrypt: Boolean): ByteArray { + lateinit var mOutput: ByteArray + lateinit var mInBlock: ByteArray + var mIndexPos: Int + lateinit var mIV: ByteArray + var mOutPos = 0 + var mPreOutPos = 0 + var isFirstBlock = true + + val mKey = LongArray(4) + + for (i in 0..3) { + mKey[i] = key.pack(i * 4, 4) + } + + fun rand(): Int = Random.Default.nextInt() + + + fun encode(bytes: ByteArray): ByteArray { + var v0 = bytes.pack(0, 4) + var v1 = bytes.pack(4, 4) + var sum: Long = 0 + val delta = 0x9e3779b9L + for (i in 0..15) { + sum = sum + delta and UINT32_MASK + v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] + v0 = v0 and UINT32_MASK + v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] + v1 = v1 and UINT32_MASK + } + + return v0.toInt().toByteArray() + v1.toInt().toByteArray() + } + + fun decode(bytes: ByteArray, offset: Int): ByteArray { + var v0 = bytes.pack(offset, 4) + var v1 = bytes.pack(offset + 4, 4) + val delta = 0x9e3779b9L + var sum = delta shl 4 and UINT32_MASK + for (i in 0..15) { + v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] + v1 = v1 and UINT32_MASK + v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] + v0 = v0 and UINT32_MASK + sum = sum - delta and UINT32_MASK + } + return v0.toInt().toByteArray() + v1.toInt().toByteArray() + } + + fun encodeOneBlock() { + mIndexPos = 0 + while (mIndexPos < 8) { + mInBlock[mIndexPos] = if (isFirstBlock) + mInBlock[mIndexPos] + else + (mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos]) + mIndexPos++ + } + + encode(mInBlock).copyInto(mOutput, mOutPos, 0, 8) + mIndexPos = 0 + while (mIndexPos < 8) { + val outPos = mOutPos + mIndexPos + mOutput[outPos] = (mOutput[outPos] xor mIV[mIndexPos]) + mIndexPos++ + } + mInBlock.copyInto(mIV, 0, 0, 8) + mPreOutPos = mOutPos + mOutPos += 8 + mIndexPos = 0 + isFirstBlock = false + } + + fun decodeOneBlock(ciphertext: ByteArray, offset: Int, len: Int): Boolean { + mIndexPos = 0 + while (mIndexPos < 8) { + if (mOutPos + mIndexPos < len) { + mIV[mIndexPos] = (mIV[mIndexPos] xor ciphertext[mOutPos + offset + mIndexPos]) + mIndexPos++ + continue + } + return true + } + + mIV = decode(mIV, 0) + mOutPos += 8 + mIndexPos = 0 + return true + + } + + @Suppress("NAME_SHADOWING") + fun encrypt(plaintext: ByteArray, offset: Int, len: Int): ByteArray { + var len = len + var offset = offset + mInBlock = ByteArray(8) + mIV = ByteArray(8) + mOutPos = 0 + mPreOutPos = 0 + isFirstBlock = true + mIndexPos = (len + 10) % 8 + if (mIndexPos != 0) { + mIndexPos = 8 - mIndexPos + } + mOutput = ByteArray(mIndexPos + len + 10) + mInBlock[0] = (rand() and 0xf8 or mIndexPos).toByte() + for (i in 1..mIndexPos) { + mInBlock[i] = (rand() and 0xff).toByte() + } + ++mIndexPos + for (i in 0..7) { + mIV[i] = 0 + } + + var g = 0 + while (g < 2) { + if (mIndexPos < 8) { + mInBlock[mIndexPos++] = (rand() and 0xff).toByte() + ++g + } + if (mIndexPos == 8) { + encodeOneBlock() + } + } + + while (len > 0) { + if (mIndexPos < 8) { + mInBlock[mIndexPos++] = plaintext[offset++] + } + if (mIndexPos == 8) { + encodeOneBlock() + } + len-- + } + g = 0 + while (g < 7) { + if (mIndexPos < 8) { + mInBlock[mIndexPos++] = 0.toByte() + } + if (mIndexPos == 8) { + encodeOneBlock() + } + g++ + } + return mOutput + } + + fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray { + require(!(len % 8 != 0 || len < 16)) { "data must len % 8 == 0 && len >= 16 but given $len" } + mIV = decode(cipherText, offset) + mIndexPos = (mIV[0] and 7).toInt() + var plen = len - mIndexPos - 10 + isFirstBlock = true + if (plen < 0) { + fail() + } + mOutput = ByteArray(plen) + mPreOutPos = 0 + mOutPos = 8 + ++mIndexPos + var g = 0 + while (g < 2) { + if (mIndexPos < 8) { + ++mIndexPos + ++g + } + if (mIndexPos == 8) { + isFirstBlock = false + if (!decodeOneBlock(cipherText, offset, len)) { + fail() + } + } + } + + var outpos = 0 + while (plen != 0) { + if (mIndexPos < 8) { + mOutput[outpos++] = if (isFirstBlock) + mIV[mIndexPos] + else + (cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos]) + ++mIndexPos + } + if (mIndexPos == 8) { + mPreOutPos = mOutPos - 8 + isFirstBlock = false + if (!decodeOneBlock(cipherText, offset, len)) { + fail() + } + } + plen-- + } + g = 0 + while (g < 7) { + if (mIndexPos < 8) { + if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) { + fail() + } else { + ++mIndexPos + } + } + + if (mIndexPos == 8) { + mPreOutPos = mOutPos + if (!decodeOneBlock(cipherText, offset, len)) { + fail() + } + } + g++ + } + return mOutput + } + + return if (encrypt) { + encrypt(data, 0, length) + } else { + decrypt(data, 0, length) + } + } + + private fun fail(): Nothing = throw DecryptionFailedException() + + @PublishedApi + @JvmStatic + internal fun encrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray = doOption(source, key, sourceLength, true) + + @PublishedApi + @JvmStatic + internal fun decrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray = doOption(source, key, sourceLength, false) + + private fun ByteArray.pack(offset: Int, len: Int): Long { + var result: Long = 0 + val maxOffset = if (len > 8) offset + 8 else offset + len + for (index in offset until maxOffset) { + result = result shl 8 or (this[index].toLong() and 0xffL) + } + return result shr 32 or (result and UINT32_MASK) + } +} + +private fun ByteArray.checkDataLengthAndReturnSelf(length: Int = this.size): ByteArray { + require(length % 8 == 0 && length >= 16) { "data must len % 8 == 0 && len >= 16 but given (length=$length) ${this.toUHexString()}" } return this +} + + +fun main() { + println("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D".hexToBytes().encryptBy("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D").decryptBy("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D").toUHexString() == "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D") } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt index b1e9093b7..204be1787 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt @@ -12,6 +12,16 @@ internal fun Long.coerceAtLeastOrFail(value: Long): Long { return this } +@PublishedApi +internal fun Int.coerceAtMostOrFail(maximumValue: Int): Int = + if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue") + else this + +@PublishedApi +internal fun Long.coerceAtMostOrFail(maximumValue: Long): Long = + if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue") + else this + /** * 表示这个参数必须为正数 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt new file mode 100644 index 000000000..6bbba12d2 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt @@ -0,0 +1,16 @@ +package net.mamoe.mirai.utils.io + +import kotlinx.io.pool.DefaultPool +import kotlinx.io.pool.ObjectPool + +internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 128 +internal const val DEFAULT_BYTE_ARRAY_SIZE = 2048 + +val ByteArrayPool: ObjectPool = ByteArrayPoolImpl + +private object ByteArrayPoolImpl : DefaultPool(DEFAULT_BYTE_ARRAY_POOL_SIZE) { + override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE) + + override fun clearInstance(instance: ByteArray): ByteArray = instance +} + diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt index 4bea97013..07870aae6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt @@ -33,7 +33,7 @@ fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString return@joinToString ret } -fun ByteArray.toReadPacket() = ByteReadPacket(this) +fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size) = ByteReadPacket(this, offset = offset, length = length) fun ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt index a12553c1c..8d6001535 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt @@ -3,8 +3,6 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.* import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.hexToBytes -import net.mamoe.mirai.utils.toIoBuffer internal object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug") @@ -78,5 +76,8 @@ internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstCons println() } +/** + * TODO 这两个方法不应该 MPP + */ expect fun printCompareHex(hex1s: String, hex2s: String): String expect fun String.printColorize(ignoreUntilFirstConst: Boolean = false): String diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt index 2d7667efe..e1e6974a2 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt @@ -84,7 +84,7 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket { 0x00_58u -> ResponsePacket.Encrypted(this) 0x03_88u -> ResponsePacket.Encrypted(this) 0x03_52u -> ResponsePacket.Encrypted(this) - 0x01_BDu -> ResponsePacket.Encrypted(this) + // 0x01_BDu -> ResponsePacket.Encrypted(this) else -> UnknownServerPacket.Encrypted(this, id, sequenceId) }.applySequence(sequenceId) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt index bf7089f1c..b5c19ace1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt @@ -3,14 +3,15 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.* +import kotlinx.io.pool.useInstance import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.internal.coerceAtMostOrFail import kotlin.random.Random import kotlin.random.nextInt - fun BytePacketBuilder.writeZero(count: Int) = repeat(count) { this.writeByte(0) } fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) } @@ -26,28 +27,25 @@ fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) { } -// will box, but it doesn't matter -private fun > N.coerceAtMostOrFail(maximumValue: N): N = - if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue") - else this - -fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = with(BytePacketBuilder().apply(builder).build()) { - if (tag != null) { - writeUByte(tag) +fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = + with(BytePacketBuilder().apply(builder).build()) { + if (tag != null) { + writeUByte(tag) + } + writeUShort((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL).toUShort()) + writePacket(this) + this.release() } - writeUShort((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL).toUShort()) - writePacket(this) - this.release() -} -fun BytePacketBuilder.writeUVarintLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = with(BytePacketBuilder().apply(builder).build()) { - if (tag != null) { - writeUByte(tag) +fun BytePacketBuilder.writeUVarintLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = + with(BytePacketBuilder().apply(builder).build()) { + if (tag != null) { + writeUByte(tag) + } + writeUVarInt((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL)) + writePacket(this) + this.release() } - writeUVarInt((lengthOffset?.invoke(remaining) ?: remaining).coerceAtMostOrFail(0xFFFFL)) - writePacket(this) - this.release() -} @Suppress("DEPRECATION") fun BytePacketBuilder.writeShortLVString(str: String) = this.writeShortLVByteArray(str.toByteArray()) @@ -100,10 +98,17 @@ fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) { this.writeFully(value) } -fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.readBytes(), encoder) -fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = writeFully(TEA.encrypt(BytePacketBuilder().apply(encoder).use { - it.build().readBytes() -}, key)) +/** + * 会使用 [ByteArrayPool] 缓存 + */ +fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = + BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) } + +fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance { + key.readFully(it, 0, key.readRemaining) + encryptAndWrite(it, encoder) +} + fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder) fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt index f7b9b1358..30e4d7745 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt @@ -9,8 +9,6 @@ import kotlinx.io.errors.IOException */ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable { - // TODO: 2019/10/27 使用 Ktor 的 socket - suspend fun read(buffer: IoBuffer): Int suspend fun send(buffer: IoBuffer): Int @@ -25,9 +23,9 @@ expect class ClosedChannelException : IOException /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ -expect class SendPacketInternalException(cause: Throwable?) : IOException +class SendPacketInternalException(cause: Throwable?) : Exception(cause) /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ -expect class ReadPacketInternalException(cause: Throwable?) : IOException \ No newline at end of file +class ReadPacketInternalException(cause: Throwable?) : Exception(cause) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConvertion.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConvertion.kt index 8e00b867e..67f640572 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConvertion.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConvertion.kt @@ -1,22 +1,28 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused") -package net.mamoe.mirai.utils +package net.mamoe.mirai.utils.io import kotlinx.io.core.IoBuffer import kotlinx.io.core.writeFully -import net.mamoe.mirai.utils.io.toUHexString +import kotlinx.io.pool.ObjectPool import kotlin.jvm.Synchronized import kotlin.random.Random import kotlin.random.nextInt + +/* + * 类型转换 Utils. + * 这些函数为内部函数, 可能会改变 + */ + /** * 255 -> 00 00 00 FF */ fun Int.toByteArray(): ByteArray = byteArrayOf( - (ushr(24) and 0xFF).toByte(), - (ushr(16) and 0xFF).toByte(), - (ushr(8) and 0xFF).toByte(), - (ushr(0) and 0xFF).toByte() + (shr(24) and 0xFF).toByte(), + (shr(16) and 0xFF).toByte(), + (shr(8) and 0xFF).toByte(), + (shr(0) and 0xFF).toByte() ) /** @@ -24,8 +30,8 @@ fun Int.toByteArray(): ByteArray = byteArrayOf( */ fun UShort.toByteArray(): ByteArray = with(toUInt()) { byteArrayOf( - (shr(8) and 255u).toByte(), - (shr(0) and 255u).toByte() + (shr(8) and 255u).toByte(), + (shr(0) and 255u).toByte() ) } @@ -33,32 +39,80 @@ fun UShort.toByteArray(): ByteArray = with(toUInt()) { * 255u -> 00 00 00 FF */ fun UInt.toByteArray(): ByteArray = byteArrayOf( - (shr(24) and 255u).toByte(), - (shr(16) and 255u).toByte(), - (shr(8) and 255u).toByte(), - (shr(0) and 255u).toByte() + (shr(24) and 255u).toByte(), + (shr(16) and 255u).toByte(), + (shr(8) and 255u).toByte(), + (shr(0) and 255u).toByte() ) +/** + * 转 [ByteArray] 后再转 hex + */ fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) + +/** + * 转无符号十六进制表示, 并补充首位 `0`. + * 转换结果示例: `FF`, `0E` + */ fun Byte.toUHexString(): String = this.toUByte().toString(16).toUpperCase().let { if (it.length == 1) "0$it" else it } -fun String.hexToBytes(): ByteArray = HexCache.hexToBytes(this) +/** + * 将无符号 Hex 转为 [ByteArray], 有根据 hex 的 [hashCode] 建立的缓存. + */ +fun String.hexToBytes(withCache: Boolean = true): ByteArray = + if (withCache) HexCache.getCacheOrConvert(this) + else this.split(" ") + .filterNot { it.isEmpty() } + .map { s -> s.toUByte(16).toByte() } + .toByteArray() /** - * 将 Hex 转为 UByteArray, 有根据 hex 的 [hashCode] 建立的缓存. + * 将无符号 Hex 转为 [UByteArray], 有根据 hex 的 [hashCode] 建立的缓存. */ -fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this) +fun String.hexToUBytes(withCache: Boolean = true): UByteArray = + if (withCache) HexCache.getUCacheOrConvert(this) + else this.split(" ") + .filterNot { it.isEmpty() } + .map { s -> s.toUByte(16) } + .toUByteArray() -fun String.hexToInt(): Int = hexToBytes().toUInt().toInt() +/** + * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] + */ fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } + +/** + * 随机生成长度为 [length] 的 [String]. + */ fun getRandomString(length: Int): String = getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9') + +/** + * 根据所给 [charRange] 随机生成长度为 [length] 的 [String]. + */ fun getRandomString(length: Int, charRange: CharRange): String = String(CharArray(length) { charRange.random() }) + +/** + * 根据所给 [charRanges] 随机生成长度为 [length] 的 [String]. + */ fun getRandomString(length: Int, vararg charRanges: CharRange): String = String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }) + +/** + * 将 [this] 前 4 个 [Byte] 的 bits 合并为一个 [Int] + * + * 详细解释: + * 一个 [Byte] 有 8 bits + * 一个 [Int] 有 32 bits + * 本函数将 4 个 [Byte] 的 bits 连接得到 [Int] + */ fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0) +/** + * 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入. + * 注意回收 ([ObjectPool.recycle]) + */ fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it } /** @@ -69,20 +123,28 @@ internal object HexCache { private val hexToByteArrayCacheMap: MutableMap = mutableMapOf() @Synchronized - internal fun hexToBytes(hex: String): ByteArray = hex.hashCode().let { id -> + internal fun getCacheOrConvert(hex: String): ByteArray = hex.hashCode().let { id -> if (hexToByteArrayCacheMap.containsKey(id)) { - return hexToByteArrayCacheMap[id]!!.copyOf() + return hexToByteArrayCacheMap[id]!! } else { - hexToUBytes(hex).toByteArray().let { - hexToByteArrayCacheMap[id] = it.copyOf() + hex.hexToBytes(withCache = false).let { + hexToByteArrayCacheMap[id] = it return it } } } - internal fun hexToUBytes(hex: String): UByteArray = - hex.split(" ") - .filterNot { it.isEmpty() } - .map { s -> s.toUByte(16) } - .toUByteArray() + private val hexToUByteArrayCacheMap: MutableMap = mutableMapOf() + + @Synchronized + internal fun getUCacheOrConvert(hex: String): UByteArray = hex.hashCode().let { id -> + if (hexToUByteArrayCacheMap.containsKey(id)) { + return hexToUByteArrayCacheMap[id]!! + } else { + hex.hexToUBytes(withCache = false).let { + hexToUByteArrayCacheMap[id] = it + return it + } + } + } } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt index 39ba9ae54..9c41dd28d 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/packet/PacketInternalJvm.kt @@ -55,6 +55,9 @@ private object IgnoreIdListInclude : List by listOf( "RefVolatile" ) +/** + * 这个方法会翻倍内存占用, 考虑修改. + */ @Suppress("UNCHECKED_CAST") internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields .filterNot { field -> diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt index 536942676..11883e913 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt @@ -9,7 +9,7 @@ actual typealias PlatformLogger = Console * JVM 控制台日志实现 */ open class Console @JvmOverloads internal constructor( - override var identity: String? = null + override val identity: String? = null ) : MiraiLoggerPlatformBase() { override fun logGreen0(any: Any?) = println(any.toString(), LoggerTextFormat.GREEN) override fun logPurple0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_PURPLE) @@ -17,7 +17,7 @@ open class Console @JvmOverloads internal constructor( override fun logCyan0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_CYAN) override fun logError0(any: Any?) = println(any.toString(), LoggerTextFormat.RED) override fun log0(e: Throwable) = e.printStackTrace() - override fun log0(any: Any?) = println(any.toString())//kotlin println + override fun log0(any: Any?) = println(any.toString(), LoggerTextFormat.WHITE) override fun logDebug0(any: Any?) { if (DEBUGGING) { println(any.toString(), LoggerTextFormat.YELLOW) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/TEA.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/TEA.kt deleted file mode 100644 index e45dd016d..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/TEA.kt +++ /dev/null @@ -1,260 +0,0 @@ -package net.mamoe.mirai.utils - -import java.nio.ByteBuffer -import java.util.* -import kotlin.experimental.and -import kotlin.experimental.xor - -/** - * TEA 加密 - * - * @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java - */ -internal actual object TEA { - private const val UINT32_MASK = 0xffffffffL - - internal actual fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray { - val mRandom = Random() - lateinit var mOutput: ByteArray - lateinit var mInBlock: ByteArray - var mIndexPos: Int - lateinit var mIV: ByteArray - var mOutPos = 0 - var mPreOutPos = 0 - var isFirstBlock = true - - val mKey = LongArray(4) - - for (i in 0..3) { - mKey[i] = pack(key, i * 4, 4) - } - - fun rand(): Int { - return mRandom.nextInt() - } - - fun encode(bytes: ByteArray): ByteArray { - var v0 = pack(bytes, 0, 4) - var v1 = pack(bytes, 4, 4) - var sum: Long = 0 - val delta = 0x9e3779b9L - for (i in 0..15) { - sum = sum + delta and UINT32_MASK - v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] - v0 = v0 and UINT32_MASK - v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] - v1 = v1 and UINT32_MASK - } - return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array() - } - - fun decode(bytes: ByteArray, offset: Int): ByteArray { - var v0 = pack(bytes, offset, 4) - var v1 = pack(bytes, offset + 4, 4) - val delta = 0x9e3779b9L - var sum = delta shl 4 and UINT32_MASK - for (i in 0..15) { - v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] - v1 = v1 and UINT32_MASK - v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] - v0 = v0 and UINT32_MASK - sum = sum - delta and UINT32_MASK - } - return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array() - } - - fun encodeOneBlock() { - mIndexPos = 0 - while (mIndexPos < 8) { - mInBlock[mIndexPos] = if (isFirstBlock) - mInBlock[mIndexPos] - else - (mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos]) - mIndexPos++ - } - - System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8) - mIndexPos = 0 - while (mIndexPos < 8) { - val outPos = mOutPos + mIndexPos - mOutput[outPos] = (mOutput[outPos] xor mIV[mIndexPos]) - mIndexPos++ - } - System.arraycopy(mInBlock, 0, mIV, 0, 8) - mPreOutPos = mOutPos - mOutPos += 8 - mIndexPos = 0 - isFirstBlock = false - } - - fun decodeOneBlock(ciphertext: ByteArray, offset: Int, len: Int): Boolean { - mIndexPos = 0 - while (mIndexPos < 8) { - if (mOutPos + mIndexPos < len) { - mIV[mIndexPos] = (mIV[mIndexPos] xor ciphertext[mOutPos + offset + mIndexPos]) - mIndexPos++ - continue - } - return true - } - - mIV = decode(mIV, 0) - mOutPos += 8 - mIndexPos = 0 - return true - - } - - @Suppress("NAME_SHADOWING") - fun encrypt(plaintext: ByteArray, offset: Int, len: Int): ByteArray { - var len = len - var offset = offset - mInBlock = ByteArray(8) - mIV = ByteArray(8) - mOutPos = 0 - mPreOutPos = 0 - isFirstBlock = true - mIndexPos = (len + 10) % 8 - if (mIndexPos != 0) { - mIndexPos = 8 - mIndexPos - } - mOutput = ByteArray(mIndexPos + len + 10) - mInBlock[0] = (rand() and 0xf8 or mIndexPos).toByte() - for (i in 1..mIndexPos) { - mInBlock[i] = (rand() and 0xff).toByte() - } - ++mIndexPos - for (i in 0..7) { - mIV[i] = 0 - } - - var g = 0 - while (g < 2) { - if (mIndexPos < 8) { - mInBlock[mIndexPos++] = (rand() and 0xff).toByte() - ++g - } - if (mIndexPos == 8) { - encodeOneBlock() - } - } - - while (len > 0) { - if (mIndexPos < 8) { - mInBlock[mIndexPos++] = plaintext[offset++] - } - if (mIndexPos == 8) { - encodeOneBlock() - } - len-- - } - g = 0 - while (g < 7) { - if (mIndexPos < 8) { - mInBlock[mIndexPos++] = 0.toByte() - } - if (mIndexPos == 8) { - encodeOneBlock() - } - g++ - } - return mOutput - } - - fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray { - require(!(len % 8 != 0 || len < 16)) { "data must len % 8 == 0 && len >= 16 but given $len" } - mIV = decode(cipherText, offset) - mIndexPos = (mIV[0] and 7).toInt() - var plen = len - mIndexPos - 10 - isFirstBlock = true - if (plen < 0) { - fail() - } - mOutput = ByteArray(plen) - mPreOutPos = 0 - mOutPos = 8 - ++mIndexPos - var g = 0 - while (g < 2) { - if (mIndexPos < 8) { - ++mIndexPos - ++g - } - if (mIndexPos == 8) { - isFirstBlock = false - if (!decodeOneBlock(cipherText, offset, len)) { - fail() - } - } - } - - var outpos = 0 - while (plen != 0) { - if (mIndexPos < 8) { - mOutput[outpos++] = if (isFirstBlock) - mIV[mIndexPos] - else - (cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos]) - ++mIndexPos - } - if (mIndexPos == 8) { - mPreOutPos = mOutPos - 8 - isFirstBlock = false - if (!decodeOneBlock(cipherText, offset, len)) { - fail() - } - } - plen-- - } - g = 0 - while (g < 7) { - if (mIndexPos < 8) { - if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) { - fail() - } else { - ++mIndexPos - } - } - - if (mIndexPos == 8) { - mPreOutPos = mOutPos - if (!decodeOneBlock(cipherText, offset, len)) { - fail() - } - } - g++ - } - return mOutput - } - - return if (encrypt) { - encrypt(data, 0, data.size) - } else { - decrypt(data, 0, data.size) - } - } - - private fun fail(): Nothing = throw DecryptionFailedException() - - @JvmStatic - actual fun encrypt(source: ByteArray, key: ByteArray): ByteArray { - return doOption(source, key, true) - } - - @JvmStatic - actual fun decrypt(source: ByteArray, key: ByteArray): ByteArray { - return doOption(source, key, false) - } - - @Suppress("SameParameterValue") - private fun pack(bytes: ByteArray, offset: Int, len: Int): Long { - var result: Long = 0 - val maxOffset = if (len > 8) offset + 8 else offset + len - for (index in offset until maxOffset) { - result = result shl 8 or (bytes[index].toLong() and 0xffL) - } - return result shr 32 or (result and UINT32_MASK) - } -} - -class DecryptionFailedException : Exception() \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/config/MiraiConfig.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/config/MiraiConfig.kt deleted file mode 100644 index 5e9cf3171..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/config/MiraiConfig.kt +++ /dev/null @@ -1,58 +0,0 @@ -package net.mamoe.mirai.utils.config - -import org.yaml.snakeyaml.DumperOptions -import org.yaml.snakeyaml.Yaml -import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.nio.charset.Charset -import java.util.* - -/** - * YAML-TYPE CONFIG - * Thread SAFE - * - * @author NaturalHG - */ -class MiraiConfig(private val root: File) : MiraiConfigSection(parse(Objects.requireNonNull(root))) { - - @Synchronized - fun save() { - val dumperOptions = DumperOptions() - dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK - val yaml = Yaml(dumperOptions) - val content = yaml.dump(this) - try { - ByteArrayInputStream(content.toByteArray()).transferTo(FileOutputStream(this.root)) - } catch (e: IOException) { - e.printStackTrace() - } - - } - - companion object { - @Suppress("UNCHECKED_CAST") - private fun parse(file: File): MutableMap? { - /* - if (!file.toURI().getPath().contains(MiraiServer.getInstance().getParentFolder().getPath())) { - file = new File(MiraiServer.getInstance().getParentFolder().getPath(), file.getName()); - }*/ - if (!file.exists()) { - try { - if (!file.createNewFile()) { - return linkedMapOf() - } - } catch (e: IOException) { - e.printStackTrace() - return linkedMapOf() - } - - } - val dumperOptions = DumperOptions() - dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK - val yaml = Yaml(dumperOptions) - return yaml.loadAs>(file.readLines(Charset.defaultCharset()).joinToString("\n"), LinkedHashMap::class.java) as MutableMap? - } - } -} diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/config/MiraiConfigSection.java b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/config/MiraiConfigSection.java deleted file mode 100644 index 1e1236c5b..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/config/MiraiConfigSection.java +++ /dev/null @@ -1,179 +0,0 @@ -package net.mamoe.mirai.utils.config; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.function.Supplier; - -/** - * @author NaturalHG - */ -public class MiraiConfigSection extends LinkedHashMap { - - public MiraiConfigSection(){ - super(); - } - - public MiraiConfigSection(Map copyOfMap) { - super(new LinkedHashMap<>(copyOfMap)); - } - - public int getInt(String key){ - return Integer.parseInt(this.get(key).toString()); - } - - public int getIntOrDefault(String key, int defaultV){ - Object result = this.getOrDefault(key, null); - try { - return result == null ? defaultV : Integer.parseInt(result.toString()); - }catch (NumberFormatException ignored){ - return defaultV; - } - } - - public int getIntOrThrow(String key, Callable throwableCallable) throws Throwable { - Object result = this.getOrDefault(key, null); - if(result == null){ - throw throwableCallable.call(); - } - try { - return Integer.parseInt(result.toString()); - }catch (NumberFormatException ignored){ - throw throwableCallable.call(); - } - } - - public double getDouble(String key){ - return Double.parseDouble(this.get(key).toString()); - } - - public double getDoubleOrDefault(String key, double defaultV){ - Object result = this.getOrDefault(key, null); - try { - return result == null ? defaultV : Double.parseDouble(result.toString()); - }catch (NumberFormatException ignored){ - return defaultV; - } - } - - public double getDoubleOrThrow(String key, Callable throwableCallable) throws Throwable { - Object result = this.getOrDefault(key, null); - if(result == null){ - throw throwableCallable.call(); - } - try { - return Double.parseDouble(result.toString()); - }catch (NumberFormatException ignored){ - throw throwableCallable.call(); - } - } - - public float getFloat(String key){ - return Float.parseFloat(this.get(key).toString()); - } - - public float getFloatOrDefault(String key, float defaultV){ - Object result = this.getOrDefault(key, null); - try { - return result == null ? defaultV : Float.parseFloat(result.toString()); - }catch (NumberFormatException ignored){ - return defaultV; - } - } - - public float getFloatOrThrow(String key, Callable throwableCallable) throws Throwable { - Object result = this.getOrDefault(key, null); - if(result == null){ - throw throwableCallable.call(); - } - try { - return Float.parseFloat(result.toString()); - }catch (NumberFormatException ignored){ - throw throwableCallable.call(); - } - } - - public long getLong(String key){ - return Long.parseLong(this.get(key).toString()); - } - - public long getLongOrDefault(String key, long defaultV){ - Object result = this.getOrDefault(key, null); - try { - return result == null ? defaultV : Long.parseLong(result.toString()); - }catch (NumberFormatException ignored){ - return defaultV; - } - } - - public long getLongOrThrow(String key, Callable throwableCallable) throws Throwable { - Object result = this.getOrDefault(key, null); - if(result == null){ - throw throwableCallable.call(); - } - try { - return Long.parseLong(result.toString()); - }catch (NumberFormatException ignored){ - throw throwableCallable.call(); - } - } - - public String getString(String key){ - return String.valueOf(this.get(key)); - } - - public String getStringOrDefault(String key, String defaultV){ - Object result = this.getOrDefault(key, null); - return result==null?defaultV:result.toString(); - } - - public String getStringOrThrow(String key, Supplier exceptionSupplier) throws Throwable { - Object result = this.getOrDefault(key, null); - if(result == null){ - throw exceptionSupplier.get(); - } - return result.toString(); - } - - @Override - public T put(String key, T value) { - return super.put(key, value); - } - - @SuppressWarnings("unchecked") - public MiraiConfigSection getTypedSection(String key){ - var content = this.get(key); - if(content instanceof MiraiConfigSection){ - return (MiraiConfigSection) content; - } - if(content instanceof Map){ - return new MiraiConfigSection<>( - (LinkedHashMap) content - ); - } - return null; - } - - public MiraiConfigSection getSection(String key){ - return this.getTypedSection(key); - } - - @SuppressWarnings("unchecked") - public D getAsOrDefault(String key, D defaultV){ - return (D)this.getOrDefault(key,defaultV); - } - - @SuppressWarnings("unchecked") - public D getAsOrDefault(String key, Supplier supplier) { - D d = (D) this.get(key); - if (d != null) { - return d; - } - return supplier.get(); - } - - @SuppressWarnings("unchecked") - public D getAs(String key) { - return (D) this.get(key); - } -} diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/HexComparator.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/HexComparator.kt index 22424c88f..50a6e8270 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/HexComparator.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/HexComparator.kt @@ -3,7 +3,6 @@ package net.mamoe.mirai.utils.io import net.mamoe.mirai.network.protocol.tim.TIMProtocol -import net.mamoe.mirai.utils.toUHexString import java.lang.reflect.Field import java.util.* import kotlin.math.max diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt index b53d39846..d9d3e4d8e 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt @@ -4,7 +4,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.io.core.Closeable import kotlinx.io.core.IoBuffer -import kotlinx.io.errors.IOException import kotlinx.io.nio.read import java.net.InetSocketAddress import java.nio.channels.DatagramChannel @@ -43,6 +42,56 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException -actual class SendPacketInternalException actual constructor(cause: Throwable?) : IOException(cause) -actual class ReadPacketInternalException actual constructor(cause: Throwable?) : IOException(cause) \ No newline at end of file +/* + +actual class PlatformDatagramChannel actual constructor(serverHost: String, serverPort: Short) : Closeable { + private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt()) + + @KtorExperimentalAPI + val socket = runBlocking { aSocket(ActorSelectorManager(Dispatchers.IO)).tcp() + .connect(remoteAddress = serverAddress) } + + @KtorExperimentalAPI + val readChannel = socket.openReadChannel() + + @KtorExperimentalAPI + val writeChannel = socket.openWriteChannel(true) + + @KtorExperimentalAPI + @Throws(ReadPacketInternalException::class) + actual suspend fun read(buffer: IoBuffer) = + try { + readChannel.readAvailable(buffer) + } catch (e: ClosedChannelException) { + throw e + } catch (e: Throwable) { + throw ReadPacketInternalException(e) + } + + + @KtorExperimentalAPI + @Throws(SendPacketInternalException::class) + actual suspend fun send(buffer: IoBuffer) = + buffer.readDirect { + try { + writeChannel.writeFully(it) + } catch (e: ClosedChannelException) { + throw e + } catch (e: Throwable) { + throw SendPacketInternalException(e) + } + } + + + @KtorExperimentalAPI + @Throws(IOException::class) + override fun close() { + socket.close() + } + + @KtorExperimentalAPI + actual val isOpen: Boolean + get() = socket.isClosed.not() +} + */ \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingListSection.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingListSection.kt deleted file mode 100644 index cd5026303..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingListSection.kt +++ /dev/null @@ -1,50 +0,0 @@ -package net.mamoe.mirai.utils.setting - - -import org.ini4j.Profile -import java.io.IOException -import java.util.* -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.locks.ReentrantLock - - -/** - * @author NaturalHG - */ -class MiraiSettingListSection : Vector(), MiraiSettingSection { - private val lock = ReentrantLock() - - @Suppress("UNCHECKED_CAST") - fun getAs(index: Int): T { - return super.get(index) as T - } - - fun getInt(index: Int): Int { - return this.getAs(index) - } - - fun getDouble(index: Int): Int { - return this.getAs(index) - } - - fun getString(index: Int): Int { - return this.getAs(index) - } - - fun getFloat(index: Int): Int { - return this.getAs(index) - } - - @Synchronized - override fun saveAsSection(section: Profile.Section) { - section.clear() - val integer = AtomicInteger(0) - this.forEach { a -> section.put(integer.getAndAdd(1).toString(), a) } - } - - @Throws(IOException::class) - override fun close() { - - } - -} diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingMapSection.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingMapSection.kt deleted file mode 100644 index a44f5345b..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingMapSection.kt +++ /dev/null @@ -1,83 +0,0 @@ -package net.mamoe.mirai.utils.setting - - -import org.ini4j.Profile -import java.io.IOException -import java.util.concurrent.ConcurrentHashMap -import kotlin.streams.toList - - -/** - * @author NaturalHG - */ -class MiraiSettingMapSection : ConcurrentHashMap(), MiraiSettingSection { - - operator fun get(key: String?, defaultValue: T): T { - if (key == null || key.isEmpty()) { - return defaultValue - } - return if (super.containsKey(key)) { - @Suppress("UNCHECKED_CAST") - super.get(key) as T - } else defaultValue - } - - - operator fun set(key: String, value: Any) { - this[key] = value - } - - fun getInt(key: String): Int { - return this.getInt(key, 0) - } - - fun getInt(key: String, defaultValue: Int): Int { - return Integer.parseInt(this[key, defaultValue].toString()) - } - - fun getDouble(key: String): Double { - return this.getDouble(key, 0.0) - } - - fun getDouble(key: String, defaultValue: Double): Double { - return java.lang.Double.parseDouble(this[key, defaultValue].toString()) - } - - fun getFloat(key: String): Float { - return this.getFloat(key, 0f) - } - - fun getFloat(key: String, defaultValue: Float): Float { - return java.lang.Float.parseFloat(this[key, defaultValue].toString()) - } - - fun getString(key: String): String { - return this.getString(key, "") - } - - fun getString(key: String, defaultValue: String): String { - return this[key, defaultValue].toString() - } - - fun getObject(key: String): Any? { - return this[key] - } - - @Suppress("UNCHECKED_CAST") - fun asList(): List { - return this.values.stream().map { a -> a as T }.toList() - } - - @Synchronized - override fun saveAsSection(section: Profile.Section) { - section.clear() - this.forEach{ key, value -> section.put(key, value) } - } - - @Throws(IOException::class) - override fun close() { - - } - -} - diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingSection.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingSection.kt deleted file mode 100644 index c9c0f8cd5..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettingSection.kt +++ /dev/null @@ -1,12 +0,0 @@ -package net.mamoe.mirai.utils.setting - -import org.ini4j.Profile - -import java.io.Closeable - -/** - * @author NaturalHG - */ -interface MiraiSettingSection : Closeable { - fun saveAsSection(section: Profile.Section) -} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettings.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettings.kt deleted file mode 100644 index 4b79f0cf1..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setting/MiraiSettings.kt +++ /dev/null @@ -1,96 +0,0 @@ -package net.mamoe.mirai.utils.setting - -import org.ini4j.Config -import org.ini4j.Ini -import java.io.File -import java.io.IOException -import java.util.* -import java.util.concurrent.ConcurrentHashMap - -/** - * Thread-safe Mirai Config

- * Only supports `INI` format

- * Supports [Map] and [List] - * - * @author NaturalHG - */ -class MiraiSettings(file: File) -/* - public MiraiSettings(MiraiPluginBase pluginBase, String filename) { - // TODO: 2019/9/6 每个插件独立文件夹存放 - this(new File(filename)); - }*/ { - - private val file: File - - private var ini: Ini - - private val cacheSection = ConcurrentHashMap() - - init { - val f = file.takeIf { it.name.contains(".") } ?: File(file.path + ".ini") - this.file = f - if (!f.exists() && !f.createNewFile()) { - throw RuntimeException("cannot create config file $f") - } - val config = Config() - config.isMultiSection = true - ini = Ini() - ini.config = config - ini.load(this.file.toURI().toURL()) - } - - @Synchronized - fun setSection(key: String, section: MiraiSettingSection) { - cacheSection[key] = section - } - - - @Synchronized - fun getMapSection(key: String): MiraiSettingMapSection { - if (!cacheSection.containsKey(key)) { - val section = MiraiSettingMapSection() - if (ini.containsKey(key)) { - section.putAll(ini[key]!!) - } - cacheSection[key] = section - } - return cacheSection[key] as MiraiSettingMapSection - } - - @Synchronized - fun getListSection(key: String): MiraiSettingListSection { - if (!cacheSection.containsKey(key)) { - val section = MiraiSettingListSection() - if (ini.containsKey(key)) { - section.addAll(ini[key]!!.values) - } - cacheSection[key] = section - } - return cacheSection[key] as MiraiSettingListSection - } - - - @Synchronized - fun save() { - cacheSection.forEach { (k, a) -> - if (!ini.containsKey(k)) { - ini.put(k, "", HashMap()) - } - a.saveAsSection(ini[k]!!) - } - this.clearCache() - try { - ini.store(file) - } catch (e: IOException) { - e.printStackTrace() - } - - } - - @Synchronized - fun clearCache() { - cacheSection.clear() - } -} - diff --git a/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt b/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt deleted file mode 100644 index 1415cc18b..000000000 --- a/mirai-core/src/jvmTest/kotlin/TestGroupImage.kt +++ /dev/null @@ -1,25 +0,0 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") - -import net.mamoe.mirai.contact.groupId -import net.mamoe.mirai.contact.toInternalId -import net.mamoe.mirai.network.protocol.tim.packet.action.GroupImageIdRequestPacket -import net.mamoe.mirai.utils.hexToBytes -import net.mamoe.mirai.utils.io.readRemainingBytes -import net.mamoe.mirai.utils.io.toUHexString -import net.mamoe.mirai.utils.toExternalImage -import java.io.File -import javax.imageio.ImageIO - -val sessionKey: ByteArray = "F1 ED F2 BC 55 17 7B FE CC CC F3 08 D1 8D A7 0E".hexToBytes() - -fun main() = println({ - val image = ImageIO.read(File("C:\\Users\\Him18\\Desktop\\test2.gif").readBytes().inputStream()).toExternalImage("png") - - // File("C:\\Users\\Him18\\Desktop\\test2.jpg").writeBytes(image.fileData.readBytes()) - GroupImageIdRequestPacket( - 1994701021u, - 580266363u.groupId().toInternalId(), - image, - sessionKey - ).packet.readRemainingBytes().toUHexString() -}()) diff --git a/mirai-debug/src/main/java/DecryptTest.kt b/mirai-debug/src/main/java/DecryptTest.kt index 176859586..019099b85 100644 --- a/mirai-debug/src/main/java/DecryptTest.kt +++ b/mirai-debug/src/main/java/DecryptTest.kt @@ -1,6 +1,6 @@ @file:Suppress("UNUSED_VARIABLE") -import net.mamoe.mirai.utils.hexToBytes +import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.stringOfWitch import net.mamoe.mirai.utils.io.toUHexString import java.io.ByteArrayInputStream diff --git a/mirai-debug/src/main/java/HexDebuggerGui.kt b/mirai-debug/src/main/java/HexDebuggerGui.kt index 469a3272b..475f52b56 100644 --- a/mirai-debug/src/main/java/HexDebuggerGui.kt +++ b/mirai-debug/src/main/java/HexDebuggerGui.kt @@ -7,7 +7,7 @@ import javafx.scene.layout.Region import javafx.scene.paint.Color import javafx.scene.text.FontWeight import kotlinx.coroutines.* -import net.mamoe.mirai.utils.hexToBytes +import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.stringOfWitch import net.mamoe.mirai.utils.readUnsignedVarInt diff --git a/mirai-debug/src/main/java/PacketDebuger.kt b/mirai-debug/src/main/java/PacketDebuger.kt index 02c1fded7..2ab07aa6e 100644 --- a/mirai-debug/src/main/java/PacketDebuger.kt +++ b/mirai-debug/src/main/java/PacketDebuger.kt @@ -15,9 +15,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket import net.mamoe.mirai.network.protocol.tim.packet.event.UnknownServerEventPacket import net.mamoe.mirai.utils.DecryptionFailedException import net.mamoe.mirai.utils.decryptBy -import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.toUHexString import org.pcap4j.core.BpfProgram.BpfCompileMode import org.pcap4j.core.PacketListener import org.pcap4j.core.PcapNetworkInterface @@ -135,7 +133,6 @@ object Main { println("密文body=" + remaining.toUHexString()) println("解密body=解密失败") } - } } diff --git a/mirai-demos/build.gradle b/mirai-demos/build.gradle new file mode 100644 index 000000000..e69de29bb diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt index 86a183e7e..04f2f6092 100644 --- a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt +++ b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt @@ -16,9 +16,9 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess import net.mamoe.mirai.network.session import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.hexToBytes +import net.mamoe.mirai.utils.io.hexToBytes +import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toUHexString -import net.mamoe.mirai.utils.toByteArray import net.mamoe.mirai.utils.toExternalImage import java.io.File @@ -36,6 +36,37 @@ private fun readTestAccount(): BotAccount? { } } +@Suppress("UNUSED_VARIABLE") +suspend fun main() { + val bot = Bot( + readTestAccount() ?: BotAccount(//填写你的账号 + id = 1994701121u, + password = "123456" + ) + ) + + // 覆盖默认的配置 + bot.login { + randomDeviceName = false + }.requireSuccess() + + bot.messageDSL() + directlySubscribe(bot) + + //DSL 监听 + subscribeAll { + always { + //获取第一个纯文本消息 + val firstText = it.message.firstOrNull() + + } + } + + demo2() + + bot.network.awaitDisconnection()//等到直到断开连接 +} + /** * 使用 dsl 监听消息事件 * @@ -237,38 +268,6 @@ suspend fun directlySubscribe(bot: Bot) { } } -@Suppress("UNUSED_VARIABLE") -suspend fun main() { - val bot = Bot( - readTestAccount() ?: BotAccount(//填写你的账号 - id = 1994701121u, - password = "123456" - ) - ) - - // 覆盖默认的配置 - bot.login { - randomDeviceName = false - }.requireSuccess() - - bot.messageDSL() - directlySubscribe(bot) - - //DSL 监听 - subscribeAll<FriendMessageEvent> { - always { - //获取第一个纯文本消息 - val firstText = it.message.firstOrNull<PlainText>() - - } - } - - demo2() - - bot.network.awaitDisconnection()//等到直到断开连接 -} - - /** * 实现功能: * 对机器人说 "记笔记", 机器人记录之后的所有消息. diff --git a/settings.gradle b/settings.gradle index eabcc7def..f5b8434e8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,7 @@ rootProject.name = 'mirai' + include(':mirai-core') + include(':mirai-console') include(':mirai-api') include(':mirai-demos:mirai-demo-1')