Merge remote-tracking branch 'origin/master'

This commit is contained in:
liujiahua123123 2019-09-13 23:50:48 +08:00
commit 86d64f026d
59 changed files with 2260 additions and 1411 deletions

View File

@ -0,0 +1,15 @@
package net.mamoe.mirai;
import net.mamoe.mirai.utils.BotAccount;
import java.io.Closeable;
/**
* @author Him188moe
*/
public interface Bot extends Closeable {
BotAccount getAccount();
// TODO: 2019/9/13 add more
}

View File

@ -17,6 +17,31 @@
</parent>
<dependencies>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.8.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>1.8.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-api</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
@ -56,7 +81,12 @@
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<scope>compile</scope>
</dependency>

View File

@ -4,6 +4,7 @@ import lombok.Getter;
import net.mamoe.mirai.contact.Group;
import net.mamoe.mirai.contact.QQ;
import net.mamoe.mirai.network.BotNetworkHandler;
import net.mamoe.mirai.network.BotNetworkHandlerImpl;
import net.mamoe.mirai.utils.BotAccount;
import net.mamoe.mirai.utils.ContactList;
import net.mamoe.mirai.utils.config.MiraiConfigSection;
@ -19,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* <br>
* {@link Bot} 3 个模块组成.
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
* {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
* {@linkplain BotNetworkHandlerImpl 网络处理器}: 可通过 {@link Bot#network} 访问
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
* <br>
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
@ -29,7 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* Bot that is the base of the whole program.
* It consists of
* a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group};
* a {@link BotNetworkHandler}, which manages the connection to the server;
* a {@link BotNetworkHandlerImpl}, which manages the connection to the server;
* a {@link BotAccount}, which stores the account information(e.g. qq number the bot)
* <br>
* To get all the QQ contacts, access {@link Bot#account}
@ -118,7 +119,7 @@ public final class Bot implements Closeable {
Objects.requireNonNull(owners);
this.account = account;
this.owners = Collections.unmodifiableList(owners);
this.network = new BotNetworkHandler(this);
this.network = new BotNetworkHandlerImpl(this);
}

View File

@ -212,22 +212,29 @@ public final class MiraiServer {
}
String qqList = "3150499752----1234567890\n" +
"3119292829----987654321\n" +
"2399148773----12345678910\n" +
"3145561616----987654321\n" +
"2374150554----12345678910\n" +
"2375307394----12345678910\n" +
"2401645747----12345678910\n" +
"1515419818----1234567890\n" +
"3107367848----987654321\n";
String qqList =
"2573990098----qq123456789\n" +
"3303923865----q123456789\n" +
"3349933294----q123456789\n" +
"3303708824----q123456789\n" +
"3227036647----q123456789\n" +
"3451394431----q123456789\n" +
"3533243484----q123456789\n" +
"3364512686----q123456789\n" +
"3137567463----q123456789\n" +
"3414786399----q123456789\n" +
"3347405939----q123456789\n" +
"3544089622----q123456789\n" +
"3108512993----q123456789\n" +
"2985563549----q123456789\n" +
"3463531892----q123456789\n";
private Bot getAvailableBot() throws ExecutionException, InterruptedException {
for (String it : qqList.split("\n")) {
var strings = it.split("----");
var bot = new Bot(new BotAccount(Long.parseLong(strings[0]), strings[1]), List.of());
if (bot.network.tryLogin$mirai_core().get() == LoginState.SUCCESS) {
if (bot.network.tryLogin(200).get() == LoginState.SUCCESS) {
MiraiLoggerKt.success(bot, "Login succeed");
return bot;
}

View File

@ -4,6 +4,9 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.message.defaults.UnsolvedImage
import net.mamoe.mirai.network.LoginSession
import java.util.concurrent.CompletableFuture
/**
* 联系人.
@ -20,6 +23,13 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) {
*/
abstract fun sendMessage(message: MessageChain)
/**
* 上传图片
*/
fun uploadImage(session: LoginSession, image: UnsolvedImage): CompletableFuture<Unit> {
return image.upload(session, this)
}
fun sendMessage(message: Message) {
if (message is MessageChain) {
return sendMessage(message)

View File

@ -26,7 +26,7 @@ class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
val members = ContactList<QQ>()
override fun sendMessage(message: MessageChain) {
bot.network.messageHandler.sendGroupMessage(this, message)
bot.network.message.sendGroupMessage(this, message)
}
override fun sendXMLMessage(message: String) {

View File

@ -12,14 +12,14 @@ import net.mamoe.mirai.message.defaults.MessageChain
* Java 获取 qq : `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()`
*
* A QQ instance helps you to receive message from or send message to.
* A QQ instance helps you to receive message from or sendPacket message to.
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
*
* @author Him188moe
*/
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
override fun sendMessage(message: MessageChain) {
bot.network.messageHandler.sendFriendMessage(this, message)
bot.network.message.sendFriendMessage(this, message)
}
override fun sendXMLMessage(message: String) {

View File

@ -4,6 +4,8 @@ package net.mamoe.mirai.message;
* @author Him188moe
*/
public enum FaceID {
unknown(0xff),
Face_jingya(0),
Face_piezui(1),
Face_se(2),

View File

@ -2,10 +2,7 @@ package net.mamoe.mirai.message
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.defaults.At
import net.mamoe.mirai.message.defaults.Image
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.message.defaults.*
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
@ -106,41 +103,22 @@ abstract class Message {
* @param tail tail
* @return message connected
*/
open fun concat(tail: Message): Message {
open fun concat(tail: Message): MessageChain {
return MessageChain(this, Objects.requireNonNull(tail))
}
fun concat(tail: String): Message {
fun concat(tail: String): MessageChain {
return concat(PlainText(tail))
}
fun withImage(imageId: String): Message {
infix fun withImage(imageId: String): MessageChain = this + Image(imageId)
fun withImage(filename: String, image: BufferedImage): MessageChain = this + UnsolvedImage(filename, image)
infix fun withImage(imageFile: File): MessageChain = this + UnsolvedImage(imageFile)
// TODO: 2019/9/1
return this
}
infix fun withAt(target: QQ): MessageChain = this + target.at()
infix fun withAt(target: Long): MessageChain = this + At(target)
fun withImage(image: BufferedImage): Message {
// TODO: 2019/9/1
return this
}
fun withImage(image: File): Message {
// TODO: 2019/9/1
return this
}
fun withAt(target: QQ): Message {
this.concat(target.at())
return this
}
fun withAt(target: Int): Message {
this.concat(At(target.toLong()))
return this
}
open fun toChain(): MessageChain {
return MessageChain(this)
@ -152,22 +130,22 @@ abstract class Message {
/**
* 实现使用 '+' 操作符连接 [Message] [Message]
*/
infix operator fun plus(another: Message): Message = this.concat(another)
infix operator fun plus(another: Message): MessageChain = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [String]
*/
infix operator fun plus(another: String): Message = this.concat(another)
infix operator fun plus(another: String): MessageChain = this.concat(another)
/**
* 实现使用 '+' 操作符连接 [Message] [Number]
*/
infix operator fun plus(another: Number): Message = this.concat(another.toString())
infix operator fun plus(another: Number): MessageChain = this.concat(another.toString())
/**
* 连接 [String] [Message]
*/
fun String.concat(another: Message): Message = PlainText(this).concat(another)
fun String.concat(another: Message): MessageChain = PlainText(this).concat(another)
override fun hashCode(): Int {
return javaClass.hashCode()

View File

@ -3,26 +3,38 @@ package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.FaceID
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.network.packet.writeHex
import net.mamoe.mirai.network.packet.writeLVByteArray
import net.mamoe.mirai.utils.lazyEncode
/**
* QQ 自带表情
*
* @author Him188moe
*/
class Face(val id: FaceID?) : Message() {
class Face(val id: FaceID) : Message() {
override val type: Int = MessageId.FACE
override fun toStringImpl(): String {
return if (id == null) {
"[face?]"
} else String.format("[face%d]", id.id)
return String.format("[face%d]", id.id)
}
override fun toByteArray(): ByteArray {
TODO()
override fun toByteArray(): ByteArray = lazyEncode { section ->
section.writeByte(this.type)
section.writeLVByteArray(lazyEncode { child ->
child.writeShort(1)
child.writeByte(this.id.id)
child.writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
child.writeShort(2)
child.writeByte(0x14)//??
child.writeByte(this.id.id + 65)
})
}
override fun valueEquals(another: Message): Boolean {
if (another !is Face) {
return false

View File

@ -2,66 +2,49 @@ package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.network.packet.cutTail
import net.mamoe.mirai.network.packet.md5
import java.awt.image.BufferedImage
import java.io.*
import java.net.URL
import javax.imageio.ImageIO
import net.mamoe.mirai.network.packet.writeHex
import net.mamoe.mirai.network.packet.writeLVByteArray
import net.mamoe.mirai.network.packet.writeLVString
import net.mamoe.mirai.utils.lazyEncode
/**
* 图片消息.
* 由接收消息时构建, 可直接发送
*
* @author Him188moe
*/
class Image : Message {
open class Image internal constructor(val imageID: String) : Message() {
override val type: Int = MessageId.IMAGE
private var imageID: String? = null
constructor(inputStream: InputStream) {
}
constructor(image: BufferedImage) {
}
@Throws(FileNotFoundException::class)
constructor(imageFile: File) : this(FileInputStream(imageFile)) {
}
@Throws(IOException::class)
constructor(url: URL) : this(ImageIO.read(url)) {
}
/**
* {xxxxx}.jpg
*
* @param imageID
*/
constructor(imageID: String) {
this.imageID = imageID
}
override fun toStringImpl(): String {
return imageID!!
return imageID
}
override fun toByteArray(): ByteArray {
TODO()
override fun toByteArray(): ByteArray = lazyEncode { section ->
section.writeByte(0x03)//todo 可能是 0x03?
section.writeLVByteArray(lazyEncode { child ->
child.writeByte(0x02)
child.writeLVString(this.imageID)
child.writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
"04 D9 8A 5A 70 " +
"06 00 " +
"04 00 00 00 50 " +
"07 00 " +
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20")
child.writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
child.writeBytes(this.imageID)
child.writeByte(0x41)
})
}
override fun valueEquals(another: Message): Boolean {
if (another !is Image) {
return false
if (another is Image) {
return this.imageID == another.imageID
}
return this.imageID == another.imageID
}
companion object {
fun getImageID(filename: String): ByteArray = md5(filename).cutTail(1)
return false
}
}
fun main() {
println(0xB0)
}

View File

@ -11,7 +11,7 @@ import java.util.stream.Stream
class MessageChain : Message {
override val type: Int = MessageId.CHAIN
internal val list = LinkedList<Message>()
val list: MutableList<Message> = Collections.synchronizedList(LinkedList<Message>())
constructor(head: Message, tail: Message) {
Objects.requireNonNull(head)
@ -26,17 +26,16 @@ class MessageChain : Message {
list.add(message)
}
constructor()
fun toList(): List<Message> {
return list.toList()
constructor(messages: Collection<Message>) {
list.addAll(messages)
}
constructor()
fun size(): Int {
return list.size
}
@Synchronized
fun containsType(@MagicConstant(valuesFromClass = MessageId::class) type: Int): Boolean {
for (message in list) {
if (message.type == type) {
@ -47,21 +46,18 @@ class MessageChain : Message {
}
fun stream(): Stream<Message> {
return ArrayList(list).stream()
return list.stream()
}
@Synchronized
override fun toStringImpl(): String {
return this.list.stream().map { it.toString() }.collect(Collectors.joining(""))
}
@Synchronized
override fun toObjectString(): String {
return String.format("MessageChain(%s)", this.list.stream().map { it.toObjectString() }.collect(Collectors.joining(", ")))
}
@Synchronized
override fun concat(tail: Message): Message {
override fun concat(tail: Message): MessageChain {
this.list.add(tail)
clearToStringCache()
return this

View File

@ -2,8 +2,8 @@ package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
import net.mamoe.mirai.network.packet.writeVarByteArray
import net.mamoe.mirai.network.packet.writeVarString
import net.mamoe.mirai.network.packet.writeLVByteArray
import net.mamoe.mirai.network.packet.writeLVString
import net.mamoe.mirai.utils.lazyEncode
/**
@ -19,9 +19,9 @@ class PlainText(private val text: String) : Message() {
override fun toByteArray(): ByteArray = lazyEncode { section ->
section.writeByte(this.type)
section.writeVarByteArray(lazyEncode { child ->
section.writeLVByteArray(lazyEncode { child ->
child.writeByte(0x01)
child.writeVarString(this.text)
child.writeLVString(this.text)
})
}

View File

@ -0,0 +1,63 @@
package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.image.ClientTryGetImageIDPacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageFailedPacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPacket
import net.mamoe.mirai.network.packet.md5
import net.mamoe.mirai.utils.ImageNetworkUtils
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.toUHexString
import java.awt.image.BufferedImage
import java.io.File
import java.net.URL
import java.util.concurrent.CompletableFuture
import javax.imageio.ImageIO
/**
* 不确定是否存在于服务器的 [Image].
* 必须在发送之前 [UnsolvedImage.upload] [Contact.uploadImage], 否则会发送失败.
x *
* @author Him188moe
*/
class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImageId(filename)) {
constructor(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile))
constructor(url: URL) : this(File(url.file))
fun upload(session: LoginSession, contact: Contact): CompletableFuture<Unit> {
return session.expectPacket<ServerTryUploadGroupImageResponsePacket> {
toSend { ClientTryGetImageIDPacket(session.bot.account.qqNumber, session.sessionKey, session.bot.account.qqNumber, image) }
expect {
when (it) {
is ServerTryUploadGroupImageFailedPacket -> {
//已经存在于服务器了
}
is ServerTryUploadGroupImageSuccessPacket -> {
val data = image.toByteArray()
if (!ImageNetworkUtils.postImage(it.uKey.toUHexString(), data.size, session.bot.account.qqNumber, contact.number, data)) {
throw RuntimeException("cannot upload image")
}
}
}
}
}
}
companion object {
@JvmStatic
fun getImageId(filename: String): String {
val md5 = md5(filename)
return "{" + md5.copyOfRange(0, 4).toUHexString("") + "-"
.plus(md5.copyOfRange(4, 6).toUHexString("")) + "-"
.plus(md5.copyOfRange(6, 8).toUHexString("")) + "-"
.plus(md5.copyOfRange(8, 12).toUHexString("")) + "-"
.plus(md5.copyOfRange(12, 16).toUHexString("")) + "}." + if (filename.endsWith(".jpeg")) "jpg" else filename.substringAfter(".", "jpg")
}
}
}

View File

@ -0,0 +1,64 @@
package net.mamoe.mirai.network
import net.mamoe.mirai.network.BotNetworkHandlerImpl.BotSocket
import net.mamoe.mirai.network.BotNetworkHandlerImpl.Login
import net.mamoe.mirai.network.handler.*
import net.mamoe.mirai.network.packet.ClientPacket
import net.mamoe.mirai.network.packet.Packet
import net.mamoe.mirai.network.packet.ServerEventPacket
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.login.LoginState
import java.io.Closeable
import java.util.concurrent.CompletableFuture
/**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
* [BotNetworkHandler] 是全异步和线程安全的.
*
* [BotNetworkHandler] 2 个模块构成:
* - [BotSocket]: 处理数据包底层的发送([ByteArray])
* - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理
*
* 其中, [PacketHandler] 4 个子模块构成:
* - [DebugPacketHandler] 输出 [Packet.toString]
* - [Login] 处理 touch/login/verification code 相关
* - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等)
*
* A BotNetworkHandler is used to connect with Tencent servers.
*
* @author Him188moe
*/
interface BotNetworkHandler : Closeable {
/**
* 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray]
*
* java 调用方式: `botNetWorkHandler.getSocket()`
*/
val socket: DataPacketSocket
/**
* 消息处理. 如发送好友消息, 接受群消息并触发事件
*
* java 调用方式: `botNetWorkHandler.getMessage()`
*/
var message: MessagePacketHandler
/**
* 动作处理. 如发送好友请求, 处理别人发来的好友请求等
*
* java 调用方式: `botNetWorkHandler.getAction()`
*/
var action: ActionPacketHandler
fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState>
/**
* 添加一个临时包处理器
*
* @see [TemporaryPacketHandler]
*/
fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
override fun close()
}

View File

@ -0,0 +1,403 @@
package net.mamoe.mirai.network
import net.mamoe.mirai.Bot
import net.mamoe.mirai.MiraiServer
import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent
import net.mamoe.mirai.event.events.network.BeforePacketSendEvent
import net.mamoe.mirai.event.events.network.PacketSentEvent
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
import net.mamoe.mirai.network.handler.*
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.network.packet.login.*
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.*
import java.io.Closeable
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.imageio.ImageIO
/**
* [BotNetworkHandler] 的内部实现, 该类不会有帮助理解的注解, 请查看 [BotNetworkHandler] 以获取帮助
*
* @see BotNetworkHandler
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
override val socket: BotSocket = BotSocket()
lateinit var login: Login
override lateinit var message: MessagePacketHandler
override lateinit var action: ActionPacketHandler
val packetHandlers: PacketHandlerList = PacketHandlerList()
internal val temporaryPacketHandlers = Collections.synchronizedList(mutableListOf<TemporaryPacketHandler<*>>())
override fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) {
temporaryPacketHandler.send(action.session)
temporaryPacketHandlers.add(temporaryPacketHandler)
}
//private | internal
/**
* 尝试登录
*
* @param touchingTimeoutMillis 连接每个服务器的 timeout
*/
override fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture<LoginState> {
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
val future = CompletableFuture<LoginState>()
fun login() {
this.socket.close()
val ip = ipQueue.poll()
if (ip == null) {
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
return
}
this.socket.touch(ip, touchingTimeoutMillis).get().let { state ->
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
login()//超时或未知, 重试连接下一个服务器
} else {
future.complete(state)
}
}
}
login()
return future
}
private fun onLoggedIn(sessionKey: ByteArray) {
val session = LoginSession(bot, sessionKey, socket)
message = MessagePacketHandler(session)
action = ActionPacketHandler(session)
packetHandlers.add(message.asNode())
packetHandlers.add(action.asNode())
}
private lateinit var sessionKey: ByteArray
override fun close() {
this.packetHandlers.forEach {
it.instance.close()
}
this.socket.close()
}
internal inner class BotSocket : Closeable, DataPacketSocket {
override fun distributePacket(packet: ServerPacket) {
try {
packet.decode()
} catch (e: java.lang.Exception) {
e.printStackTrace()
bot.debugPacket(packet)
return
}
temporaryPacketHandlers.removeIf {
it.onPacketReceived(action.session, packet)
}
//For debug
kotlin.run {
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
bot.notice("Packet received: $packet")
}
if (packet is ServerEventPacket) {
sendPacket(ClientEventResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
}
}
if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) {
return
}
login.onPacketReceived(packet)
packetHandlers.forEach {
it.instance.onPacketReceived(packet)
}
}
private var socket: DatagramSocket? = null
internal var serverIP: String = ""
set(value) {
field = value
restartSocket()
}
internal var loginFuture: CompletableFuture<LoginState>? = null
@Synchronized
private fun restartSocket() {
socket?.close()
socket = DatagramSocket(0)
socket!!.connect(InetSocketAddress(serverIP, 8000))
Thread {
while (socket!!.isConnected) {
val packet = DatagramPacket(ByteArray(2048), 2048)
kotlin.runCatching { socket?.receive(packet) }
.onSuccess {
MiraiThreadPool.getInstance().submit {
try {
distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail()))
} catch (e: Exception) {
e.printStackTrace()
}
}
}.onFailure {
if (it.message == "Socket closed" || it.message == "socket closed") {
return@Thread
}
it.printStackTrace()
}
}
}.start()
}
/**
* Start network and touch the server
*/
fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> {
bot.info("Connecting server: $serverAddress")
if (this@BotNetworkHandlerImpl::login.isInitialized) {
login.close()
}
login = Login()
this.loginFuture = CompletableFuture()
serverIP = serverAddress
bot.waitForPacket(ServerPacket::class, timeoutMillis) {
loginFuture!!.complete(LoginState.TIMEOUT)
}
sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP))
return this.loginFuture!!
}
/**
* Not async
*/
@Synchronized
override fun sendPacket(packet: ClientPacket) {
checkNotNull(socket) { "network closed" }
if (socket!!.isClosed) {
return
}
try {
packet.encodePacket()
if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) {
return
}
val data = packet.toByteArray()
socket!!.send(DatagramPacket(data, data.size))
bot.cyanL("Packet sent: $packet")
PacketSentEvent(bot, packet).broadcast()
} catch (e: Throwable) {
e.printStackTrace()
}
}
override fun getOwner(): Bot = this@BotNetworkHandlerImpl.bot
override fun close() {
this.socket?.close()
if (this.loginFuture != null) {
if (!this.loginFuture!!.isDone) {
this.loginFuture!!.cancel(true)
}
this.loginFuture = null
}
}
override fun isClosed(): Boolean {
return this.socket?.isClosed ?: true
}
}
/**
* 处理登录过程
*/
inner class Login : Closeable {
private lateinit var token00BA: ByteArray
private lateinit var token0825: ByteArray
private var loginTime: Int = 0
private lateinit var loginIP: String
private var tgtgtKey: ByteArray = getRandomByteArray(16)
/**
* 0828_decr_key
*/
private lateinit var sessionResponseDecryptionKey: ByteArray
private var captchaSectionId: Int = 1
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
private var heartbeatFuture: ScheduledFuture<*>? = null
fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerTouchResponsePacket -> {
if (packet.serverIP != null) {//redirection
socket.serverIP = packet.serverIP!!
//connect(packet.serverIP!!)
socket.sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber))
} else {//password submission
this.loginIP = packet.loginIP
this.loginTime = packet.loginTime
this.token0825 = packet.token0825
socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
}
}
is ServerLoginResponseFailedPacket -> {
socket.loginFuture?.complete(packet.loginState)
return
}
is ServerVerificationCodeCorrectPacket -> {
this.tgtgtKey = getRandomByteArray(16)
this.token00BA = packet.token00BA
socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
//[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA
this.captchaCache = packet.verifyCodePart1
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
this.captchaSectionId = 1
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
}
}
is ServerVerificationCodeTransmissionPacket -> {
if (packet is ServerVerificationCodeWrongPacket) {
bot.error("验证码错误, 请重新输入")
captchaSectionId = 1
this.captchaCache = byteArrayOf()
}
this.captchaCache = this.captchaCache!! + packet.captchaSectionN
this.token00BA = packet.token00BA
if (packet.transmissionCompleted) {
bot.notice(CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream())))
bot.notice("需要验证码登录, 验证码为 4 字母")
try {
(MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!)
bot.notice("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png")
} catch (e: Exception) {
bot.notice("无法写出验证码文件, 请尝试查看以上字符图片")
}
bot.notice("若要更换验证码, 请直接回车")
val code = Scanner(System.`in`).nextLine()
if (code.isEmpty() || code.length != 4) {
this.captchaCache = byteArrayOf()
this.captchaSectionId = 1
socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825))
} else {
socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
}
} else {
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA))
}
}
is ServerLoginResponseSuccessPacket -> {
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
socket.sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey))
}
//是ClientPasswordSubmissionPacket之后服务器回复的
is ServerLoginResponseKeyExchangePacket -> {
//if (packet.tokenUnknown != null) {
//this.token00BA = packet.token00BA!!
//println("token00BA changed!!! to " + token00BA.toUByteArray())
//}
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
this.tgtgtKey = packet.tgtgtKey
socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: this.token00BA, packet.tlv0006))
} else {
socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: token00BA, packet.tlv0006))
}
}
is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
}, 90000, 90000, TimeUnit.MILLISECONDS)
socket.loginFuture!!.complete(LoginState.SUCCESS)
login.changeOnlineStatus(ClientLoginStatus.ONLINE)
}
is ServerLoginSuccessPacket -> {
BotLoginSucceedEvent(bot).broadcast()
//登录成功后会收到大量上次的消息, 忽略掉
MiraiThreadPool.getInstance().schedule({
message.ignoreMessage = false
}, 3, TimeUnit.SECONDS)
onLoggedIn(sessionKey)
}
is ServerVerificationCodePacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey))
is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey))
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerHeartbeatResponsePacket,
is UnknownServerPacket -> {
//ignored
}
else -> {
}
}
}
fun changeOnlineStatus(status: ClientLoginStatus) {
socket.sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, status))
}
override fun close() {
this.captchaCache = null
this.heartbeatFuture?.cancel(true)
this.heartbeatFuture = null
}
}
}

