ByteArrayPool

This commit is contained in:
Him188 2019-10-30 23:00:29 +08:00
parent b9129576bb
commit 71d5dda297
47 changed files with 867 additions and 985 deletions

5
.gitignore vendored
View File

@ -34,4 +34,7 @@ mirai.iml
/test
.gradle/
.gradle/
local.properties

View File

@ -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/" }
}
}

View File

@ -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
View 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")
}
}
}

View File

@ -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')
}

View File

@ -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 -> {

View File

@ -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)

View File

@ -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() {

View File

@ -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
/**
* 数据包.

View File

@ -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() }

View File

@ -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] 中被使用.

View File

@ -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)
}
}
}
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 } }
}

View File

@ -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

View File

@ -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(

View File

@ -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?) {
}
}
/**
* 在顶层日志记录这个异常

View File

@ -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 }
}

View File

@ -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")
}

View File

@ -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
/**
* 表示这个参数必须为正数
*/

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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
}
}
}
}

View File

@ -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 ->

View File

@ -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)

View File

@ -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()

View File

@ -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>?
}
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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()
}
*/

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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()
}())

View File

@ -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

View File

@ -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

View File

@ -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
View File

View 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()//等到直到断开连接
}
/**
* 实现功能:
* 对机器人说 "记笔记", 机器人记录之后的所有消息.

View File

@ -1,5 +1,7 @@
rootProject.name = 'mirai'
include(':mirai-core')
include(':mirai-console')
include(':mirai-api')
include(':mirai-demos:mirai-demo-1')