mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-27 17:00:14 +08:00
ByteArrayPool
This commit is contained in:
parent
b9129576bb
commit
71d5dda297
5
.gitignore
vendored
5
.gitignore
vendored
@ -34,4 +34,7 @@ mirai.iml
|
||||
/test
|
||||
|
||||
|
||||
.gradle/
|
||||
.gradle/
|
||||
|
||||
|
||||
local.properties
|
@ -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/" }
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
101
mirai-core/build.gradle.kts
Normal file
101
mirai-core/build.gradle.kts
Normal file
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -183,7 +183,6 @@ class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
|
||||
|
||||
/**
|
||||
* 如果消息的前缀是 [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<T : SenderAndMessage<*>>(
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
|
||||
@DslMarker
|
||||
internal annotation class MessageDsl
|
||||
internal annotation class MessageDsl
|
||||
|
||||
|
||||
fun main() {
|
||||
|
||||
println('B')
|
||||
println("\u7154225")
|
||||
println('B' - 'b')
|
||||
}
|
@ -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 -> {
|
||||
|
@ -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<TIMBotNetworkHandler.BotSocketAdapter>, 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 <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
|
||||
@ -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)
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
* 数据包.
|
||||
|
@ -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 : ServerPacket> 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())
|
||||
inline fun <R> 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 <R> ServerPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R = this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
|
||||
inline fun <R> 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() }
|
||||
|
@ -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] 中被使用.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<ByteArray> = ByteBufferPool()
|
||||
|
||||
class ByteBufferPool : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) {
|
||||
override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
|
||||
override fun clearInstance(instance: ByteArray): ByteArray = instance.apply { map { 0 } }
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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?) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在顶层日志记录这个异常
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
@ -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
|
||||
|
||||
/**
|
||||
* 表示这个参数必须为正数
|
||||
*/
|
||||
|
@ -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<ByteArray> = ByteArrayPoolImpl
|
||||
|
||||
private object ByteArrayPoolImpl : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) {
|
||||
override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE)
|
||||
|
||||
override fun clearInstance(instance: ByteArray): ByteArray = instance
|
||||
}
|
||||
|
@ -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 <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t)
|
||||
|
||||
|
@ -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
|
||||
|
@ -84,7 +84,7 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
|
||||
0x00_58u -> ResponsePacket.Encrypted<HeartbeatPacket.Response>(this)
|
||||
0x03_88u -> ResponsePacket.Encrypted<GroupImageIdRequestPacket.Response>(this)
|
||||
0x03_52u -> ResponsePacket.Encrypted<FriendImageIdRequestPacket.Response>(this)
|
||||
0x01_BDu -> ResponsePacket.Encrypted<SubmitImageFilenamePacket.Response>(this)
|
||||
// 0x01_BDu -> ResponsePacket.Encrypted<SubmitImageFilenamePacket.Response>(this)
|
||||
|
||||
else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
|
||||
}.applySequence(sequenceId)
|
||||
|
@ -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 : Comparable<N>> 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) {
|
||||
|
@ -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
|
||||
class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
|
@ -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<Int, ByteArray> = 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<Int, UByteArray> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -55,6 +55,9 @@ private object IgnoreIdListInclude : List<String> 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 ->
|
||||
|
@ -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)
|
||||
|
@ -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()
|
@ -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<Any>(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<String, Any>? {
|
||||
/*
|
||||
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<LinkedHashMap<*, *>>(file.readLines(Charset.defaultCharset()).joinToString("\n"), LinkedHashMap::class.java) as MutableMap<String, Any>?
|
||||
}
|
||||
}
|
||||
}
|
@ -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<T> extends LinkedHashMap<String, T> {
|
||||
|
||||
public MiraiConfigSection(){
|
||||
super();
|
||||
}
|
||||
|
||||
public MiraiConfigSection(Map<String, T> 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<Throwable> 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<Throwable> 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<Throwable> 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<Throwable> 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<Throwable> 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 <D> MiraiConfigSection<D> getTypedSection(String key){
|
||||
var content = this.get(key);
|
||||
if(content instanceof MiraiConfigSection){
|
||||
return (MiraiConfigSection<D>) content;
|
||||
}
|
||||
if(content instanceof Map){
|
||||
return new MiraiConfigSection<>(
|
||||
(LinkedHashMap<String, D>) content
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MiraiConfigSection<Object> getSection(String key){
|
||||
return this.getTypedSection(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends T> D getAsOrDefault(String key, D defaultV){
|
||||
return (D)this.getOrDefault(key,defaultV);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends T> D getAsOrDefault(String key, Supplier<D> supplier) {
|
||||
D d = (D) this.get(key);
|
||||
if (d != null) {
|
||||
return d;
|
||||
}
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends T> D getAs(String key) {
|
||||
return (D) this.get(key);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
/*
|
||||
|
||||
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()
|
||||
}
|
||||
*/
|
@ -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<Any>(), MiraiSettingSection {
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> 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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<String, Any>(), MiraiSettingSection {
|
||||
|
||||
operator fun <T> 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 <T> asList(): List<T> {
|
||||
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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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 <br></br>
|
||||
* Only supports `INI` format <br></br>
|
||||
* 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<String, MiraiSettingSection>()
|
||||
|
||||
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<Any, Any>())
|
||||
}
|
||||
a.saveAsSection(ini[k]!!)
|
||||
}
|
||||
this.clearCache()
|
||||
try {
|
||||
ini.store(file)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun clearCache() {
|
||||
cacheSection.clear()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}())
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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=解密失败")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
0
mirai-demos/build.gradle
Normal file
0
mirai-demos/build.gradle
Normal file
@ -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<FriendMessageEvent> {
|
||||
always {
|
||||
//获取第一个纯文本消息
|
||||
val firstText = it.message.firstOrNull<PlainText>()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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()//等到直到断开连接
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现功能:
|
||||
* 对机器人说 "记笔记", 机器人记录之后的所有消息.
|
||||
|
@ -1,5 +1,7 @@
|
||||
rootProject.name = 'mirai'
|
||||
|
||||
include(':mirai-core')
|
||||
|
||||
include(':mirai-console')
|
||||
include(':mirai-api')
|
||||
include(':mirai-demos:mirai-demo-1')
|
||||
|
Loading…
Reference in New Issue
Block a user