View File

@ -0,0 +1,98 @@
package net.mamoe.mirai.network
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.handler.DataPacketSocket
import net.mamoe.mirai.network.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.packet.ClientPacket
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.utils.getGTK
import java.util.concurrent.CompletableFuture
/**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
* 此时建立 session, 然后开始处理事务.
*
* @author Him188moe
*/
class LoginSession(
val bot: Bot,
val sessionKey: ByteArray,
val socket: DataPacketSocket
) {
/**
* Web api 使用
*/
lateinit var cookies: String
/**
* Web api 使用
*/
var sKey: String = ""
set(value) {
field = value
gtk = getGTK(value)
}
/**
* Web api 使用
*/
var gtk: Int = 0
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket]. Kotlin 使用
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
*
* 实现方法:
* ```kotlin
* session.expectPacket<ServerPacketXXX> {
* toSend { ClientPacketXXX(...) }
* expect {//it: ServerPacketXXX
*
* }
* }
* ```
*
* @param P 期待的包
* @param handlerTemporary 处理器.
* @return future. 可进行超时处理
*
* Kotlin DSL: Kotlin 使用.
*/
@JvmSynthetic
inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableFuture<Unit> {
val future = CompletableFuture<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also(handlerTemporary))
return future
}
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
* 发送成功后, 该方法会等待收到 [ServerPacket] 直到超时.
* 由于包名可能过长, 可使用 `DataPacketSocket.expectPacket(PacketProcessor)` 替代.
*
* 实现方法:
* ```kotlin
* session.expectPacket<ServerPacketXXX>(ClientPacketXXX(...)) {//it: ServerPacketXXX
*
* }
* ```
*
* @param P 期待的包
* @param toSend 将要发送的包
* @param handler 处理期待的包
* @return future. 可进行超时处理
*
* Kotlin DSL: Kotlin 使用.
*/
@JvmSynthetic
inline fun <reified P : ServerPacket> expectPacket(toSend: ClientPacket, noinline handler: (P) -> Unit): CompletableFuture<Unit> {
val future = CompletableFuture<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also {
it.toSend { toSend }
it.expect(handler)
})
return future
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network
import java.net.InetAddress
@ -72,7 +74,7 @@ object Protocol {
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
@ExperimentalUnsignedTypes
fun hexToBytes(hex: String): ByteArray {
hex.hashCode().let { id ->
if (hexToByteArrayCacheMap.containsKey(id)) {
@ -86,7 +88,7 @@ object Protocol {
}
}
@ExperimentalUnsignedTypes
fun hexToUBytes(hex: String): UByteArray = Arrays
.stream(hex.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
.map { value -> value.trim { it <= ' ' } }

View File

@ -1,645 +0,0 @@
@file:JvmMultifileClass
@file:JvmName("BotNetworkHandler")
package net.mamoe.mirai.network
import net.mamoe.mirai.Bot
import net.mamoe.mirai.MiraiServer
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent
import net.mamoe.mirai.event.events.network.BeforePacketSendEvent
import net.mamoe.mirai.event.events.network.PacketSentEvent
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
import net.mamoe.mirai.event.hookWhile
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.BotNetworkHandler.*
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.network.packet.action.*
import net.mamoe.mirai.network.packet.login.*
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.*
import java.io.Closeable
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import javax.imageio.ImageIO
import kotlin.reflect.KClass
/**
* Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收.
* [BotNetworkHandler] 是全程异步和线程安全的.
*
* [BotNetworkHandler] 2 个模块构成:
* - [SocketHandler]: 处理数据包底层的发送([ByteArray])
* - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理
*
* 其中, [PacketHandler] 4 个子模块构成:
* - [DebugHandler] 输出 [Packet.toString]
* - [LoginHandler] 处理 touch/login/verification code 相关
* - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等)
*
* A BotNetworkHandler is used to connect with Tencent servers.
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
class BotNetworkHandler(private val bot: Bot) : Closeable {
private val socketHandler: SocketHandler = SocketHandler()
val debugHandler = DebugHandler()
val loginHandler = LoginHandler()
val messageHandler = MessageHandler()
val actionHandler = ActionHandler()
private val packetHandlers: Map<KClass<out PacketHandler>, PacketHandler> = linkedMapOf(
DebugHandler::class to debugHandler,
LoginHandler::class to loginHandler,
MessageHandler::class to messageHandler,
ActionHandler::class to actionHandler
)
/**
* Not async
*/
@ExperimentalUnsignedTypes
fun sendPacket(packet: ClientPacket) {
socketHandler.sendPacket(packet)
}
override fun close() {
this.packetHandlers.values.forEach {
it.close()
}
this.socketHandler.close()
}
//private | internal
/**
* 仅当 [LoginState] [LoginState.UNKNOWN] 且非 [LoginState.TIMEOUT] 才会调用 [loginHook].
* 如果要输入验证码, 那么会以参数 [LoginState.VERIFICATION_CODE] 调用 [loginHandler], 登录完成后再以 [LoginState.SUCCESS] 调用 [loginHandler]
*
* @param touchingTimeoutMillis 连接每个服务器的 timeout
*/
@JvmOverloads
internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState> {
val ipQueue: LinkedList<String> = LinkedList(Protocol.SERVER_IP)
val future = CompletableFuture<LoginState>()
fun login() {
this.socketHandler.close()
val ip = ipQueue.poll()
if (ip == null) {
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
return
}
this@BotNetworkHandler.socketHandler.touch(ip, touchingTimeoutMillis).get().let { state ->
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
login()
} else {
future.complete(state)
}
}
}
login()
return future
}
/**
* 分配收到的数据包
*/
@ExperimentalUnsignedTypes
internal fun distributePacket(packet: ServerPacket) {
try {
packet.decode()
} catch (e: java.lang.Exception) {
e.printStackTrace()
bot.debug("Packet=$packet")
bot.debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
bot.debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
return
}
if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) {
debugHandler.onPacketReceived(packet)
return
}
this.packetHandlers.values.forEach {
it.onPacketReceived(packet)
}
}
private inner class SocketHandler : Closeable {
private var socket: DatagramSocket? = null
internal var serverIP: String = ""
set(value) {
field = value
restartSocket()
}
internal var loginFuture: CompletableFuture<LoginState>? = null
@Synchronized
private fun restartSocket() {
socket?.close()
socket = DatagramSocket(0)
socket!!.connect(InetSocketAddress(serverIP, 8000))
Thread {
while (socket!!.isConnected) {
val packet = DatagramPacket(ByteArray(2048), 2048)
kotlin.runCatching { socket?.receive(packet) }
.onSuccess {
MiraiThreadPool.getInstance().submit {
try {
distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail()))
} catch (e: Exception) {
e.printStackTrace()
}
}
}.onFailure {
if (it.message == "Socket closed" || it.message == "socket closed") {
return@Thread
}
it.printStackTrace()
}
}
}.start()
}
/**
* Start network and touch the server
*/
internal fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> {
bot.info("Connecting server: $serverAddress")
this.loginFuture = CompletableFuture()
socketHandler.serverIP = serverAddress
waitForPacket(ServerPacket::class, timeoutMillis) {
loginFuture!!.complete(LoginState.TIMEOUT)
}
sendPacket(ClientTouchPacket(bot.account.qqNumber, socketHandler.serverIP))
return this.loginFuture!!
}
/**
* Not async
*/
@Synchronized
@ExperimentalUnsignedTypes
internal fun sendPacket(packet: ClientPacket) {
checkNotNull(socket) { "network closed" }
if (socket!!.isClosed) {
return
}
try {
packet.encodePacket()
if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) {
return
}
val data = packet.toByteArray()
socket!!.send(DatagramPacket(data, data.size))
bot cyanL "Packet sent: $packet"
PacketSentEvent(bot, packet).broadcast()
} catch (e: Throwable) {
e.printStackTrace()
}
}
@Suppress("UNCHECKED_CAST")
internal fun <P : ServerPacket> waitForPacket(packetClass: KClass<P>, timeoutMillis: Long, timeout: () -> Unit) {
var got = false
ServerPacketReceivedEvent::class.hookWhile {
if (packetClass.isInstance(it.packet) && it.bot == bot) {
got = true
true
} else {
false
}
}
MiraiThreadPool.getInstance().submit {
val startingTime = System.currentTimeMillis()
while (!got) {
if (System.currentTimeMillis() - startingTime > timeoutMillis) {
timeout.invoke()
return@submit
}
Thread.sleep(10)
}
}
}
override fun close() {
this.socket?.close()
if (this.loginFuture != null) {
if (!this.loginFuture!!.isDone) {
this.loginFuture!!.cancel(true)
}
this.loginFuture = null
}
}
fun isClosed(): Boolean {
return this.socket?.isClosed ?: true
}
}
private lateinit var sessionKey: ByteArray
abstract inner class PacketHandler : Closeable {
abstract fun onPacketReceived(packet: ServerPacket)
override fun close() {
}
}
/**
* Kind of [PacketHandler] that prints all packets received in the format of hex byte array.
*/
inner class DebugHandler : PacketHandler() {
override fun onPacketReceived(packet: ServerPacket) {
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
bot notice "Packet received: $packet"
}
if (packet is ServerEventPacket) {
sendPacket(ClientMessageResponsePacket(bot.account.qqNumber, packet.packetId, sessionKey, packet.eventIdentity))
}
}
}
/**
* 处理登录过程
*/
inner class LoginHandler : PacketHandler() {
private lateinit var token00BA: ByteArray
private lateinit var token0825: ByteArray
private var loginTime: Int = 0
private lateinit var loginIP: String
private var tgtgtKey: ByteArray = getRandomByteArray(16)
private var tlv0105: ByteArray = lazyEncode {
it.writeHex("01 05 00 30")
it.writeHex("00 01 01 02 00 14 01 01 00 10")
it.writeRandom(16)
it.writeHex("00 14 01 02 00 10")
it.writeRandom(16)
}
/**
* 0828_decr_key
*/
private lateinit var sessionResponseDecryptionKey: ByteArray
private var captchaSectionId: Int = 1
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
private var heartbeatFuture: ScheduledFuture<*>? = null
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerTouchResponsePacket -> {
if (packet.serverIP != null) {//redirection
socketHandler.serverIP = packet.serverIP!!
//connect(packet.serverIP!!)
sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber))
} else {//password submission
this.loginIP = packet.loginIP
this.loginTime = packet.loginTime
this.token0825 = packet.token0825
sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825))
}
}
is ServerLoginResponseFailedPacket -> {
socketHandler.loginFuture?.complete(packet.loginState)
return
}
is ServerVerificationCodeCorrectPacket -> {
this.tgtgtKey = getRandomByteArray(16)
this.token00BA = packet.token00BA
sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
//[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA
this.captchaCache = packet.verifyCodePart1
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
this.captchaSectionId = 1
sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
}
}
is ServerVerificationCodeTransmissionPacket -> {
if (packet is ServerVerificationCodeWrongPacket) {
bot error "验证码错误, 请重新输入"
captchaSectionId = 1
this.captchaCache = byteArrayOf()
}
this.captchaCache = this.captchaCache!! + packet.captchaSectionN
this.token00BA = packet.token00BA
if (packet.transmissionCompleted) {
bot notice (CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream())))
bot notice ("需要验证码登录, 验证码为 4 字母")
try {
(MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!)
bot notice ("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png")
} catch (e: Exception) {
bot notice "无法写出验证码文件, 请尝试查看以上字符图片"
}
bot notice ("若要更换验证码, 请直接回车")
val code = Scanner(System.`in`).nextLine()
if (code.isEmpty() || code.length != 4) {
this.captchaCache = byteArrayOf()
this.captchaSectionId = 1
sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825))
} else {
sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken))
}
} else {
sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA))
}
}
is ServerLoginResponseSuccessPacket -> {
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socketHandler.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105))
}
//是ClientPasswordSubmissionPacket之后服务器回复的
is ServerLoginResponseKeyExchangePacket -> {
//if (packet.tokenUnknown != null) {
//this.token00BA = packet.token00BA!!
//println("token00BA changed!!! to " + token00BA.toUByteArray())
//}
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
this.tgtgtKey = packet.tgtgtKey
sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: this.token00BA, packet.tlv0006))
} else {
sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
?: token00BA, packet.tlv0006))
}
}
is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
}, 90000, 90000, TimeUnit.MILLISECONDS)
BotLoginSucceedEvent(bot).broadcast()
//登录成功后会收到大量上次的消息, 忽略掉
MiraiThreadPool.getInstance().schedule({
messageHandler.ignoreMessage = false
}, 3, TimeUnit.SECONDS)
this.tlv0105 = packet.tlv0105
sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
}
is ServerLoginSuccessPacket -> {
socketHandler.loginFuture!!.complete(LoginState.SUCCESS)
sendPacket(ClientSKeyRequestPacket(bot.account.qqNumber, sessionKey))
}
is ServerSKeyResponsePacket -> {
actionHandler.sKey = packet.sKey
actionHandler.cookies = "uin=o" + bot.account.qqNumber + ";skey=" + actionHandler.sKey + ";"
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
sendPacket(ClientSKeyRefreshmentRequestPacket(bot.account.qqNumber, sessionKey))
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
actionHandler.gtk = getGTK(actionHandler.sKey)
sendPacket(ClientAccountInfoRequestPacket(bot.account.qqNumber, sessionKey))
}
is ServerEventPacket.Raw -> distributePacket(packet.distribute())
is ServerVerificationCodePacket.Encrypted -> distributePacket(packet.decrypt())
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> distributePacket(packet.decrypt())
is ServerLoginResponseKeyExchangePacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey))
is ServerLoginResponseSuccessPacket.Encrypted -> distributePacket(packet.decrypt(this.tgtgtKey))
is ServerSessionKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
is ServerTouchResponsePacket.Encrypted -> distributePacket(packet.decrypt())
is ServerSKeyResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
is ServerAccountInfoResponsePacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
is ServerEventPacket.Raw.Encrypted -> distributePacket(packet.decrypt(sessionKey))
is ServerAccountInfoResponsePacket,
is ServerHeartbeatResponsePacket,
is UnknownServerPacket -> {
//ignored
}
else -> {
}
}
}
override fun close() {
this.captchaCache = null
this.heartbeatFuture?.cancel(true)
this.sKeyRefresherFuture?.cancel(true)
this.heartbeatFuture = null
this.sKeyRefresherFuture = null
}
}
/**
* 处理消息事件, 承担消息发送任务.
*/
inner class MessageHandler : PacketHandler() {
internal var ignoreMessage: Boolean = true
init {
//todo for test
FriendMessageEvent::class.hookWhile {
if (socketHandler.isClosed()) {
return@hookWhile false
}
if (it.message() valueEquals "你好") {
it.qq.sendMessage("你好!")
} else if (it.message().toString().startsWith("复读")) {
it.qq.sendMessage(it.message())
}
return@hookWhile true
}
}
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerGroupUploadFileEventPacket -> {
//todo
}
is ServerFriendMessageEventPacket -> {
if (ignoreMessage) {
return
}
FriendMessageEvent(bot, bot.contacts.getQQ(packet.qq), packet.message).broadcast()
}
is ServerGroupMessageEventPacket -> {
//todo message chain
//GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message)
}
is UnknownServerEventPacket -> {
//todo
}
is ServerSendFriendMessageResponsePacket,
is ServerSendGroupMessageResponsePacket -> {
//ignored
}
else -> {
//ignored
}
}
}
fun sendFriendMessage(qq: QQ, message: MessageChain) {
sendPacket(ClientSendFriendMessagePacket(bot.account.qqNumber, qq.number, sessionKey, message))
}
fun sendGroupMessage(group: Group, message: Message): Unit {
TODO()
//sendPacket(ClientSendGroupMessagePacket(group.groupId, bot.account.qqNumber, sessionKey, message))
}
}
/**
* 动作: 获取好友列表, 点赞, 踢人等.
* 处理动作事件, 承担动作任务.
*/
inner class ActionHandler : PacketHandler() {
internal lateinit var cookies: String
internal var sKey: String = ""
set(value) {
field = value
gtk = getGTK(value)
}
internal var gtk: Int = 0
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
this.addFriendSessions.forEach {
it.onPacketReceived(packet)
}
}
else -> {
}
}
}
fun addFriend(qqNumber: Long, message: Supplier<String>) {
addFriend(qqNumber, lazy { message.get() })
}
@JvmSynthetic
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
val future = CompletableFuture<AddFriendResult>()
val session = AddFriendSession(qqNumber, future, message)
addFriendSessions.add(session)
session.sendAddRequest();
return future
}
override fun close() {
}
private inner class AddFriendSession(
private val qq: Long,
private val future: CompletableFuture<AddFriendResult>,
private val message: Lazy<String>
) : Closeable {
lateinit var id: ByteArray
fun onPacketReceived(packet: ServerPacket) {
if (!::id.isInitialized) {
return
}
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
return
}
when (packet.state) {
ServerCanAddFriendResponsePacket.State.FAILED -> {
future.complete(AddFriendResult.FAILED)
close()
}
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
future.complete(AddFriendResult.ALREADY_ADDED)
close()
}
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
sendPacket(ClientAddFriendPacket(bot.account.qqNumber, qq, sessionKey))
}
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
}
}
}
}
}
fun sendAddRequest() {
sendPacket(ClientCanAddFriendPacket(bot.account.qqNumber, qq, sessionKey).also { this.id = it.packetIdLast })
}
override fun close() {
addFriendSessions.remove(this)
}
}
}
}

