mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-25 04:50:26 +08:00
Updated docs, migrated TEA encryption from java to kotlin, fixed bugs
This commit is contained in:
parent
87532418e5
commit
ece200d499
mirai-core/src/main/java/net/mamoe/mirai
Bot.javaMiraiMain.javaMiraiServer.java
contact
event/events/bot
network
RobotNetworkHandler.kt
packet
plugin
task
utils
@ -17,20 +17,28 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||
* <br>
|
||||
* {@link Bot} 由 2 个模块组成.
|
||||
* {@link Bot} 由 3 个模块组成.
|
||||
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
|
||||
* {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
|
||||
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
|
||||
* <br>
|
||||
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
|
||||
* 若你需要得到服务器上所有机器人列表, 请访问 {@link Bot#instances}
|
||||
*
|
||||
* <p>
|
||||
* 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 BotAccount}, which stores the account information(e.g. qq number the bot)
|
||||
* <br>
|
||||
* To get all the QQ contacts, access {@link Bot#account}
|
||||
* To get all the Robot instance, access {@link Bot#instances}
|
||||
* </p>
|
||||
*
|
||||
* @author Him188moe
|
||||
* @author NatrualHG
|
||||
* @see net.mamoe.mirai.contact.Contact
|
||||
*
|
||||
* <p>
|
||||
* Bot that is the base of the whole program.
|
||||
* It contains a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group}.
|
||||
*/
|
||||
public final class Bot implements Closeable {
|
||||
public static final List<Bot> instances = Collections.synchronizedList(new LinkedList<>());
|
||||
|
@ -1,12 +1,8 @@
|
||||
package net.mamoe.mirai;
|
||||
|
||||
|
||||
import net.mamoe.mirai.event.MiraiEventHook;
|
||||
import net.mamoe.mirai.event.MiraiEventManager;
|
||||
import net.mamoe.mirai.event.events.qq.FriendMessageEvent;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public final class MiraiMain {
|
||||
private static MiraiServer server;
|
||||
|
@ -23,6 +23,9 @@ import java.util.Scanner;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* Mirai 服务器.
|
||||
* 管理一些基础的事务
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiServer {
|
||||
|
@ -6,9 +6,12 @@ import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import net.mamoe.mirai.message.defaults.PlainText
|
||||
|
||||
/**
|
||||
* 联系人.
|
||||
*
|
||||
* A contact is a [QQ] or a [Group] for one particular [Bot] instance only.
|
||||
*
|
||||
* @param bot Owner [Bot]
|
||||
* @param bot the Owner [Bot]
|
||||
* @param number the id number of this contact
|
||||
* @author Him188moe
|
||||
*/
|
||||
abstract class Contact internal constructor(val bot: Bot, val number: Long) {
|
||||
|
@ -7,7 +7,11 @@ import net.mamoe.mirai.utils.ContactList
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* 群
|
||||
* 群.
|
||||
*
|
||||
* Group ID 与 Group Number 并不是同一个值.
|
||||
* - Group Number([Group.number]) 是通常使用的群号码.(在 QQ 客户端中可见)
|
||||
* - Group ID([Group.groupId]) 是与服务器通讯时使用的 id.(在 QQ 客户端中不可见)
|
||||
*
|
||||
* Java 获取 groupNumber: `group.getNumber()`
|
||||
* Java 获取所属 bot: `group.getBot()`
|
||||
@ -15,6 +19,7 @@ import java.io.Closeable
|
||||
* Java 获取 groupId: `group.getGroupId()`
|
||||
*
|
||||
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
|
||||
* @author Him188moe
|
||||
*/
|
||||
class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
|
||||
val groupId = groupNumberToId(number)
|
||||
|
@ -6,6 +6,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public abstract class BotEvent extends MiraiEvent {
|
||||
private final Bot bot;
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
package net.mamoe.mirai.event.events.bot
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.MiraiEvent
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class BotLoginEvent(val bot: Bot) : MiraiEvent()
|
||||
|
||||
class BotLogoutEvent(val bot: Bot) : MiraiEvent()
|
||||
|
||||
class BotMessageReceivedEvent(val bot: Bot, val type: Type, val message: String) : MiraiEvent() {
|
||||
enum class Type {
|
||||
FRIEND,
|
||||
GROUP
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@ package net.mamoe.mirai.event.events.bot;
|
||||
|
||||
import net.mamoe.mirai.Bot;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public final class BotLoginSucceedEvent extends BotEvent {
|
||||
|
||||
public BotLoginSucceedEvent(Bot bot) {
|
||||
|
@ -423,7 +423,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
|
||||
//登录成功后会收到大量上次的消息, 忽略掉
|
||||
MiraiThreadPool.getInstance().schedule({
|
||||
messageHandler.ignoreMessage = false
|
||||
}, 2, TimeUnit.SECONDS)
|
||||
}, 3, TimeUnit.SECONDS)
|
||||
|
||||
this.tlv0105 = packet.tlv0105
|
||||
sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
|
||||
@ -485,7 +485,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
|
||||
* 处理消息事件, 承担消息发送任务.
|
||||
*/
|
||||
inner class MessageHandler : PacketHandler() {
|
||||
internal var ignoreMessage: Boolean = false
|
||||
internal var ignoreMessage: Boolean = true
|
||||
|
||||
init {
|
||||
//todo for test
|
||||
|
@ -112,10 +112,6 @@ fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) {
|
||||
this.write(TEA.encrypt(byteArray, key))
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, cryptor: TEA) {
|
||||
this.write(cryptor.encrypt(byteArray))
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(key: ByteArray, encoder: (ByteArrayDataOutputStream) -> Unit) {
|
||||
this.write(TEA.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key))
|
||||
}
|
||||
@ -125,10 +121,6 @@ fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutp
|
||||
this.encryptAndWrite(keyHex.hexToBytes(), encoder)
|
||||
}
|
||||
|
||||
fun DataOutputStream.encryptAndWrite(cryptor: TEA, encoder: (ByteArrayDataOutputStream) -> Unit) {
|
||||
this.write(cryptor.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }))
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
|
||||
|
@ -67,7 +67,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
|
||||
*/
|
||||
@ExperimentalUnsignedTypes
|
||||
@PacketId("08 25 31 01")
|
||||
class ClientTouchPacket(val qq: Long, val serverIp: String) : ClientPacket() {
|
||||
class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() {
|
||||
@ExperimentalUnsignedTypes
|
||||
@Throws(IOException::class)
|
||||
override fun encode() {
|
||||
@ -75,19 +75,15 @@ class ClientTouchPacket(val qq: Long, val serverIp: String) : ClientPacket() {
|
||||
this.writeHex(Protocol.fixVer)
|
||||
this.writeHex(Protocol.key0825)
|
||||
|
||||
this.write(TEA.CRYPTOR_0825KEY.encrypt(object : ByteArrayDataOutputStream() {
|
||||
@Throws(IOException::class)
|
||||
override fun toByteArray(): ByteArray {
|
||||
this.writeHex(Protocol.constantData1)
|
||||
this.writeHex(Protocol.constantData2)
|
||||
this.writeQQ(qq)
|
||||
this.writeHex("00 00 00 00 03 09 00 08 00 01")
|
||||
this.writeIP(serverIp);
|
||||
this.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")
|
||||
this.writeHex(Protocol.publicKey)
|
||||
return super.toByteArray()
|
||||
}
|
||||
}.toByteArray()))
|
||||
this.encryptAndWrite(Protocol.key0825) {
|
||||
it.writeHex(Protocol.constantData1)
|
||||
it.writeHex(Protocol.constantData2)
|
||||
it.writeQQ(qq)
|
||||
it.writeHex("00 00 00 00 03 09 00 08 00 01")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@ package net.mamoe.mirai.network.packet.action
|
||||
|
||||
/**
|
||||
* 添加好友结果
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
enum class AddFriendResult {
|
||||
/**
|
||||
|
@ -1,10 +1,9 @@
|
||||
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.cutTail
|
||||
import net.mamoe.mirai.network.packet.dataInputStream
|
||||
import net.mamoe.mirai.network.packet.goto
|
||||
import net.mamoe.mirai.utils.TEA
|
||||
import net.mamoe.mirai.utils.TestedSuccessfully
|
||||
import net.mamoe.mirai.utils.hexToUBytes
|
||||
import java.io.DataInputStream
|
||||
@ -40,9 +39,10 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket {
|
||||
this.input goto 14
|
||||
val data = TEA.CRYPTOR_SHARE_KEY.decrypt(this.input.readAllBytes().cutTail(1));
|
||||
val data = this.decryptBy(Protocol.shareKey).goto(0).readAllBytes()
|
||||
return ServerLoginResponseVerificationCodeInitPacket(data.dataInputStream(), data.size).setId(this.idHex)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import net.mamoe.mirai.Bot;
|
||||
*
|
||||
* @see net.mamoe.mirai.event.MiraiEventManager
|
||||
* @see net.mamoe.mirai.event.MiraiEventManagerKt
|
||||
* @author Him188moe
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public abstract class MiraiPluginBase {
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.mamoe.mirai.plugin;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiPluginManager {
|
||||
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.mamoe.mirai.task;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MiraiTaskExceptionHandler {
|
||||
void onHandle(Throwable e);
|
||||
|
@ -10,6 +10,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public final class MiraiTaskManager {
|
||||
|
||||
private static MiraiTaskManager instance = new MiraiTaskManager();
|
||||
|
@ -3,6 +3,9 @@ package net.mamoe.mirai.task;
|
||||
import java.io.Closeable;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public final class MiraiThreadPool extends ScheduledThreadPoolExecutor implements Closeable {
|
||||
private static MiraiThreadPool instance = new MiraiThreadPool();
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public final class CharImageUtil {
|
||||
|
||||
public static String createCharImg(BufferedImage image) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
/**
|
||||
* QQ 在线状态
|
||||
*
|
||||
* @author Him188moe
|
||||
* @see net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket
|
||||
*/
|
||||
public enum ClientLoginStatus {
|
||||
/**
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class EventException extends RuntimeException {
|
||||
private final Throwable cause;
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public enum LoggerTextFormat {
|
||||
RESET("\33[0m"),
|
||||
|
||||
|
@ -6,6 +6,9 @@ import java.util.*
|
||||
|
||||
/**
|
||||
* used to replace old logger
|
||||
*
|
||||
* @author Him188moe
|
||||
* @author NaturalHG
|
||||
*/
|
||||
object MiraiLogger {
|
||||
infix fun log(o: Any?) = info(o)
|
||||
|
@ -1,263 +0,0 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
import net.mamoe.mirai.network.Protocol;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* TEA 加密
|
||||
*
|
||||
* @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java
|
||||
*/
|
||||
public final class TEA {
|
||||
public static final TEA CRYPTOR_SHARE_KEY = new TEA(Protocol.INSTANCE.hexToBytes(Protocol.shareKey));
|
||||
public static final TEA CRYPTOR_0825KEY = new TEA(Protocol.INSTANCE.hexToBytes(Protocol.key0825));
|
||||
|
||||
private static final long UINT32_MASK = 0xffffffffL;
|
||||
private final long[] mKey;
|
||||
private final Random mRandom;
|
||||
private final byte[] key;
|
||||
private byte[] mOutput;
|
||||
private byte[] mInBlock;
|
||||
private int mIndexPos;
|
||||
private byte[] mIV;
|
||||
private int mOutPos;
|
||||
private int mPreOutPos;
|
||||
private boolean isFirstBlock;
|
||||
|
||||
public TEA(byte[] key) {
|
||||
this.key = key;
|
||||
mKey = new long[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
mKey[i] = pack(key, i * 4, 4);
|
||||
}
|
||||
mRandom = new Random();
|
||||
isFirstBlock = true;
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] source, byte[] key) {
|
||||
return new TEA(key).encrypt(source);
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] source, String keyHex) {
|
||||
return encrypt(source, UtilsKt.hexToBytes(keyHex));
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] source, byte[] key) {
|
||||
return new TEA(key).decrypt(source);
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] source, String keyHex) {
|
||||
return decrypt(source, UtilsKt.hexToBytes(keyHex));
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static long pack(byte[] bytes, int offset, int len) {
|
||||
long result = 0;
|
||||
int max_offset = len > 8 ? offset + 8 : offset + len;
|
||||
for (int index = offset; index < max_offset; index++) {
|
||||
result = result << 8 | ((long) bytes[index] & 0xffL);
|
||||
}
|
||||
return result >> 32 | result & UINT32_MASK;
|
||||
}
|
||||
|
||||
private int rand() {
|
||||
return mRandom.nextInt();
|
||||
}
|
||||
|
||||
private byte[] encode(byte[] bytes) {
|
||||
long v0 = pack(bytes, 0, 4);
|
||||
long v1 = pack(bytes, 4, 4);
|
||||
long sum = 0;
|
||||
long delta = 0x9e3779b9L;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
sum = (sum + delta) & UINT32_MASK;
|
||||
v0 += ((v1 << 4) + mKey[0]) ^ (v1 + sum) ^ ((v1 >>> 5) + mKey[1]);
|
||||
v0 &= UINT32_MASK;
|
||||
v1 += ((v0 << 4) + mKey[2]) ^ (v0 + sum) ^ ((v0 >>> 5) + mKey[3]);
|
||||
v1 &= UINT32_MASK;
|
||||
}
|
||||
return ByteBuffer.allocate(8).putInt((int) v0).putInt((int) v1).array();
|
||||
}
|
||||
|
||||
private byte[] decode(byte[] bytes, int offset) {
|
||||
long v0 = pack(bytes, offset, 4);
|
||||
long v1 = pack(bytes, offset + 4, 4);
|
||||
long delta = 0x9e3779b9L;
|
||||
long sum = (delta << 4) & UINT32_MASK;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
v1 -= ((v0 << 4) + mKey[2]) ^ (v0 + sum) ^ ((v0 >>> 5) + mKey[3]);
|
||||
v1 &= UINT32_MASK;
|
||||
v0 -= ((v1 << 4) + mKey[0]) ^ (v1 + sum) ^ ((v1 >>> 5) + mKey[1]);
|
||||
v0 &= UINT32_MASK;
|
||||
sum = (sum - delta) & UINT32_MASK;
|
||||
}
|
||||
return ByteBuffer.allocate(8).putInt((int) v0).putInt((int) v1).array();
|
||||
}
|
||||
|
||||
private void encodeOneBlock() {
|
||||
for (mIndexPos = 0; mIndexPos < 8; mIndexPos++) {
|
||||
mInBlock[mIndexPos] = isFirstBlock ?
|
||||
mInBlock[mIndexPos]
|
||||
: ((byte) (mInBlock[mIndexPos] ^ mOutput[mPreOutPos + mIndexPos]));
|
||||
}
|
||||
|
||||
System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8);
|
||||
for (mIndexPos = 0; mIndexPos < 8; mIndexPos++) {
|
||||
int out_pos = mOutPos + mIndexPos;
|
||||
mOutput[out_pos] = (byte) (mOutput[out_pos] ^ mIV[mIndexPos]);
|
||||
}
|
||||
System.arraycopy(mInBlock, 0, mIV, 0, 8);
|
||||
mPreOutPos = mOutPos;
|
||||
mOutPos += 8;
|
||||
mIndexPos = 0;
|
||||
isFirstBlock = false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean decodeOneBlock(byte[] ciphertext, int offset, int len) {
|
||||
for (mIndexPos = 0; mIndexPos < 8; mIndexPos++) {
|
||||
if (mOutPos + mIndexPos < len) {
|
||||
mIV[mIndexPos] = (byte) (mIV[mIndexPos] ^ ciphertext[mOutPos + offset + mIndexPos]);
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
mIV = decode(mIV, 0);
|
||||
mOutPos += 8;
|
||||
mIndexPos = 0;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private byte[] encrypt(byte[] plaintext, int offset, int len) {
|
||||
mInBlock = new byte[8];
|
||||
mIV = new byte[8];
|
||||
mOutPos = 0;
|
||||
mPreOutPos = 0;
|
||||
isFirstBlock = true;
|
||||
mIndexPos = (len + 10) % 8;
|
||||
if (mIndexPos != 0) {
|
||||
mIndexPos = 8 - mIndexPos;
|
||||
}
|
||||
mOutput = new byte[mIndexPos + len + 10];
|
||||
mInBlock[0] = (byte) (rand() & 0xf8 | mIndexPos);
|
||||
for (int i = 1; i <= mIndexPos; i++) {
|
||||
mInBlock[i] = (byte) (rand() & 0xff);
|
||||
}
|
||||
++mIndexPos;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
mIV[i] = 0;
|
||||
}
|
||||
|
||||
int g = 0;
|
||||
while (g < 2) {
|
||||
if (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos++] = (byte) (rand() & 0xff);
|
||||
++g;
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
encodeOneBlock();
|
||||
}
|
||||
}
|
||||
|
||||
for (; len > 0; len--) {
|
||||
if (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos++] = plaintext[offset++];
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
encodeOneBlock();
|
||||
}
|
||||
}
|
||||
for (g = 0; g < 7; g++) {
|
||||
if (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos++] = (byte) 0;
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
encodeOneBlock();
|
||||
}
|
||||
}
|
||||
return mOutput;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private byte[] decrypt(byte[] cipherText, int offset, int len) {
|
||||
if (len % 8 != 0 || len < 16) {
|
||||
throw new IllegalArgumentException("must len % 8 == 0 && len >= 16");
|
||||
}
|
||||
mIV = decode(cipherText, offset);
|
||||
mIndexPos = mIV[0] & 7;
|
||||
int plen = len - mIndexPos - 10;
|
||||
isFirstBlock = true;
|
||||
if (plen < 0) {
|
||||
return null;
|
||||
}
|
||||
mOutput = new byte[plen];
|
||||
mPreOutPos = 0;
|
||||
mOutPos = 8;
|
||||
++mIndexPos;
|
||||
int g = 0;
|
||||
while (g < 2) {
|
||||
if (mIndexPos < 8) {
|
||||
++mIndexPos;
|
||||
++g;
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
isFirstBlock = false;
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw new RuntimeException("Unable to decode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int outpos = 0; plen != 0; plen--) {
|
||||
if (mIndexPos < 8) {
|
||||
mOutput[outpos++] = isFirstBlock ?
|
||||
mIV[mIndexPos] :
|
||||
(byte) (cipherText[mPreOutPos + offset + mIndexPos] ^ mIV[mIndexPos]);
|
||||
++mIndexPos;
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
mPreOutPos = mOutPos - 8;
|
||||
isFirstBlock = false;
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw new RuntimeException("Unable to decode");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (g = 0; g < 7; g++) {
|
||||
if (mIndexPos < 8) {
|
||||
if ((cipherText[mPreOutPos + offset + mIndexPos] ^ mIV[mIndexPos]) != 0) {
|
||||
throw new RuntimeException();
|
||||
} else {
|
||||
++mIndexPos;
|
||||
}
|
||||
}
|
||||
|
||||
if (mIndexPos == 8) {
|
||||
mPreOutPos = mOutPos;
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw new RuntimeException("Unable to decode");
|
||||
}
|
||||
}
|
||||
}
|
||||
return mOutput;
|
||||
}
|
||||
|
||||
public byte[] encrypt(byte[] plaintext) {
|
||||
return encrypt(plaintext, 0, plaintext.length);
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] ciphertext) {
|
||||
try {
|
||||
return decrypt(ciphertext, 0, ciphertext.length);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Source: " + UtilsKt.toUHexString(ciphertext, " "));
|
||||
System.out.println("Key: " + UtilsKt.toUHexString(this.key, " "));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
277
mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt
Normal file
277
mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt
Normal file
@ -0,0 +1,277 @@
|
||||
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) {
|
||||
|
||||
|
||||
companion object {
|
||||
val CRYPTOR_SHARE_KEY = TEA(Protocol.hexToBytes(Protocol.shareKey))
|
||||
val CRYPTOR_0825KEY = TEA(Protocol.hexToBytes(Protocol.key0825))
|
||||
|
||||
private val UINT32_MASK = 0xffffffffL
|
||||
|
||||
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
|
||||
|
||||
val mKey = LongArray(4)
|
||||
|
||||
for (i in 0..3) {
|
||||
mKey[i] = pack(key, i * 4, 4)
|
||||
}
|
||||
|
||||
fun rand(): Int {
|
||||
return mRandom.nextInt()
|
||||
}
|
||||
|
||||
fun encode(bytes: ByteArray): ByteArray {
|
||||
var v0 = pack(bytes, 0, 4)
|
||||
var v1 = pack(bytes, 4, 4)
|
||||
var sum: Long = 0
|
||||
val delta = 0x9e3779b9L
|
||||
for (i in 0..15) {
|
||||
sum = sum + delta and UINT32_MASK
|
||||
v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1]
|
||||
v0 = v0 and UINT32_MASK
|
||||
v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3]
|
||||
v1 = v1 and UINT32_MASK
|
||||
}
|
||||
return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array()
|
||||
}
|
||||
|
||||
fun decode(bytes: ByteArray, offset: Int): ByteArray {
|
||||
var v0 = pack(bytes, offset, 4)
|
||||
var v1 = pack(bytes, offset + 4, 4)
|
||||
val delta = 0x9e3779b9L
|
||||
var sum = delta shl 4 and UINT32_MASK
|
||||
for (i in 0..15) {
|
||||
v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3]
|
||||
v1 = v1 and UINT32_MASK
|
||||
v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1]
|
||||
v0 = v0 and UINT32_MASK
|
||||
sum = sum - delta and UINT32_MASK
|
||||
}
|
||||
return ByteBuffer.allocate(8).putInt(v0.toInt()).putInt(v1.toInt()).array()
|
||||
}
|
||||
|
||||
fun encodeOneBlock() {
|
||||
mIndexPos = 0
|
||||
while (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos] = if (isFirstBlock)
|
||||
mInBlock[mIndexPos]
|
||||
else
|
||||
(mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos])
|
||||
mIndexPos++
|
||||
}
|
||||
|
||||
System.arraycopy(encode(mInBlock), 0, mOutput, mOutPos, 8)
|
||||
mIndexPos = 0
|
||||
while (mIndexPos < 8) {
|
||||
val 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
|
||||
}
|
||||
|
||||
var g = 0
|
||||
while (g < 2) {
|
||||
if (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos++] = (rand() and 0xff).toByte()
|
||||
++g
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
encodeOneBlock()
|
||||
}
|
||||
}
|
||||
|
||||
while (len > 0) {
|
||||
if (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos++] = plaintext[offset++]
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
encodeOneBlock()
|
||||
}
|
||||
len--
|
||||
}
|
||||
g = 0
|
||||
while (g < 7) {
|
||||
if (mIndexPos < 8) {
|
||||
mInBlock[mIndexPos++] = 0.toByte()
|
||||
}
|
||||
if (mIndexPos == 8) {
|
||||
encodeOneBlock()
|
||||
}
|
||||
g++
|
||||
}
|
||||
return mOutput
|
||||
}
|
||||
|
||||
fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray? {
|
||||
require(!(len % 8 != 0 || len < 16)) { "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
|
||||
if (!decodeOneBlock(cipherText, offset, len)) {
|
||||
throw RuntimeException("Unable to decode")
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
/**
|
||||
* 仅用于测试时标记, 未来会删除
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
annotation class TestedSuccessfully
|
||||
internal annotation class TestedSuccessfully
|
@ -8,6 +8,11 @@ import java.lang.reflect.Field
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
* @author NaturalHG
|
||||
*/
|
||||
|
||||
@JvmSynthetic
|
||||
fun ByteArray.toHexString(): String = toHexString(" ")
|
||||
|
||||
@ -64,6 +69,7 @@ open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream())
|
||||
open fun toUByteArray(): UByteArray = (out as ByteArrayOutputStream).toByteArray().toUByteArray()
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().let { t(it); return it.toByteArray() }
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
|
@ -7,6 +7,9 @@ import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String, T> {
|
||||
|
||||
public MiraiConfigSection(){
|
||||
@ -148,7 +151,7 @@ public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String
|
||||
}
|
||||
if(content instanceof Map){
|
||||
return new MiraiConfigSection<>(
|
||||
(LinkedHashMap<String, D>) content
|
||||
(LinkedHashMap<String, D>) content
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -11,8 +11,10 @@ import java.util.function.Function;
|
||||
/**
|
||||
* 实现了可以直接被继承的 SynchronizedLinkedListMap<K,V>
|
||||
*
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
* @param <K> the type of key
|
||||
* @param <V> the type of value
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiSynchronizedLinkedListMap<K,V> extends AbstractMap<K,V> {
|
||||
|
||||
|
@ -10,6 +10,9 @@ import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiSettingListSection extends Vector<Object> implements MiraiSettingSection {
|
||||
private Lock lock = new ReentrantLock();
|
||||
|
||||
|
@ -9,6 +9,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiSettingMapSection extends ConcurrentHashMap<String, Object> implements MiraiSettingSection {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -4,6 +4,9 @@ import org.ini4j.Profile;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public interface MiraiSettingSection extends Closeable {
|
||||
void saveAsSection(Profile.Section section);
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* Thread-safe Mirai Config <br>
|
||||
* Only supports <code>INI</code> format <br>
|
||||
* Supports {@link Map} and {@link List}
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class MiraiSettings {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user