View File

@ -0,0 +1,213 @@
package net.mamoe.mirai.network.handler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.network.packet.action.AddFriendResult
import net.mamoe.mirai.network.packet.action.ClientAddFriendPacket
import net.mamoe.mirai.network.packet.action.ClientCanAddFriendPacket
import net.mamoe.mirai.network.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageFailedPacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPacket
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.getGTK
import java.awt.image.BufferedImage
import java.io.Closeable
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
/**
* 动作: 获取好友列表, 点赞, 踢人等.
* 处理动作事件, 承担动作任务.
*
* @author Him188moe
*/
class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
this.uploadImageSessions.forEach {
it.onPacketReceived(packet)
}
}
is ServerTryUploadGroupImageSuccessPacket -> {
// ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
}
is ServerTryUploadGroupImageFailedPacket -> {
}
is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket -> {
}
is ServerSKeyResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerSKeyResponsePacket -> {
session.sKey = packet.sKey
session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";"
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey))
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
session.gtk = getGTK(session.sKey)
}
is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute())
else -> {
}
}
}
fun addFriend(qqNumber: Long, message: Supplier<String>) {
addFriend(qqNumber, lazy { message.get() })
}
@JvmSynthetic
fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
val future = CompletableFuture<AddFriendResult>()
val session = AddFriendSession(qqNumber, future, message)
// uploadImageSessions.add(session)
session.sendAddRequest();
return future
}
fun requestSKey() {
session.socket.sendPacket(ClientSKeyRequestPacket(session.bot.account.qqNumber, session.sessionKey))
}
fun requestAccountInfo() {
session.socket.sendPacket(ClientAccountInfoRequestPacket(session.bot.account.qqNumber, session.sessionKey))
}
override fun close() {
this.sKeyRefresherFuture?.cancel(true)
this.sKeyRefresherFuture = null
}
private inner class UploadImageSession(
private val group: Long,
private val future: CompletableFuture<AddFriendResult>,
private val image: BufferedImage
) : Closeable {
lateinit var id: ByteArray
fun onPacketReceived(packet: ServerPacket) {
if (!::id.isInitialized) {
return
}
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
return
}
when (packet.state) {
ServerCanAddFriendResponsePacket.State.FAILED -> {
future.complete(AddFriendResult.FAILED)
close()
}
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
future.complete(AddFriendResult.ALREADY_ADDED)
close()
}
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
// session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
}
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
}
}
}
}
}
fun sendRequest() {
}
override fun close() {
uploadImageSessions.remove(this)
}
}
private inner class AddFriendSession(
private val qq: Long,
private val future: CompletableFuture<AddFriendResult>,
private val message: Lazy<String>
) : Closeable {
lateinit var id: ByteArray
fun onPacketReceived(packet: ServerPacket) {
if (!::id.isInitialized) {
return
}
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
return
}
when (packet.state) {
ServerCanAddFriendResponsePacket.State.FAILED -> {
future.complete(AddFriendResult.FAILED)
close()
}
ServerCanAddFriendResponsePacket.State.ALREADY_ADDED -> {
future.complete(AddFriendResult.ALREADY_ADDED)
close()
}
ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> {
session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
}
ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> {
}
}
}
}
}
fun sendAddRequest() {
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.packetIdLast })
}
override fun close() {
// uploadImageSessions.remove(this)
}
}
}

View File

@ -0,0 +1,53 @@
package net.mamoe.mirai.network.handler
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
import net.mamoe.mirai.network.BotNetworkHandlerImpl
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.ClientPacket
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.task.MiraiThreadPool
import java.io.Closeable
import java.util.concurrent.Future
/**
* 网络接口.
* 发包 / 处理包.
* 仅可通过 [BotNetworkHandlerImpl.socket] 得到实例.
*
* @author Him188moe
*/
interface DataPacketSocket : Closeable {
fun getOwner(): Bot
/**
* 分发数据包给 [PacketHandler]
*/
fun distributePacket(packet: ServerPacket)
/**
* 发送一个数据包(非异步).
*
* 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回.
*
* @see [LoginSession.expectPacket] kotlin DSL
*/
fun sendPacket(packet: ClientPacket)
/**
* 发送一个数据包(异步).
*
* 可通过 hook 事件 [ServerPacketReceivedEvent] 来获取服务器返回.
*
* @see [LoginSession.expectPacket] kotlin DSL
*/
fun sendPacketAsync(packet: ClientPacket): Future<*> {
return MiraiThreadPool.getInstance().submit {
sendPacket(packet)
}
}
fun isClosed(): Boolean
override fun close()
}

View File

@ -0,0 +1,91 @@
package net.mamoe.mirai.network.handler
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
import net.mamoe.mirai.event.hookWhile
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText
import net.mamoe.mirai.message.defaults.UnsolvedImage
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.network.packet.action.ClientSendFriendMessagePacket
import net.mamoe.mirai.network.packet.action.ClientSendGroupMessagePacket
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
import java.io.File
/**
* 处理消息事件, 承担消息发送任务.
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")
class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
internal var ignoreMessage: Boolean = true
init {
//todo for test
FriendMessageEvent::class.hookWhile {
if (session.socket.isClosed()) {
return@hookWhile false
}
when {
it.message() valueEquals "你好" -> it.qq.sendMessage("你好!")
it.message().toString().startsWith("复读") -> it.qq.sendMessage(it.message())
it.message().toString().startsWith("发群") -> {
it.message().list.toMutableList().let { messages ->
messages.removeAt(0)
sendGroupMessage(Group(session.bot, 580266363), MessageChain(messages))
}
}
it.message() valueEquals "发图片" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, it.qq).get()
})
}
return@hookWhile true
}
}
override fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerGroupUploadFileEventPacket -> {
//todo
}
is ServerFriendMessageEventPacket -> {
if (ignoreMessage) {
return
}
FriendMessageEvent(session.bot, session.bot.contacts.getQQ(packet.qq), packet.message).broadcast()
}
is ServerGroupMessageEventPacket -> {
//todo message chain
//GroupMessageEvent(this.bot, bot.contacts.getGroupByNumber(packet.groupNumber), bot.contacts.getQQ(packet.qq), packet.message)
}
is UnknownServerEventPacket -> {
//todo
}
is ServerSendFriendMessageResponsePacket,
is ServerSendGroupMessageResponsePacket -> {
//ignored
}
else -> {
//ignored
}
}
}
fun sendFriendMessage(qq: QQ, message: MessageChain) {
session.socket.sendPacketAsync(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message))
}
fun sendGroupMessage(group: Group, message: MessageChain) {
session.socket.sendPacket(ClientSendGroupMessagePacket(group.groupId, session.bot.account.qqNumber, session.sessionKey, message))
}
}

View File

@ -0,0 +1,42 @@
package net.mamoe.mirai.network.handler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.utils.MiraiSynchronizedLinkedList
import java.io.Closeable
/**
* 数据包(接受/发送)处理器
*/
abstract class PacketHandler(
val session: LoginSession
) : Closeable {
abstract fun onPacketReceived(packet: ServerPacket)
override fun close() {
}
}
class PacketHandlerNode<T : PacketHandler>(
val clazz: Class<T>,
val instance: T
)
fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> {
return PacketHandlerNode(this.javaClass, this)
}
class PacketHandlerList : MiraiSynchronizedLinkedList<PacketHandlerNode<*>>() {
fun <T : PacketHandler> get(clazz: Class<T>): T {
this.forEach {
if (it.clazz == clazz) {
@Suppress("UNCHECKED_CAST")
return@get it.instance as T
}
}
throw NoSuchElementException()
}
}

View File

@ -0,0 +1,49 @@
package net.mamoe.mirai.network.handler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.packet.ClientPacket
import net.mamoe.mirai.network.packet.ServerPacket
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
/**
* 临时数据包处理器
*
* @see LoginSession.expectPacket
*/
open class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>,
private val future: CompletableFuture<Unit>,
private val fromSession: LoginSession
) {
private lateinit var toSend: ClientPacket
private lateinit var expect: (P) -> Unit
lateinit var session: LoginSession//无需覆盖
fun toSend(packet: () -> ClientPacket) {
this.toSend = packet()
}
fun expect(handler: (P) -> Unit) {
this.expect = handler
}
fun send(session: LoginSession) {
this.session = session
session.socket.sendPacket(toSend)
}
fun onPacketReceived(session: LoginSession, packet: ServerPacket): Boolean {
if (expectationClass.isInstance(packet) && session === this.fromSession) {
@Suppress("UNCHECKED_CAST")
expect(packet as P)
future.complete(Unit)
return true
}
return false
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.packet
import lombok.Getter
@ -12,12 +14,12 @@ import java.security.MessageDigest
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
@Getter
val idHex: String
var encoded: Boolean = false
private var encoded: Boolean = false
init {
val annotation = this.javaClass.getAnnotation(PacketId::class.java)
@ -84,7 +86,6 @@ abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
}
@ExperimentalUnsignedTypes
@Throws(IOException::class)
fun DataOutputStream.writeIP(ip: String) {
for (s in ip.trim().split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
@ -97,14 +98,13 @@ fun DataOutputStream.writeTime() {
this.writeInt(System.currentTimeMillis().toInt())
}
@ExperimentalUnsignedTypes
@Throws(IOException::class)
fun DataOutputStream.writeHex(hex: String) {
for (s in hex.trim().split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
fun DataOutputStream.writeHex(uHex: String) {
for (s in uHex.trim().split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
if (s.isEmpty()) {
continue
}
this.writeByte(s.toUByte(16).toByte().toInt())
this.writeByte(s.toUByte(16).toInt())
}
}
@ -113,15 +113,13 @@ fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) {
}
fun DataOutputStream.encryptAndWrite(key: ByteArray, encoder: (ByteArrayDataOutputStream) -> Unit) {
this.write(TEA.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key))
this.write(TEA.encrypt(ByteArrayDataOutputStream().also(encoder).toByteArray(), key))
}
@ExperimentalUnsignedTypes
fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutputStream) -> Unit) {
this.encryptAndWrite(keyHex.hexToBytes(), encoder)
}
@ExperimentalUnsignedTypes
@Throws(IOException::class)
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
ByteArrayDataOutputStream().let {
@ -146,12 +144,10 @@ fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, lo
}
}
@ExperimentalUnsignedTypes
@TestedSuccessfully
@Tested
fun DataOutputStream.writeCRC32() = writeCRC32(getRandomByteArray(16))
@ExperimentalUnsignedTypes
fun DataOutputStream.writeCRC32(key: ByteArray) {
key.let {
write(it)//key
@ -159,8 +155,8 @@ fun DataOutputStream.writeCRC32(key: ByteArray) {
}
}
@ExperimentalUnsignedTypes
@TestedSuccessfully
@Tested
fun DataOutputStream.writeDeviceName(random: Boolean = false) {
val deviceName: String = if (random) {
String(getRandomByteArray(10))
@ -185,7 +181,7 @@ fun Int.toByteArray(): ByteArray = byteArrayOf(
/**
* 255u -> 00 00 00 FF
*/
@ExperimentalUnsignedTypes
fun UInt.toByteArray(): ByteArray = byteArrayOf(
(this.shr(24) and 255u).toByte(),
(this.shr(16) and 255u).toByte(),
@ -193,24 +189,14 @@ fun UInt.toByteArray(): ByteArray = byteArrayOf(
(this.shr(0) and 255u).toByte()
)
/**
* 255 -> FF 00 00 00
*/
fun Int.toLByteArray(): ByteArray = byteArrayOf(
(this.ushr(0) and 0xFF).toByte(),
(this.ushr(8) and 0xFF).toByte(),
(this.ushr(16) and 0xFF).toByte(),
(this.ushr(24) and 0xFF).toByte()
)
@ExperimentalUnsignedTypes
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUByteArray().toUHexString(separator)
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
internal fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
internal fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
@ExperimentalUnsignedTypes
@Throws(IOException::class)
fun DataOutputStream.writeZero(count: Int) {
repeat(count) {
@ -225,33 +211,26 @@ fun DataOutputStream.writeRandom(length: Int) {
}
}
@ExperimentalUnsignedTypes
@Throws(IOException::class)
fun DataOutputStream.writeQQ(qq: Long) {
this.write(qq.toUInt().toByteArray())
}
@ExperimentalUnsignedTypes
@Throws(IOException::class)
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
this.write(groupIdOrGroupNumber.toUInt().toByteArray())
}
fun DataOutputStream.writeVarByteArray(byteArray: ByteArray) {
fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size)
this.write(byteArray)
}
fun DataOutputStream.writeVarString(str: String) {
this.writeVarByteArray(str.toByteArray())
fun DataOutputStream.writeLVString(str: String) {
this.writeLVByteArray(str.toByteArray())
}
fun DataOutputStream.writeVarShort(short: Int) {
this.writeByte(0x02)
this.writeShort(short)
}
fun DataOutputStream.writeVarInt(int: Int) {
this.writeByte(0x04)
this.writeInt(int)
fun DataOutputStream.writeLVHex(hex: String) {
this.writeLVByteArray(hex.hexToBytes())
}

View File

@ -9,7 +9,7 @@ import java.io.DataInputStream
*
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 5C")
class ClientAccountInfoRequestPacket(
private val qq: Long,

View File

@ -7,7 +7,7 @@ import java.io.IOException
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 58")
class ClientHeartbeatPacket(
private val qq: Long,

View File

@ -6,9 +6,11 @@ import java.io.DataInputStream
/**
* SKey 用于 http api
*
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 1D")
class ClientSKeyRequestPacket(
private val qq: Long,
@ -29,7 +31,7 @@ class ClientSKeyRequestPacket(
* @author Him188moe
*/
@PacketId("00 1D")
@ExperimentalUnsignedTypes
class ClientSKeyRefreshmentRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.message.FaceID
@ -19,10 +21,10 @@ import java.util.zip.GZIPInputStream
*
* @author Him188moe
*/
open class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, val eventIdentity: ByteArray) : ServerPacket(input) {
abstract class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, val eventIdentity: ByteArray) : ServerPacket(input) {
@PacketId("00 17")
class Raw(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
@ExperimentalUnsignedTypes
fun distribute(): ServerEventPacket {
val eventIdentity = this.input.readNBytes(16)
val type = this.input.goto(18).readNBytes(2)
@ -80,6 +82,7 @@ class ServerGroupUploadFileEventPacket(input: DataInputStream, packetId: ByteArr
}//todo test
}
@Suppress("EXPERIMENTAL_API_USAGE")
class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
var groupNumber: Long = 0
var qq: Long = 0
@ -100,16 +103,17 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
OTHER,
}
@ExperimentalUnsignedTypes
override fun decode() {
println(this.input.goto(0).readAllBytes().toUHexString())
groupNumber = this.input.goto(51).readInt().toLong()
qq = this.input.goto(56).readLong().toUInt().toLong()
qq = this.input.goto(56).readLong()
val fontLength = this.input.goto(108).readShort()
//println(this.input.goto(110 + fontLength).readNBytesAt(2).toUHexString())//always 00 00
messageType = when (val id = this.input.goto(110 + fontLength + 2).readByte().toInt()) {
0x13 -> MessageType.NORMAL
0xE -> MessageType.XML
0x0E -> MessageType.XML
0x06 -> MessageType.AT
@ -119,7 +123,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
0x19 -> MessageType.ANONYMOUS
else -> {
MiraiLogger debug ("ServerGroupMessageEventPacket id=$id")
MiraiLogger.debug("ServerGroupMessageEventPacket id=$id")
MessageType.OTHER
}
}
@ -188,7 +192,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
var qq: Long = 0
lateinit var message: MessageChain
@ExperimentalUnsignedTypes
override fun decode() {
input.goto(0)
println()
@ -222,16 +226,16 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
//00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
val id1 = FaceID.ofId(readVarNumber().toInt())//可能这个是id, 也可能下面那个
val id1 = FaceID.ofId(readLVNumber().toInt())//可能这个是id, 也可能下面那个
this.skip(this.readByte().toLong())
this.readVarNumber()//某id?
this.readLVNumber()//某id?
return Face(id1)
}
0x06 -> {
this.skip(sectionLength - 37 - 1)
val imageId = String(this.readNBytes(36))
this.skip(1)//0x41
return Image(imageId)
return Image("{$imageId}.jpg")//todo 如何确定文件后缀??
}
else -> null
}
@ -291,8 +295,7 @@ B1 89 BE 09 8F 00 1A E5 00 0B 03 A2 09 90 BB 7A 1F 40 00 A6 00 00 00 20 00 05 00
* 告知服务器已经收到数据
*/
@PacketId("")//随后写入
@ExperimentalUnsignedTypes
class ClientMessageResponsePacket(
class ClientEventResponsePacket(
private val qq: Long,
private val packetIdFromServer: ByteArray,//4bytes
private val sessionKey: ByteArray,
@ -329,7 +332,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
lateinit var message: String
@ExperimentalUnsignedTypes
override fun decode() {
//start at Sep1.0:27
qq = input.readIntAt(0)

View File

@ -1,12 +1,23 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
//to simplify code
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
import net.mamoe.mirai.event.hookWhile
import net.mamoe.mirai.network.packet.PacketNameFormatter.adjustName
import net.mamoe.mirai.network.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageResponsePacket
import net.mamoe.mirai.network.packet.login.*
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.*
import java.io.DataInputStream
import java.io.EOFException
import kotlin.reflect.KClass
/**
* @author Him188moe
@ -45,7 +56,6 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
companion object {
@ExperimentalUnsignedTypes
fun ofByteArray(bytes: ByteArray): ServerPacket {
val stream = bytes.dataInputStream()
@ -72,7 +82,7 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
println(bytes.size)
return ServerLoginResponseFailedPacket(when (bytes.size) {
63, 319, 135, 351 -> LoginState.WRONG_PASSWORD//这四个其中一个是被冻结
63, 319, 135, 351 -> LoginState.WRONG_PASSWORD//这四个其中一个是被冻结
//135 -> LoginState.RETYPE_PASSWORD
279 -> LoginState.BLOCKED
263 -> LoginState.UNKNOWN_QQ_NUMBER
@ -111,6 +121,8 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
"00 A7" -> ServerCanAddFriendResponsePacket(stream)
"03 88" -> ServerTryUploadGroupImageResponsePacket.Encrypted(stream)
else -> throw IllegalArgumentException(idHex)
}
}.apply { this.idHex = idHex }
@ -118,9 +130,8 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
}
@ExperimentalUnsignedTypes
override fun toString(): String {
return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "encoded" }.joinToString(", ", "{", "}") {
return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "idByteArray" || it.name == "encoded" }.joinToString(", ", "{", "}") {
it.trySetAccessible(); it.name + "=" + it.get(this).let { value ->
when (value) {
is ByteArray -> value.toUHexString()
@ -145,38 +156,37 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
return decryptAsByteArray(key).dataInputStream()
}
@ExperimentalUnsignedTypes
fun decryptBy(keyHex: String): DataInputStream {
return this.decryptBy(keyHex.hexToBytes())
}
fun decryptBy(key1: ByteArray, key2: ByteArray): DataInputStream {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).dataInputStream();
return TEA.decrypt(this.decryptAsByteArray(key1), key2).dataInputStream()
}
@ExperimentalUnsignedTypes
fun decryptBy(key1: String, key2: ByteArray): DataInputStream {
return this.decryptBy(key1.hexToBytes(), key2)
}
@ExperimentalUnsignedTypes
fun decryptBy(key1: ByteArray, key2: String): DataInputStream {
return this.decryptBy(key1, key2.hexToBytes())
}
@ExperimentalUnsignedTypes
fun decryptBy(keyHex1: String, keyHex2: String): DataInputStream {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
private fun decryptAsByteArray(key: ByteArray): ByteArray {
fun decryptAsByteArray(key: ByteArray): ByteArray {
input.goto(14)
return TEA.decrypt(input.readAllBytes().cutTail(1), key)
}
}
@ExperimentalUnsignedTypes
fun DataInputStream.readIP(): String {
var buff = ""
for (i in 0..3) {
@ -207,7 +217,7 @@ fun ByteArray.dataInputStream(): DataInputStream = DataInputStream(this.inputStr
*/
infix fun <N : Number> DataInputStream.goto(position: N): DataInputStream {
this.reset()
this.skip(position.toLong());
this.skip(position.toLong())
return this
}
@ -221,7 +231,7 @@ fun <N : Number> DataInputStream.readNBytes(length: N): ByteArray {
}
fun DataInputStream.readVarNumber(): Number {
fun DataInputStream.readLVNumber(): Number {
return when (this.readShort().toInt()) {
1 -> this.readByte()
2 -> this.readShort()
@ -238,23 +248,92 @@ fun DataInputStream.readNBytesIn(range: IntRange): ByteArray {
fun <N : Number> DataInputStream.readIntAt(position: N): Int {
this.goto(position)
return this.readInt();
return this.readInt()
}
@ExperimentalUnsignedTypes
fun <N : Number> DataInputStream.readUIntAt(position: N): UInt {
this.goto(position)
return this.readNBytes(4).toUInt();
return this.readNBytes(4).toUInt()
}
fun <N : Number> DataInputStream.readByteAt(position: N): Byte {
this.goto(position)
return this.readByte();
return this.readByte()
}
fun <N : Number> DataInputStream.readShortAt(position: N): Short {
this.goto(position)
return this.readShort();
return this.readShort()
}
@JvmSynthetic
fun DataInputStream.gotoWhere(matcher: UByteArray): DataInputStream {
return this.gotoWhere(matcher.toByteArray())
}
/**
* 去往下一个含这些连续字节的位置
*/
@Throws(EOFException::class)
fun DataInputStream.gotoWhere(matcher: ByteArray): DataInputStream {
require(matcher.isNotEmpty())
loop@
do {
val byte = this.readByte()
if (byte == matcher[0]) {
//todo mark here
for (i in 1 until matcher.size) {
val b = this.readByte()
if (b != matcher[i]) {
continue@loop //todo goto mark
}
return this
}
}
} while (true)
}
@Suppress("UNCHECKED_CAST")
internal fun <P : ServerPacket> Bot.waitForPacket(packetClass: KClass<P>, timeoutMillis: Long = Long.MAX_VALUE, timeout: () -> Unit = {}) {
var got = false
ServerPacketReceivedEvent::class.hookWhile {
if (packetClass.isInstance(it.packet) && it.bot === this) {
got = true
true
} else {
false
}
}
MiraiThreadPool.getInstance().submit {
val startingTime = System.currentTimeMillis()
while (!got) {
if (System.currentTimeMillis() - startingTime > timeoutMillis) {
timeout.invoke()
return@submit
}
Thread.sleep(10)
}
}
}
/*
@Throws(EOFException::class)
fun DataInputStream.gotoWhere(matcher: ByteArray) {
require(matcher.isNotEmpty())
do {
val byte = this.readByte()
if (byte == matcher[0]) {
for (i in 1 until matcher.size){
}
}
} while (true)
}*/
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)

View File

@ -1,9 +1,7 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.getRandomByteArray
import net.mamoe.mirai.utils.lazyEncode
import java.io.DataInputStream
import java.net.InetAddress
@ -11,64 +9,67 @@ import java.net.InetAddress
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("08 28 04 34")
class ClientSessionRequestPacket(
private val qq: Long,
private val serverIp: String,
private val token38: ByteArray,
private val token88: ByteArray,
private val encryptionKey: ByteArray,
private val tlv0105: ByteArray
private val encryptionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeQQ(qq)
this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
this.writeHex("00 38")
this.write(token38)
this.write(TEA.encrypt(object : ByteArrayDataOutputStream() {
override fun toByteArray(): ByteArray {
this.writeHex("00 07 00 88")
this.write(token88)
this.writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00")
this.writeIP(serverIp)
this.writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1
this.writeHex("01 92 A5 D2 59 00 10 54 2D CF 9B 60 BF BB EC 0D D4 81 CE 36 87 DE 35 02 AE 6D ED DC 00 10 ")
this.writeHex(Protocol.fix0836)
this.writeHex("00 36 00 12 00 02 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00")
this.writeHex(Protocol.constantData1)
this.writeHex(Protocol.constantData2)
this.writeQQ(qq)
this.writeHex("00 00 00 00 00 1F 00 22 00 01")
this.writeHex("1A 68 73 66 E4 BA 79 92 CC C2 D4 EC 14 7C 8B AF 43 B0 62 FB 65 58 A9 EB 37 55 1D 26 13 A8 E5 3D")//device ID
this.write(tlv0105)
this.writeHex("01 0B 00 85 00 02")
this.writeHex("B9 ED EF D7 CD E5 47 96 7A B5 28 34 CA 93 6B 5C")//fix2
this.write(getRandomByteArray(1))
this.writeHex("10 00 00 00 00 00 00 00 02")
this.encryptAndWrite(encryptionKey) {
it.writeHex("00 07 00 88")
it.write(token88)
it.writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00")
it.writeIP(serverIp)
it.writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1
it.writeHex("01 92 A5 D2 59 00 10 54 2D CF 9B 60 BF BB EC 0D D4 81 CE 36 87 DE 35 02 AE 6D ED DC 00 10 ")
it.writeHex(Protocol.fix0836)
it.writeHex("00 36 00 12 00 02 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00")
it.writeHex(Protocol.constantData1)
it.writeHex(Protocol.constantData2)
it.writeQQ(qq)
it.writeHex("00 00 00 00 00 1F 00 22 00 01")
it.writeHex("1A 68 73 66 E4 BA 79 92 CC C2 D4 EC 14 7C 8B AF 43 B0 62 FB 65 58 A9 EB 37 55 1D 26 13 A8 E5 3D")//device ID
//fix3
this.writeHex("00 63 3E 00 63 02 04 03 06 02 00 04 00 52 D9 00 00 00 00 A9 58 3E 6D 6D 49 AA F6 A6 D9 33 0A E7 7E 36 84 03 01 00 00 68 20 15 8B 00 00 01 02 00 00 03 00 07 DF 00 0A 00 0C 00 01 00 04 00 03 00 04 20 5C 00")
this.write(getRandomByteArray(32))//md5 32
this.writeHex("68")
//tlv0106
it.writeHex("01 05 00 30")
it.writeHex("00 01 01 02 00 14 01 01 00 10")
it.writeRandom(16)
it.writeHex("00 14 01 02 00 10")
it.writeRandom(16)
this.writeHex("00 00 00 00 00 2D 00 06 00 01")
this.writeIP(InetAddress.getLocalHost().hostAddress)
it.writeHex("01 0B 00 85 00 02")
it.writeHex("B9 ED EF D7 CD E5 47 96 7A B5 28 34 CA 93 6B 5C")//fix2
it.writeRandom(1)
it.writeHex("10 00 00 00 00 00 00 00 02")
return super.toByteArray()
}
}.toByteArray(), encryptionKey))
//fix3
it.writeHex("00 63 3E 00 63 02 04 03 06 02 00 04 00 52 D9 00 00 00 00 A9 58 3E 6D 6D 49 AA F6 A6 D9 33 0A E7 7E 36 84 03 01 00 00 68 20 15 8B 00 00 01 02 00 00 03 00 07 DF 00 0A 00 0C 00 01 00 04 00 03 00 04 20 5C 00")
it.writeRandom(32)//md5 32
it.writeHex("68")
it.writeHex("00 00 00 00 00 2D 00 06 00 01")
it.writeIP(InetAddress.getLocalHost().hostAddress)
}
}
}
/**
* @author Him188moe
*/
@PacketId("08 28 04 34")
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) {
lateinit var sessionKey: ByteArray
lateinit var tlv0105: ByteArray
@ExperimentalUnsignedTypes
override fun decode() {
when (dataLength) {
407 -> {

View File

@ -1,6 +1,7 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.login.ClientPasswordSubmissionPacket
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.hexToBytes
@ -11,11 +12,12 @@ import java.io.IOException
/**
* A packet received when logging in, used to redirect server address
*
* @see net.mamoe.mirai.network.packet.client.login.ClientServerRedirectionPacket
* @see net.mamoe.mirai.network.packet.client.login.ClientPasswordSubmissionPacket
* @see ClientServerRedirectionPacket
* @see ClientPasswordSubmissionPacket
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@PacketId("08 25 31 01")
class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) {
var serverIP: String? = null
@ -29,7 +31,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
TYPE_08_25_31_02,
}
@ExperimentalUnsignedTypes
override fun decode() {
when (val id = input.readByte().toUByte().toInt()) {
0xFE -> {
@ -52,7 +54,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
}
class Encrypted(private val type: Type, inputStream: DataInputStream) : ServerPacket(inputStream) {
@ExperimentalUnsignedTypes
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
Type.TYPE_08_25_31_02 -> Protocol.redirectionKey.hexToBytes()
Type.TYPE_08_25_31_01 -> Protocol.key0825.hexToBytes()
@ -65,10 +67,10 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
*
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("08 25 31 01")
class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() {
@ExperimentalUnsignedTypes
@Throws(IOException::class)
override fun encode() {
this.writeQQ(qq)
@ -80,7 +82,7 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
it.writeHex(Protocol.constantData2)
it.writeQQ(qq)
it.writeHex("00 00 00 00 03 09 00 08 00 01")
it.writeIP(serverIp);
it.writeIP(serverIp)
it.writeHex("00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19")
it.writeHex(Protocol.publicKey)
}
@ -92,10 +94,10 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
*
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("08 25 31 02")
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
@ExperimentalUnsignedTypes
override fun encode() {
this.writeQQ(qq)
this.writeHex(Protocol.fixVer)

View File

@ -1,13 +1,15 @@
package net.mamoe.mirai.network.packet
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.hexToBytes
import java.io.DataInputStream
/**
* 客户端请求验证码图片数据的第几部分
*/
@ExperimentalUnsignedTypes
@PacketId("00 BA 31")
class ClientVerificationCodeTransmissionRequestPacket(
private val packetId: Int,
@ -16,11 +18,8 @@ class ClientVerificationCodeTransmissionRequestPacket(
private val verificationSequence: Int,
private val token00BA: ByteArray
) : ClientPacket() {
@TestedSuccessfully
@Tested
override fun encode() {
MiraiLogger debug "packetId=$packetId"
MiraiLogger debug "verificationSequence=$verificationSequence"
this.writeByte(packetId)//part of packet id
this.writeQQ(qq)
@ -47,7 +46,7 @@ class ClientVerificationCodeTransmissionRequestPacket(
* 提交验证码
*/
@PacketId("00 BA 32")
@ExperimentalUnsignedTypes
class ClientVerificationCodeSubmitPacket(
private val packetIdLast: Int,
private val qq: Long,
@ -90,39 +89,11 @@ class ClientVerificationCodeSubmitPacket(
}
}
@ExperimentalUnsignedTypes
fun main() {
val token0825 = "6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2".hexToBytes()
val verificationCode = "AAAA"
val verificationToken = "84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94".hexToBytes()
//00 02 00 00 08 04 01 E0 00 00 04 53 00 00 00 01 00 00 15 85 01 00 38 6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2 01 03 00 19 02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3 14 00 05 00 00 00 00 00 04 58 51 4E 44 00 38 84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94 00 10 69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A
ByteArrayDataOutputStream().let {
it.writeHex("00 02 00 00 08 04 01 E0")
it.writeHex(Protocol.constantData2)
it.writeHex("01 00 38")
it.write(token0825)
it.writeHex("01 03")
it.writeShort(25)
it.writeHex(Protocol.publicKey)
it.writeHex("14 00 05 00 00 00 00 00 04")
it.write(verificationCode.substring(0..3).toByteArray())
it.writeHex("00 38")
it.write(verificationToken)
it.writeHex("00 10")
it.writeHex(Protocol.key00BAFix)
println(it.toByteArray().toUHexString())
}
}
/**
* 刷新验证码
*/
@PacketId("00 BA 31")
@ExperimentalUnsignedTypes
class ClientVerificationCodeRefreshPacket(
private val packetIdLast: Int,
private val qq: Long,
@ -152,7 +123,7 @@ class ClientVerificationCodeRefreshPacket(
}
/**
* 验证码输入错误
* 验证码输入错误, 同时也会给一部分验证码
*/
@PacketId("00 BA 32")
class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId)
@ -171,7 +142,7 @@ open class ServerVerificationCodeTransmissionPacket(input: DataInputStream, priv
lateinit var token00BA: ByteArray//40 bytes
var packetIdLast: Int = 0
@ExperimentalUnsignedTypes
override fun decode() {
this.verificationToken = this.input.readNBytesAt(10, 56)
@ -210,7 +181,7 @@ class ServerVerificationCodeCorrectPacket(input: DataInputStream) : ServerVerifi
lateinit var token00BA: ByteArray//56 bytes
@ExperimentalUnsignedTypes
override fun decode() {
token00BA = this.input.readNBytesAt(10, 56)
}
@ -220,7 +191,7 @@ abstract class ServerVerificationCodePacket(input: DataInputStream) : ServerPack
@PacketId("00 BA")
class Encrypted(input: DataInputStream, private val id: String) : ServerPacket(input) {
@ExperimentalUnsignedTypes
fun decrypt(): ServerVerificationCodePacket {
this.input goto 14
val data = TEA.decrypt(this.input.readAllBytes().cutTail(1), Protocol.key00BA.hexToBytes())

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.packet.action
import net.mamoe.mirai.network.Protocol
@ -13,7 +15,7 @@ import java.util.*
* @author Him188moe
*/
@PacketId("00 A7")
@ExperimentalUnsignedTypes
class ClientCanAddFriendPacket(
val bot: Long,
val qq: Long,
@ -47,7 +49,7 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
FAILED,
}
@ExperimentalUnsignedTypes
override fun decode() {
val data = input.goto(0).readAllBytes()
if (data.size == 99) {
@ -78,7 +80,7 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
* 请求添加好友
*/
@PacketId("00 AE")
@ExperimentalUnsignedTypes
class ClientAddFriendPacket(
val bot: Long,
val qq: Long,
@ -111,7 +113,7 @@ class ServerAddGroupResponsePacket(input: DataInputStream) : ServerAddContactRes
/**
* 添加好友/群的回复
*/
open class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) {
abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) {
class Raw(input: DataInputStream) : ServerPacket(input) {

View File

@ -10,7 +10,7 @@ import java.io.DataInputStream
* @author Him188moe
*/
@PacketId("00 CD")
@ExperimentalUnsignedTypes
class ClientSendFriendMessagePacket(
private val botQQ: Long,
private val targetQQ: Long,
@ -19,6 +19,7 @@ class ClientSendFriendMessagePacket(
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(botQQ)
this.writeHex(Protocol.fixVer2)
@ -40,7 +41,6 @@ class ClientSendFriendMessagePacket(
it.writeHex(Protocol.friendMessageConst1)//... 85 E9 BB 91
it.writeZero(2)
it.write(message.toByteArray())
/*

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.network.packet.action
import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.toUHexString
@ -9,12 +10,11 @@ import java.io.DataInputStream
* @author Him188moe
*/
@PacketId("00 02")
@ExperimentalUnsignedTypes
class ClientSendGroupMessagePacket(
private val groupId: Long,//不是 number
private val botQQ: Long,
private val sessionKey: ByteArray,
private val message: String
private val message: MessageChain
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
@ -25,20 +25,24 @@ class ClientSendGroupMessagePacket(
val bytes = message.toByteArray()
it.writeByte(0x2A)
it.writeGroup(groupId)
it.writeShort(56 + bytes.size)
it.writeHex("00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
it.writeShort(50 + bytes.size)
it.writeHex("00 01 01")
it.writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
it.writeTime()
it.writeRandom(4)
it.writeHex("Protocol.messageConst1")
it.writeHex("00 00 00 00 09 00 86")
it.writeHex(Protocol.friendMessageConst1)
it.writeZero(2)
//messages
it.writeByte(0x01)
it.write(bytes)
/*it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
it.writeShort(bytes.size)
it.write(bytes)
it.write(bytes)*/
println(it.toByteArray().toUHexString())
}

View File

@ -1,35 +0,0 @@
package net.mamoe.mirai.network.packet.image
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.writeProtoInt
import java.awt.image.BufferedImage
/**
* 查询群消息的 image id.
* That is, 查询服务器上是否有这个图片, 有就返回 id, 没有就需要上传
*
* @author Him188moe
*/
@PacketId("03 88")
@ExperimentalUnsignedTypes
class ClientGetGroupImageIDPacket(
val bot: Long,
val sessionKey: ByteArray,
val group: Long,
val image: BufferedImage
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)
this.writeQQ(bot)
this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00")
this.encryptAndWrite(sessionKey) {
it.writeHex("00 00 00 07 00 00 00 5E 08 01 12 03 98 01 01 10 01 1A")
it.writeHex("5A")
it.writeHex("08")
it.writeProtoInt(group)
it.writeHex("08")
it.writeProtoInt(image.height)
}
}
}

View File

@ -0,0 +1,132 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.packet.image
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.writeUVarInt
import java.awt.image.BufferedImage
import java.io.DataInputStream
/**
* 请求上传图片. 将发送图片的 md5, size.
* 服务器返回以下之一:
* - 服务器已经存有这个图片 [ServerTryUploadGroupImageFailedPacket]
* - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryUploadGroupImageSuccessPacket]
*
* @author Him188moe
*/
@PacketId("03 88")
class ClientTryGetImageIDPacket(
private val botNumber: Long,
private val sessionKey: ByteArray,
private val groupNumberOrQQNumber: Long,//todo 为什么还要有qq number呢? bot不就是了么
private val image: BufferedImage
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)
this.writeQQ(botNumber)
this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00")
val byteArray = image.toByteArray()
this.encryptAndWrite(sessionKey) {
it.writeZero(3)
it.writeHex("07 00")
it.writeZero(2)
it.writeHex("5E")
it.writeHex("08")
it.writeHex("01 12 03 98 01 01 10 01")
it.writeHex("1A")
it.writeHex("5A")
it.writeHex("08")
it.writeUVarInt(groupNumberOrQQNumber)
it.writeHex("10")
it.writeUVarInt(botNumber)
it.writeHex("18 00")
it.writeHex("22")
it.writeHex("10")
it.write(md5(byteArray))
it.writeHex("28")
it.writeUVarInt(byteArray.size.toUInt())
it.writeHex("32")
it.writeHex("1A")
it.writeHex("37 00 4D 00 32 00 25 00 4C 00 31 00 56 00 32 00 7B 00 39 00 30 00 29 00 52 00")
it.writeHex("38 01")
it.writeHex("48 01")
it.writeHex("50")
it.writeUVarInt(image.width.toUInt())
it.writeHex("58")
it.writeUVarInt(image.height.toUInt())
it.writeHex("60 04")
it.writeHex("6A")
it.writeHex("05")
it.writeHex("32 36 36 35 36")
it.writeHex("70 00")
it.writeHex("78 03")
it.writeHex("80 01")
it.writeHex("00")
}
}
}
abstract class ServerTryUploadGroupImageResponsePacket(input: DataInputStream) : ServerPacket(input) {
class Encrypted(input: DataInputStream) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryUploadGroupImageResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
println(data.size)
println(data.size)
if (data.size == 209) {
return ServerTryUploadGroupImageSuccessPacket(data.dataInputStream()).setId(this.idHex)
}
return ServerTryUploadGroupImageFailedPacket(data.dataInputStream())
}
}
}
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
class ServerTryUploadGroupImageSuccessPacket(input: DataInputStream) : ServerTryUploadGroupImageResponsePacket(input) {
lateinit var uKey: ByteArray
override fun decode() {
uKey = this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u)).readNBytes(128)
}
}
/**
* 服务器已经存有这个图片
*/
class ServerTryUploadGroupImageFailedPacket(input: DataInputStream) : ServerTryUploadGroupImageResponsePacket(input) {
override fun decode() {
}
}
fun main() {
println(0xff)
}

View File

@ -9,7 +9,7 @@ import net.mamoe.mirai.utils.ClientLoginStatus
*
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 EC")
class ClientChangeOnlineStatusPacket(
private val qq: Long,
@ -29,4 +29,6 @@ class ClientChangeOnlineStatusPacket(
it.writeHex("00 01 00 01 00 04 00 00 00 00")
}
}
}
}

View File

@ -4,7 +4,7 @@ import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.*
import net.mamoe.mirai.utils.ByteArrayDataOutputStream
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.hexToBytes
import java.io.DataOutputStream
@ -14,8 +14,8 @@ import java.io.DataOutputStream
* @author Him188moe
*/
@PacketId("08 36 31 03")
@ExperimentalUnsignedTypes
@TestedSuccessfully
@Tested
class ClientPasswordSubmissionPacket(
private val qq: Long,
private val password: String,
@ -24,7 +24,7 @@ class ClientPasswordSubmissionPacket(
private val tgtgtKey: ByteArray,
private val token0825: ByteArray
) : ClientPacket() {
@ExperimentalUnsignedTypes
override fun encode() {
this.writeQQ(qq)
this.writeHex(Protocol.passwordSubmissionKey1)
@ -40,21 +40,21 @@ class ClientPasswordSubmissionPacket(
}
@PacketId("08 36 31 04")
@ExperimentalUnsignedTypes
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006)
@PacketId("08 36 31 05")
@ExperimentalUnsignedTypes
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, null)
@PacketId("08 36 31 06")
@ExperimentalUnsignedTypes
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006)
@ExperimentalUnsignedTypes
open class ClientLoginResendPacket internal constructor(
val qq: Long,
val password: String,
@ -93,7 +93,7 @@ open class ClientLoginResendPacket internal constructor(
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) {
//this.writeInt(System.currentTimeMillis().toInt())
@ -130,7 +130,7 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I
this.writeHex("60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6")//key
}
@ExperimentalUnsignedTypes
private fun DataOutputStream.writePart2() {
this.writeHex("03 12")//tag

View File

@ -34,11 +34,6 @@ enum class LoginState {
*/
TAKEN_BACK,
/**
* 需要验证码登录
*/
VERIFICATION_CODE,
/**
* 未知. 更换服务器或等几分钟再登录可能解决.
*/

View File

@ -4,7 +4,7 @@ import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.goto
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.Tested
import java.io.DataInputStream
/**
@ -23,7 +23,7 @@ class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Fla
var tokenUnknown: ByteArray? = null
lateinit var tgtgtKey: ByteArray//16bytes
@TestedSuccessfully
@Tested
override fun decode() {
this.input.skip(5)
tgtgtKey = this.input.readNBytes(16)//22
@ -46,8 +46,8 @@ class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Fla
}
class Encrypted(input: DataInputStream, private val flag: Flag) : ServerPacket(input) {
@ExperimentalUnsignedTypes
@TestedSuccessfully
@Tested
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseKeyExchangePacket {
return ServerLoginResponseKeyExchangePacket(this.decryptBy(Protocol.shareKey, tgtgtKey), flag).setId(this.idHex)
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.packet.login
import net.mamoe.mirai.network.Protocol
@ -5,7 +7,7 @@ import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.goto
import net.mamoe.mirai.network.packet.readNBytesAt
import net.mamoe.mirai.network.packet.readString
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
@ -21,8 +23,8 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
lateinit var encryptionKey: ByteArray
@TestedSuccessfully
@ExperimentalUnsignedTypes
@Tested
override fun decode() {
this.input.skip(7)//8
this.encryptionKey = this.input.readNBytes(16)//24
@ -52,7 +54,7 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
class Encrypted(input: DataInputStream) : ServerPacket(input) {
@ExperimentalUnsignedTypes
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseSuccessPacket {
input goto 14
return ServerLoginResponseSuccessPacket(this.decryptBy(Protocol.shareKey, tgtgtKey)).setId(this.idHex)

View File

@ -1,10 +1,12 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.packet.login
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.dataInputStream
import net.mamoe.mirai.network.packet.goto
import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.hexToUBytes
import java.io.DataInputStream
@ -20,8 +22,8 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
var unknownBoolean: Boolean? = null
@TestedSuccessfully
@ExperimentalUnsignedTypes
@Tested
override fun decode() {
val verifyCodeLength = this.input.goto(78).readShort()//2bytes
this.verifyCodePart1 = this.input.readNBytes(verifyCodeLength.toInt())
@ -39,7 +41,7 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
}
@ExperimentalUnsignedTypes
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket {
this.input goto 14
val data = this.decryptBy(Protocol.shareKey).goto(0).readAllBytes()

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.network.packet.login
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.ServerPacket
import java.io.DataInputStream
@ -8,4 +9,5 @@ import java.io.DataInputStream
*
* @author Him188moe
*/
@PacketId("00 EC")
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input)

View File

@ -1,10 +1,9 @@
package net.mamoe.mirai.utils;
import net.mamoe.mirai.contact.Contact;
import net.mamoe.mirai.utils.config.MiraiSynchronizedLinkedListMap;
/**
* @author Him188moe
*/
public class ContactList<C extends Contact> extends MiraiSynchronizedLinkedListMap<Long, C> {
public class ContactList<C extends Contact> extends MiraiSynchronizedLinkedHashMap<Long, C> {
}

View File

@ -1,36 +1,31 @@
package net.mamoe.mirai.utils;
import org.apache.commons.httpclient.util.HttpURLConnection;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author NaturalHG
*/
public class ImageNetworkUtils {
public static void postImage(String ukey, int fileSize, String g_uin,String groupCode, byte[] img){
public static boolean postImage(String uKeyHex, int fileSize, long qqNumber, long groupCode, byte[] img) throws IOException {
//http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc&ukey= 删全部空 (ukey) &filesize= 到文本 (fileSize) &range=0&uin= g_uin &groupcode= Group
StringBuilder builder = new StringBuilder("http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc");
builder.append("&ukey=")
.append(ukey.trim())
.append("&filezise=").append(fileSize)
.append("&range=").append("0")
.append("&uin=").append(g_uin)
.append("&groupcode=").append(groupCode);
try {
HttpURLConnection conn = (HttpURLConnection) new URL(builder.toString()).openConnection();
conn.setRequestProperty("User-agent","QQClient");
conn.setRequestProperty("Content-length","" + fileSize);
conn.setRequestMethod("POST");
conn.getOutputStream().write(img);
String builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" + "&ukey=" +
uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
"&uin=" + qqNumber +
"&groupcode=" + groupCode;
HttpURLConnection conn = (HttpURLConnection) new URL(builder).openConnection();
conn.setRequestProperty("User-agent", "QQClient");
conn.setRequestProperty("Content-length", "" + fileSize);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.getOutputStream().write(img);
conn.connect();
System.out.println(conn.getResponseCode());
} catch (IOException e) {
e.printStackTrace();
}
conn.connect();
return conn.getResponseCode() == 200;
}
}

View File

@ -1,6 +1,8 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.packet.ServerPacket
import net.mamoe.mirai.network.packet.goto
import java.text.SimpleDateFormat
import java.util.*
@ -11,20 +13,20 @@ import java.util.*
* @author NaturalHG
*/
object MiraiLogger {
infix fun log(o: Any?) = info(o)
infix fun println(o: Any?) = info(o)
infix fun info(o: Any?) = this.print(o.toString(), LoggerTextFormat.RESET)
fun log(o: Any?) = info(o)
fun println(o: Any?) = info(o)
fun info(o: Any?) = this.print(o.toString(), LoggerTextFormat.RESET)
infix fun error(o: Any?) = this.print(o.toString(), LoggerTextFormat.RED)
fun error(o: Any?) = this.print(o.toString(), LoggerTextFormat.RED)
infix fun notice(o: Any?) = this.print(o.toString(), LoggerTextFormat.LIGHT_BLUE)
fun notice(o: Any?) = this.print(o.toString(), LoggerTextFormat.LIGHT_BLUE)
infix fun success(o: Any?) = this.print(o.toString(), LoggerTextFormat.GREEN)
fun success(o: Any?) = this.print(o.toString(), LoggerTextFormat.GREEN)
infix fun debug(o: Any?) = this.print(o.toString(), LoggerTextFormat.YELLOW)
fun debug(o: Any?) = this.print(o.toString(), LoggerTextFormat.YELLOW)
infix fun catching(e: Throwable) {
fun catching(e: Throwable) {
e.printStackTrace()
/*
this.print(e.message)
@ -39,21 +41,26 @@ object MiraiLogger {
}
}
infix fun Bot.log(o: Any?) = info(o)
infix fun Bot.println(o: Any?) = info(o)
infix fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
fun Bot.log(o: Any?) = info(o)
fun Bot.println(o: Any?) = info(o)
fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
infix fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
infix fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
infix fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
infix fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
fun Bot.cyanL(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
infix fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
infix fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
fun Bot.debugPacket(packet: ServerPacket) {
debug("Packet=$packet")
debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
}
@Synchronized

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.utils.config;
package net.mamoe.mirai.utils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -16,15 +16,15 @@ import java.util.function.Function;
*
* @author NaturalHG
*/
public class MiraiSynchronizedLinkedListMap<K,V> extends AbstractMap<K,V> {
public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
public MiraiSynchronizedLinkedListMap(){
public MiraiSynchronizedLinkedHashMap() {
this.sortedMap = Collections.synchronizedMap(new LinkedHashMap<>());
}
protected final Map<K, V> sortedMap;
public MiraiSynchronizedLinkedListMap(LinkedHashMap<K,V> map){
public MiraiSynchronizedLinkedHashMap(LinkedHashMap<K, V> map) {
this.sortedMap = Collections.synchronizedMap(map);
}
@ -155,13 +155,13 @@ public class MiraiSynchronizedLinkedListMap<K,V> extends AbstractMap<K,V> {
return this.sortedMap.merge(key,value,remappingFunction);
}
public boolean equals(MiraiSynchronizedLinkedListMap o) {
public boolean equals(MiraiSynchronizedLinkedHashMap o) {
return this.sortedMap.equals(o.sortedMap);
}
@Override
public boolean equals(Object o) {
return o instanceof MiraiSynchronizedLinkedListMap?this.equals((MiraiSynchronizedLinkedListMap)o):super.equals(o);
return o instanceof MiraiSynchronizedLinkedHashMap ? this.equals((MiraiSynchronizedLinkedHashMap) o) : super.equals(o);
}
}

View File

@ -0,0 +1,195 @@
package net.mamoe.mirai.utils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
/**
* @author Him188moe
*/
public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> {
@SuppressWarnings("WeakerAccess")
protected final List<E> syncList;
public MiraiSynchronizedLinkedList() {
this.syncList = Collections.synchronizedList(new LinkedList<>());
}
public MiraiSynchronizedLinkedList(Collection<E> collection) {
this.syncList = Collections.synchronizedList(new LinkedList<>(collection));
}
@Override
public E get(int index) {
return this.syncList.get(index);
}
@Override
public void forEach(Consumer<? super E> action) {
this.syncList.forEach(action);
}
@Override
public Spliterator<E> spliterator() {
return this.syncList.spliterator();
}
@Override
public Stream<E> stream() {
return this.syncList.stream();
}
@Override
public Stream<E> parallelStream() {
return this.syncList.parallelStream();
}
@Override
public int size() {
return this.syncList.size();
}
@SuppressWarnings("SuspiciousToArrayCall")
@Override
public <T> T[] toArray(IntFunction<T[]> generator) {
return this.syncList.toArray(generator);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
return this.syncList.removeIf(filter);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
this.syncList.replaceAll(operator);
}
@Override
public void sort(Comparator<? super E> c) {
this.syncList.sort(c);
}
@Override
public boolean add(E e) {
return this.syncList.add(e);
}
@Override
public E set(int index, E element) {
return this.syncList.set(index, element);
}
@Override
public void add(int index, E element) {
this.syncList.add(index, element);
}
@Override
public E remove(int index) {
return this.syncList.remove(index);
}
@Override
public int indexOf(Object o) {
return this.syncList.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return this.syncList.lastIndexOf(o);
}
@Override
public void clear() {
this.syncList.clear();
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return this.syncList.addAll(index, c);
}
@NotNull
@Override
public Iterator<E> iterator() {
return this.syncList.iterator();
}
@NotNull
@Override
public ListIterator<E> listIterator() {
return this.syncList.listIterator();
}
@NotNull
@Override
public ListIterator<E> listIterator(int index) {
return this.syncList.listIterator(index);
}
@NotNull
@Override
public List<E> subList(int fromIndex, int toIndex) {
return this.syncList.subList(fromIndex, toIndex);
}
@Override
public int hashCode() {
return this.syncList.hashCode();
}
@Override
public boolean isEmpty() {
return this.syncList.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.syncList.contains(o);
}
@NotNull
@Override
public Object[] toArray() {
return this.syncList.toArray();
}
@SuppressWarnings("SuspiciousToArrayCall")
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
return this.syncList.toArray(a);
}
@Override
public boolean remove(Object o) {
return this.syncList.remove(o);
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
return this.syncList.containsAll(c);
}
@Override
public boolean addAll(@NotNull Collection<? extends E> c) {
return this.syncList.addAll(c);
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
return this.syncList.removeAll(c);
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
return this.syncList.retainAll(c);
}
}

View File

@ -1,48 +0,0 @@
package net.mamoe.mirai.utils
import java.io.DataOutputStream
/**
* Google ProtocolBuff 的一些算法实现
*
* @author Him188moe
*/
/**
* 128(10000000) -> 0x7F (10000000_10000001)
*
* TODO improve
*/
@ExperimentalUnsignedTypes
fun DataOutputStream.writeProtoFixedInt(int: Long) {
if (int == 0xFFL) {
this.writeShort(0x80_01)//unsigned//1000000010000001
return
}
this.writeByte((int.rem(0xFF) + 0xFF).toInt())
this.writeByte((int / 0xFF).toInt())
}
/**
* 127(1111111(7)) -> 0x7F (11111111(8))
*
* TODO improve
*/
@ExperimentalUnsignedTypes
fun DataOutputStream.writeProtoInt(int: Long) {
if (int < 0xFF) {
this.writeByte((int and 0xFF).toInt())//10000000
return
}
this.writeProtoFixedInt(int)
}
@ExperimentalUnsignedTypes
fun main() {
println()
println(lazyEncode {
it.writeProtoInt(128)
}.toUHexString())
}

View File

@ -1,277 +1,269 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.Protocol
import java.nio.ByteBuffer
import java.util.*
import kotlin.experimental.and
import kotlin.experimental.xor
/**
* @author Him188moe
*/
/**
* TEA 加密
*
* @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java
*/
class TEA(private val key: ByteArray) {
object TEA {
private const val UINT32_MASK = 0xffffffffL
private 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
companion object {
val CRYPTOR_SHARE_KEY = TEA(Protocol.hexToBytes(Protocol.shareKey))
val CRYPTOR_0825KEY = TEA(Protocol.hexToBytes(Protocol.key0825))
val mKey = LongArray(4)
private val UINT32_MASK = 0xffffffffL
for (i in 0..3) {
mKey[i] = pack(key, i * 4, 4)
}
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: Boolean = true
fun rand(): Int {
return mRandom.nextInt()
}
val mKey = LongArray(4)
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()
}
for (i in 0..3) {
mKey[i] = pack(key, i * 4, 4)
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++
}
fun rand(): Int {
return mRandom.nextInt()
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 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])
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
}
System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8)
mIndexPos = 0
while (mIndexPos < 8) {
val out_pos = mOutPos + mIndexPos
mOutput[out_pos] = (mOutput[out_pos] 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
}
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
}
mIV = decode(mIV, 0)
mOutPos += 8
mIndexPos = 0
return true
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
@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
}
fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray? {
require(!(len % 8 != 0 || len < 16)) { "must len % 8 == 0 && len >= 16" }
mIV = decode(cipherText, offset)
mIndexPos = (mIV[0] and 7).toInt()
var plen = len - mIndexPos - 10
isFirstBlock = true
if (plen < 0) {
return null
var g = 0
while (g < 2) {
if (mIndexPos < 8) {
mInBlock[mIndexPos++] = (rand() and 0xff).toByte()
++g
}
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)) {
throw RuntimeException("Unable to decode")
}
}
if (mIndexPos == 8) {
encodeOneBlock()
}
}
var outpos = 0
while (plen != 0) {
if (mIndexPos < 8) {
mOutput[outpos++] = if (isFirstBlock)
mIV[mIndexPos]
else
(cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos])
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)) { "must len % 8 == 0 && len >= 16" }
mIV = decode(cipherText, offset)
mIndexPos = (mIV[0] and 7).toInt()
var plen = len - mIndexPos - 10
isFirstBlock = true
if (plen < 0) {
return null
}
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)) {
throw RuntimeException("Unable to decode")
}
}
}
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)) {
throw RuntimeException("Unable to decode")
}
}
plen--
}
g = 0
while (g < 7) {
if (mIndexPos < 8) {
if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) {
throw RuntimeException()
} else {
++mIndexPos
}
if (mIndexPos == 8) {
mPreOutPos = mOutPos - 8
isFirstBlock = false
if (!decodeOneBlock(cipherText, offset, len)) {
throw RuntimeException("Unable to decode")
}
}
plen--
}
g = 0
while (g < 7) {
if (mIndexPos < 8) {
if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) {
throw RuntimeException()
} else {
++mIndexPos
}
if (mIndexPos == 8) {
mPreOutPos = mOutPos
if (!decodeOneBlock(cipherText, offset, len)) {
throw RuntimeException("Unable to decode")
}
if (mIndexPos == 8) {
mPreOutPos = mOutPos
if (!decodeOneBlock(cipherText, offset, len)) {
throw RuntimeException("Unable to decode")
}
}
g++
}
return mOutput
g++
}
return mOutput
}
return if (encrypt) {
encrypt(data, 0, data.size)
} else {
try {
return decrypt(data, 0, data.size)!!
} catch (e: Exception) {
println("Source: " + data.toUHexString(" "))
println("Key: " + key.toUHexString(" "))
throw e
}
return if (encrypt) {
encrypt(data, 0, data.size)
} else {
try {
return decrypt(data, 0, data.size)!!
} catch (e: Exception) {
println("Source: " + data.toUHexString(" "))
println("Key: " + key.toUHexString(" "))
throw e
}
}
fun encrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, true)
}
fun encrypt(source: ByteArray, keyHex: String): ByteArray {
return encrypt(source, keyHex.hexToBytes())
}
fun decrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, false)
}
fun decrypt(source: ByteArray, keyHex: String): ByteArray {
return decrypt(source, keyHex.hexToBytes())
}
private fun pack(bytes: ByteArray, offset: Int, len: Int): Long {
var result: Long = 0
val max_offset = if (len > 8) offset + 8 else offset + len
for (index in offset until max_offset) {
result = result shl 8 or (bytes[index].toLong() and 0xffL)
}
return result shr 32 or (result and UINT32_MASK)
}
}
fun encrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, true)
}
@Suppress("unused")
fun encrypt(source: ByteArray, keyHex: String): ByteArray {
return encrypt(source, keyHex.hexToBytes())
}
fun decrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, false)
}
fun decrypt(source: ByteArray, keyHex: String): ByteArray {
return decrypt(source, keyHex.hexToBytes())
}
@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)
}
}

View File

@ -5,4 +5,4 @@ package net.mamoe.mirai.utils
*
* @author Him188moe
*/
internal annotation class TestedSuccessfully
internal annotation class Tested

View File

@ -1,12 +1,16 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.Protocol
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.io.File
import java.lang.reflect.Field
import java.util.*
import java.util.zip.CRC32
import javax.imageio.ImageIO
/**
* @author Him188moe
@ -24,14 +28,14 @@ fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(s
return@joinToString ret
}
@ExperimentalUnsignedTypes
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
@ExperimentalUnsignedTypes
@JvmSynthetic
fun ByteArray.toUHexString(): String = this.toUByteArray().toUHexString()
@ExperimentalUnsignedTypes
@JvmSynthetic
fun UByteArray.toUHexString(separator: String = " "): String {
return this.joinToString(separator) {
@ -43,36 +47,36 @@ fun UByteArray.toUHexString(separator: String = " "): String {
}
}
@ExperimentalUnsignedTypes
@JvmSynthetic
fun UByteArray.toUHexString(): String = this.toUHexString(" ")
@ExperimentalUnsignedTypes
fun Byte.toUHexString(): String = this.toUByte().toString(16)
@ExperimentalUnsignedTypes
fun String.hexToBytes(): ByteArray = Protocol.hexToBytes(this)
@ExperimentalUnsignedTypes
fun String.hexToUBytes(): UByteArray = Protocol.hexToUBytes(this)
@ExperimentalUnsignedTypes
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
@ExperimentalUnsignedTypes
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)
open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream()) {
open fun toByteArray(): ByteArray = (out as ByteArrayOutputStream).toByteArray()
@ExperimentalUnsignedTypes
open fun toUByteArray(): UByteArray = (out as ByteArrayOutputStream).toByteArray().toUByteArray()
}
@JvmSynthetic
fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().let { t(it); return it.toByteArray() }
fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().also(t).toByteArray()
@ExperimentalUnsignedTypes
fun getRandomByteArray(length: Int): ByteArray {
val bytes = LinkedList<Byte>()
repeat(length) { bytes.add((Math.random() * 255).toByte()) }
@ -134,4 +138,10 @@ fun ByteArray.removeZeroTail(): ByteArray {
--i
}
return this.copyOfRange(0, i + 1)
}
fun BufferedImage.toByteArray(formatName: String = "PNG"): ByteArray {
return lazyEncode {
ImageIO.write(this, formatName, it)
}
}

View File

@ -0,0 +1,133 @@
@file:JvmName("Varint")
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.IOException
import java.io.InputStream
import kotlin.experimental.or
/**
* Tool class for VarInt or VarLong operations.
*
* Some code from http://wiki.vg/Protocol.
*
* @author MagicDroidX of Nukkit Project
* @author lmlstarqaq of Nukkit Project
*/
fun encodeZigZag32(signedInt: Int): Long {
return (signedInt shl 1 xor (signedInt shr 31)).toLong()
}
@JvmSynthetic
fun decodeZigZag32(uint: UInt): Int {
return decodeZigZag32(uint.toLong())
}
fun decodeZigZag32(uint: Long): Int {
return (uint shr 1).toInt() xor -(uint and 1).toInt()
}
fun encodeZigZag64(signedLong: Long): Long {
return signedLong shl 1 xor (signedLong shr 63)
}
fun decodeZigZag64(signedLong: Long): Long {
return signedLong.ushr(1) xor -(signedLong and 1)
}
@Throws(IOException::class)
fun DataInputStream.readVarInt(): Int {
return decodeZigZag32(this.readUnsignedVarInt())
}
@Throws(IOException::class)
fun DataInputStream.readUnsignedVarInt(): UInt {
return read(this, 5).toUInt()
}
@Throws(IOException::class)
fun DataInputStream.readVarLong(): Long {
return decodeZigZag64(readUnsignedVarLong().toLong())
}
@Throws(IOException::class)
fun DataInputStream.readUnsignedVarLong(): ULong {
return read(this, 10).toULong()
}
@Throws(IOException::class)
fun DataOutputStream.writeVarInt(signedInt: Int) {
this.writeUVarInt(encodeZigZag32(signedInt))
}
@Throws(IOException::class)
fun DataOutputStream.writeUVarInt(uint: UInt) {
return writeUVarInt(uint.toLong())
}
@Throws(IOException::class)
fun DataOutputStream.writeUVarInt(uint: Long) {
this.write0(uint)
}
@Throws(IOException::class)
fun DataOutputStream.writeVarLong(signedLong: Long) {
this.writeUVarLong(encodeZigZag64(signedLong))
}
@Throws(IOException::class)
fun DataOutputStream.writeUVarLong(ulong: Long) {
this.write0(ulong)
}
@Throws(IOException::class)
private fun DataOutputStream.write0(long: Long) {
var value = long
do {
var temp = (value and 127).toByte()
value = value ushr 7
if (value != 0L) {
temp = temp or 128.toByte()
}
this.writeByte(temp.toInt())
} while (value != 0L)
}
@Throws(IOException::class)
private fun read(stream: DataInputStream, maxSize: Int): Long {
var value: Long = 0
var size = 0
var b = stream.readByte().toInt()
while (b and 0x80 == 0x80) {
value = value or ((b and 0x7F).toLong() shl size++ * 7)
require(size < maxSize) { "VarLong too big" }
b = stream.readByte().toInt()
}
return value or ((b and 0x7F).toLong() shl size * 7)
}
@Throws(IOException::class)
private fun read(stream: InputStream, maxSize: Int): Long {
var value: Long = 0
var size = 0
var b = stream.read()
while (b and 0x80 == 0x80) {
value = value or ((b and 0x7F).toLong() shl size++ * 7)
require(size < maxSize) { "VarLong too big" }
b = stream.read()
}
return value or ((b and 0x7F).toLong() shl size * 7)
}

View File

@ -1,5 +1,6 @@
package net.mamoe.mirai.utils.config;
import net.mamoe.mirai.utils.MiraiSynchronizedLinkedHashMap;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
@ -10,7 +11,7 @@ import java.util.function.Supplier;
/**
* @author NaturalHG
*/
public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String, T> {
public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedHashMap<String, T> {
public MiraiConfigSection(){
super();

View File

@ -1,47 +0,0 @@
import net.mamoe.mirai.network.packet.login.ClientPasswordSubmissionPacket
import net.mamoe.mirai.utils.toUHexString
@ExperimentalUnsignedTypes
fun main(){
/*
val data = "00 37 13 08 25 31 01 EB 10 08 30 69 50 1C 84 A9 C2 16 D7 52 B9 1C 79 CA 5A CF FD BC EB 10 08 30 69 50 1C 84 A9 C2 16 D7 52 B9 1C 79 CA 5A CF FD BC AE D8 A6 BB DC 21 6E 79 26 E1 A2 23 11 AA B0 9A AE D8 A6 BB DC 21 6E 79 26 E1 A2 23 11 AA B0 9A 76 E4 B8 DD 03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D C3 47 F0 25 A1 8E 74 EF 1E 0B 32 5B 20 8A FA 3B 0B 52 8F 86 E6 04 F1 D6 F8 63 75 60 8C 0C 7D 06 D1 E0 22 F8 49 EF AF 61 EE 7E 69 72 EB 10 08 30 69 50 1C 84 A9 C2 16 D7 52 B9 1C 79 CA 5A CF FD BC AE D8 A6 BB DC 21 6E 79 26 E1 A2 23 11 AA B0 9A 49 39 72 ED 61 12 B6 88 4D A2 56 23 E9 92 11 92 27 4A 70 00 C9 01 7B 03";
val s = DataInputStream(data.hexToBytes().inputStream())
val packet = ServerTouchResponsePacket(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, s)
packet.decode()
System.out.println(packet.token.toUByteArray().toUHexString(" "))
System.out.println(packet.loginTime.toUHexString(" "))
System.out.println(packet.loginIP)
*/
// val packet = ClientPasswordSubmissionPacket(1994701021,"xiaoqqq",)
/*
val data = "00 01 09 00 70 00 01 5C 71 80 A6 BA 20 62 2E C1 BE BF F2 47 37 40 A1 00 38 91 25 85 58 18 D3 67 77 2C 4D 02 D8 66 A6 F7 3E 57 D8 CE 01 47 7F D0 8F 13 C8 3A E5 19 A2 60 BC 4C 9A 35 4E 92 9F 21 48 6C 67 68 36 6B 94 C1 6F 11 8D 55 6B 04 9A 22 C3 00 20 29 7E D4 A7 16 02 07 14 41 90 3A 65 06 AC CB 28 AB 90 DB 46 33 C9 C0 1D 06 44 7A 92 17 C3 A5 F3 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 01 07 01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D B6 00 09 3A 80 00 00 00 3C 00 02 2E 29 4E 47 5F 68 78 2C 47 25 5F 59 50 65 60 5D 00 88 00 04 5D 59 45 17 B7 5F F8 D4 00 00 00 00 00 78 38 E0 3B 23 4A C5 0E 93 CB C1 66 96 37 8B 46 B2 86 23 3F 2D 09 45 E0 16 1C E9 9C 11 7A FA 2D A8 50 47 42 74 01 06 84 76 0E 5F C6 04 29 1A 4A 65 AA 93 49 DF BD 00 ED 80 B8 26 CA 80 E8 20 6D 15 43 DD D8 E6 48 C2 8A 5A F8 70 6B 51 3A E2 2D 21 95 4B 6A 75 A8 90 CA B1 C0 E5 73 99 D7 59 D8 DD 3D C9 5C E4 49 61 22 11 60 85 48 C4 7D E0 84 62 AD B3 13 84 61 C1 9E 19 35 41 44 44 37 3F 21 33 64 4B 37 5D 77 6D 61 3F 00 78 00 01 5D 59 45 17 00 70 A4 D9 44 9E 95 51 B4 B0 91 CC 1E DB 34 F9 F7 13 8B 30 08 C0 AE 33 22 9C FF 87 CF 9B A2 B0 E5 E1 D0 E0 AD DD 8F E9 F6 1E 01 1F AA 74 46 66 B4 81 54 B9 29 E5 FC 0B 7F C9 13 AE 32 BA D6 55 2E B0 A1 30 24 B6 F2 E7 62 F9 2E 00 E4 51 61 50 7C D1 36 E8 61 96 36 FF B7 32 74 3C 2A F7 74 63 DA 7D 57 84 18 ED 84 E9 D8 87 6D 66 1D D5 84 D4 23 99 00 83 01 63 2A 69 2E 25 79 28 3B 29 33 29 40 28 54 7E 21 00 70 00 01 5D 59 45 17 00 68 6F 1F FB 31 7B D7 B7 D8 91 32 D7 20 8B 8A F6 02 C8 22 E5 24 8C 25 F2 6A C5 B0 ED 35 01 BF AF 42 72 33 4E FB 3F D3 02 BA F4 46 2B 68 20 0B E3 39 81 B1 D3 8A E0 1B 0F 69 D1 70 AE 49 A5 24 4F BB 58 4F F8 31 A0 37 4C CD F1 12 35 80 99 7D 25 CA F9 E9 45 B6 B0 57 56 66 61 C5 7B 90 57 BF E2 2C 94 91 80 1A B0 D7 21 A8 44 2C 33 4A 29 77 5F 71 40 41 38 3D 7A 41 65 33 01 08 00 29 00 01 00 25 00 1D 02 5B 14 28 E0 B9 91 E2 80 A2 CC 80 CF 89 E2 80 A2 CC 81 E0 B9 91 29 02 13 80 02 00 05 00 04 00 00 00 01 01 15 00 10 F9 86 85 81 30 F6 1B E0 E7 97 98 F6 46 C3 4F B2"
val s = DataInputStream(data.hexToBytes().inputStream())
val packet = ServerLoginResponseSuccessPacket(s,(data.length+1)/3)
packet.decode()
System.out.println("0828key: \n" + packet._0828_rec_decr_key.toUByteArray().toUHexString(" "))
System.out.println("token88: \n" + packet.token88.toUByteArray().toUHexString(" "))
System.out.println("token38: \n" + packet.token38.toUByteArray().toUHexString(" "))
System.out.println("enckey: \n" + packet.encryptionKey.toUByteArray().toUHexString(" "))
System.out.println("nick: " + packet.nick)
System.out.println("age: " + packet.age)
System.out.println("gender: " + packet.gender)
*/
/*
val data = "FB 01 04 03 33 00 01 00 BA 02 03 2C 13 00 05 01 00 00 01 23 00 38 F5 C3 CF F4 B4 27 C5 8F 9B D3 ED 18 73 7D E9 CB 43 1F 57 43 BE D3 1B 9A F5 26 2B F4 D9 43 14 9A ED 3B C3 6C E5 7F 4E B0 0C BA 55 57 18 06 78 E1 13 A7 B2 A8 7F 47 E1 1C 02 BC 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 6C 50 4C 54 45 F6 F8 F4 E3 EE FD E5 FD FF EE FD F0 F6 F9 E7 F7 F9 EC F4 FE FF F7 F7 F9 F5 F4 FF EC F7 FF E6 F4 E0 F5 FC F8 49 5B 8F CB DB FA BB C9 F5 2B 40 7C D6 E6 FF FD FB FF ED F1 F0 2A 43 B3 D6 F6 FF FC F1 FC 1C 38 91 A7 BD F6 63 74 BD 70 8B CC 34 4F A5 DE F3 F1 75 88 E7 A4 B7 D3 73 82 A1 8C A3 DC F3 FB D8 4D 68 B9 94 A8 FB 8D 9C B6 CB 42 B0 8E 00 00 0A 6F 49 44 41 54 58 C3 B4 57 89 76 EB 2A 12 14 20 B1 09 10 42 68 DF 2C FB FF FF 71 0A 79 49 E2 E4 CE CC 79 37 8F 13 59 B6 72 EC 2E AA BB AB 9A 2C FB 61 89 8F 95 E5 79 A9 70 CB B2 22 C7 2A 65 DB 5E 72 21 F1 A9 90 B2 28 C4 F9 AF 5F 5F 05 30 9C 28 D2 4B C6 58 F9 8C 92 17 39 13 6D 9B FF EB 10 8A 3B 0F 2F 10 52 64 45 51 64 42 A6 37 79 21 DA FC F2 6F 40 08 9F AE B7 9C A4 C8 27 31 F2 C4 50 A4 D4 9C F9 01 BC A2 90 0F 08 E1 2F AE 1F E3 7E 7D 24 52 F4 67 2C 91 02 E3 CA 13 2F 78 97 00 86 DF 4E C4 23 56 F1 F8 7B 10 90 42 3D D2 93 EE 27 04 F1 80 F0 4B 99 10 4F 62 5E D9 2D EE 57 8A F1 A0 E1 64 20 2B 55 F9 C8 C3 67 08 F8 81 7F F2 F7 BE FD 67 1D 9E 9B 4F EB 89 25 21 28 19 63 09 5D A9 94 2A 59 AA 87 B3 32 4E 84 FF 3C 13 8F AF 06 F1 29 FD E2 C1 02 54 E0 8C 5E 5C 2E A5 72 43 65 F4 E5 72 61 5C 4A 45 81 41 97 79 0A 9E FF 62 22 C2 EB 7E 42 48 45 80 2B 0F 58 B9 66 D6 1C 63 75 D3 97 BC F4 08 0F 08 5C 6B 8D CC 14 1F 10 C4 5F B4 C3 37 66 EE E5 78 CF 42 12 05 CD F5 4D 29 EB 36 AD 19 82 CF 76 35 C6 86 B2 94 A7 36 FC 62 39 BE A8 B8 D7 02 78 08 8F 66 E4 44 45 DF BB AB A3 34 92 14 DF 54 C6 32 1D 00 21 7F 41 08 7F C9 7F F8 28 88 4F B5 90 89 3A 21 90 9E DF E8 DE 34 4D 8C D1 EF C6 AC 6B 7F 74 8E B1 FA 84 90 7F B0 F0 CF 5A E2 27 4C AF 8E 28 EF 24 48 B2 AF B1 9A 96 3E F6 43 75 38 6F 29 5D 1A C7 59 78 87 F0 1B B2 F0 95 05 3C 93 B2 26 B6 9F BA 18 AB AE 59 EC 88 17 17 A9 8F 4B B3 CE 60 A1 CD F2 CB 03 42 10 7F D9 06 1F 76 80 8E 4F 6D 9F 56 99 05 25 A5 DD AA EB 62 7A 6F 87 E6 B8 0E 5D 33 C6 99 C6 C4 02 24 EB 74 8C 87 98 64 F8 5E A9 9E 1E F7 72 FB FF B3 21 8A A4 00 01 00 28 F9 59 C5 E6 34 43 53 95 C8 17 2E 62 78 BF E8 27 BF 20 BA 11 5A 74 D1 7C D0 95 6C F6 A3 41 D2 84 BD 7D F6 64 BC 27 40 50 01 15 00 10 44 98 EB B8 30 3B DE 7D 2B CC 4C 41 B3 1C 92 86"
val s = DataInputStream(data.hexToBytes().inputStream())
val packet = ServerLoginResponseVerificationCodePacket(s,(data.length+1)/3)
packet.decode()
println(packet.token00BA.toUByteArray().toUHexString(" "))
println(packet.verifyCode.toUByteArray().toUHexString(" "))
println(packet.verifyCodeLength)
File(System.getProperty("user.dir") + "/5.png").createNewFile()
packet.verifyCode.inputStream().transferTo(FileOutputStream(System.getProperty("user.dir") + "/5.png"))
*/
val packet = ClientPasswordSubmissionPacket(1994701021, "xiaoqqq", 131513, "123.123.123.123", "tgtgtKey".toByteArray(), "".toByteArray())
packet.encodeToByteArray().toUByteArray().toUHexString(" ")
}

36
pom.xml
View File

@ -17,10 +17,12 @@
</modules>
<repositories>
<!--
<repository>
<id>mamoe-repo</id>
<url>http://mamoe.net:8081/repository/public/</url>
</repository>
-->
<repository>
<id>huawei</id>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
@ -52,17 +54,24 @@
<version>1.18.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.5.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
@ -90,12 +99,6 @@
<version>4.1.38.Final</version>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.5.2</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
@ -123,12 +126,11 @@
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>