Commit ece200d4 authored by Him188moe's avatar Him188moe

Updated docs, migrated TEA encryption from java to kotlin, fixed bugs

parent 87532418
...@@ -17,20 +17,28 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -17,20 +17,28 @@ import java.util.concurrent.atomic.AtomicInteger;
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号. * Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人. * Mirai 为多账号设计, 可同时维护多个机器人.
* <br> * <br>
* {@link Bot} 由 2 个模块组成. * {@link Bot} 由 3 个模块组成.
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问 * {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
* {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问 * {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
* <br> * <br>
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account} * 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
* 若你需要得到服务器上所有机器人列表, 请访问 {@link Bot#instances} * 若你需要得到服务器上所有机器人列表, 请访问 {@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 Him188moe
* @author NatrualHG * @author NatrualHG
* @see net.mamoe.mirai.contact.Contact * @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 final class Bot implements Closeable {
public static final List<Bot> instances = Collections.synchronizedList(new LinkedList<>()); public static final List<Bot> instances = Collections.synchronizedList(new LinkedList<>());
......
package net.mamoe.mirai; 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 { public final class MiraiMain {
private static MiraiServer server; private static MiraiServer server;
......
...@@ -23,6 +23,9 @@ import java.util.Scanner; ...@@ -23,6 +23,9 @@ import java.util.Scanner;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
/** /**
* Mirai 服务器.
* 管理一些基础的事务
*
* @author NaturalHG * @author NaturalHG
*/ */
public class MiraiServer { public class MiraiServer {
......
...@@ -6,9 +6,12 @@ import net.mamoe.mirai.message.defaults.MessageChain ...@@ -6,9 +6,12 @@ import net.mamoe.mirai.message.defaults.MessageChain
import net.mamoe.mirai.message.defaults.PlainText import net.mamoe.mirai.message.defaults.PlainText
/** /**
* 联系人.
*
* A contact is a [QQ] or a [Group] for one particular [Bot] instance only. * 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 * @author Him188moe
*/ */
abstract class Contact internal constructor(val bot: Bot, val number: Long) { abstract class Contact internal constructor(val bot: Bot, val number: Long) {
......
...@@ -7,7 +7,11 @@ import net.mamoe.mirai.utils.ContactList ...@@ -7,7 +7,11 @@ import net.mamoe.mirai.utils.ContactList
import java.io.Closeable import java.io.Closeable
/** /**
* 群 * 群.
*
* Group ID 与 Group Number 并不是同一个值.
* - Group Number([Group.number]) 是通常使用的群号码.(在 QQ 客户端中可见)
* - Group ID([Group.groupId]) 是与服务器通讯时使用的 id.(在 QQ 客户端中不可见)
* *
* Java 获取 groupNumber: `group.getNumber()` * Java 获取 groupNumber: `group.getNumber()`
* Java 获取所属 bot: `group.getBot()` * Java 获取所属 bot: `group.getBot()`
...@@ -15,6 +19,7 @@ import java.io.Closeable ...@@ -15,6 +19,7 @@ import java.io.Closeable
* Java 获取 groupId: `group.getGroupId()` * Java 获取 groupId: `group.getGroupId()`
* *
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)` * Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe
*/ */
class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable { class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
val groupId = groupNumberToId(number) val groupId = groupNumberToId(number)
......
...@@ -6,6 +6,9 @@ import org.jetbrains.annotations.NotNull; ...@@ -6,6 +6,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
/**
* @author Him188moe
*/
public abstract class BotEvent extends MiraiEvent { public abstract class BotEvent extends MiraiEvent {
private final Bot bot; private final Bot bot;
......
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; ...@@ -2,6 +2,9 @@ package net.mamoe.mirai.event.events.bot;
import net.mamoe.mirai.Bot; import net.mamoe.mirai.Bot;
/**
* @author NaturalHG
*/
public final class BotLoginSucceedEvent extends BotEvent { public final class BotLoginSucceedEvent extends BotEvent {
public BotLoginSucceedEvent(Bot bot) { public BotLoginSucceedEvent(Bot bot) {
......
...@@ -423,7 +423,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable { ...@@ -423,7 +423,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
//登录成功后会收到大量上次的消息, 忽略掉 //登录成功后会收到大量上次的消息, 忽略掉
MiraiThreadPool.getInstance().schedule({ MiraiThreadPool.getInstance().schedule({
messageHandler.ignoreMessage = false messageHandler.ignoreMessage = false
}, 2, TimeUnit.SECONDS) }, 3, TimeUnit.SECONDS)
this.tlv0105 = packet.tlv0105 this.tlv0105 = packet.tlv0105
sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE)) sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, ClientLoginStatus.ONLINE))
...@@ -485,7 +485,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable { ...@@ -485,7 +485,7 @@ class BotNetworkHandler(private val bot: Bot) : Closeable {
* 处理消息事件, 承担消息发送任务. * 处理消息事件, 承担消息发送任务.
*/ */
inner class MessageHandler : PacketHandler() { inner class MessageHandler : PacketHandler() {
internal var ignoreMessage: Boolean = false internal var ignoreMessage: Boolean = true
init { init {
//todo for test //todo for test
......
...@@ -112,10 +112,6 @@ fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) { ...@@ -112,10 +112,6 @@ fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) {
this.write(TEA.encrypt(byteArray, key)) 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) { fun DataOutputStream.encryptAndWrite(key: ByteArray, encoder: (ByteArrayDataOutputStream) -> Unit) {
this.write(TEA.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key)) this.write(TEA.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }, key))
} }
...@@ -125,10 +121,6 @@ fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutp ...@@ -125,10 +121,6 @@ fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutp
this.encryptAndWrite(keyHex.hexToBytes(), encoder) this.encryptAndWrite(keyHex.hexToBytes(), encoder)
} }
fun DataOutputStream.encryptAndWrite(cryptor: TEA, encoder: (ByteArrayDataOutputStream) -> Unit) {
this.write(cryptor.encrypt(ByteArrayDataOutputStream().let { encoder(it); it.toByteArray() }))
}
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
@Throws(IOException::class) @Throws(IOException::class)
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) { fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
......
...@@ -67,7 +67,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp ...@@ -67,7 +67,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
*/ */
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
@PacketId("08 25 31 01") @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 @ExperimentalUnsignedTypes
@Throws(IOException::class) @Throws(IOException::class)
override fun encode() { override fun encode() {
...@@ -75,19 +75,15 @@ class ClientTouchPacket(val qq: Long, val serverIp: String) : ClientPacket() { ...@@ -75,19 +75,15 @@ class ClientTouchPacket(val qq: Long, val serverIp: String) : ClientPacket() {
this.writeHex(Protocol.fixVer) this.writeHex(Protocol.fixVer)
this.writeHex(Protocol.key0825) this.writeHex(Protocol.key0825)
this.write(TEA.CRYPTOR_0825KEY.encrypt(object : ByteArrayDataOutputStream() { this.encryptAndWrite(Protocol.key0825) {
@Throws(IOException::class) it.writeHex(Protocol.constantData1)
override fun toByteArray(): ByteArray { it.writeHex(Protocol.constantData2)
this.writeHex(Protocol.constantData1) it.writeQQ(qq)
this.writeHex(Protocol.constantData2) it.writeHex("00 00 00 00 03 09 00 08 00 01")
this.writeQQ(qq) it.writeIP(serverIp);
this.writeHex("00 00 00 00 03 09 00 08 00 01") 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")
this.writeIP(serverIp); it.writeHex(Protocol.publicKey)
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()))
} }
} }
......
...@@ -2,6 +2,8 @@ package net.mamoe.mirai.network.packet.action ...@@ -2,6 +2,8 @@ package net.mamoe.mirai.network.packet.action
/** /**
* 添加好友结果 * 添加好友结果
*
* @author Him188moe
*/ */
enum class AddFriendResult { enum class AddFriendResult {
/** /**
......
package net.mamoe.mirai.network.packet.login 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.ServerPacket
import net.mamoe.mirai.network.packet.cutTail
import net.mamoe.mirai.network.packet.dataInputStream import net.mamoe.mirai.network.packet.dataInputStream
import net.mamoe.mirai.network.packet.goto import net.mamoe.mirai.network.packet.goto
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.TestedSuccessfully import net.mamoe.mirai.utils.TestedSuccessfully
import net.mamoe.mirai.utils.hexToUBytes import net.mamoe.mirai.utils.hexToUBytes
import java.io.DataInputStream import java.io.DataInputStream
...@@ -40,9 +39,10 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv ...@@ -40,9 +39,10 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
} }
@ExperimentalUnsignedTypes
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket { fun decrypt(): ServerLoginResponseVerificationCodeInitPacket {
this.input goto 14 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) return ServerLoginResponseVerificationCodeInitPacket(data.dataInputStream(), data.size).setId(this.idHex)
} }
} }
......
...@@ -9,6 +9,8 @@ import net.mamoe.mirai.Bot; ...@@ -9,6 +9,8 @@ import net.mamoe.mirai.Bot;
* *
* @see net.mamoe.mirai.event.MiraiEventManager * @see net.mamoe.mirai.event.MiraiEventManager
* @see net.mamoe.mirai.event.MiraiEventManagerKt * @see net.mamoe.mirai.event.MiraiEventManagerKt
* @author Him188moe
* @author NaturalHG
*/ */
public abstract class MiraiPluginBase { public abstract class MiraiPluginBase {
} }
package net.mamoe.mirai.plugin; package net.mamoe.mirai.plugin;
/**
* @author NaturalHG
*/
public class MiraiPluginManager { public class MiraiPluginManager {
......
package net.mamoe.mirai.task; package net.mamoe.mirai.task;
/**
* @author NaturalHG
*/
@FunctionalInterface @FunctionalInterface
public interface MiraiTaskExceptionHandler { public interface MiraiTaskExceptionHandler {
void onHandle(Throwable e); void onHandle(Throwable e);
......
...@@ -10,6 +10,9 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -10,6 +10,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
/**
* @author NaturalHG
*/
public final class MiraiTaskManager { public final class MiraiTaskManager {
private static MiraiTaskManager instance = new MiraiTaskManager(); private static MiraiTaskManager instance = new MiraiTaskManager();
......
...@@ -3,6 +3,9 @@ package net.mamoe.mirai.task; ...@@ -3,6 +3,9 @@ package net.mamoe.mirai.task;
import java.io.Closeable; import java.io.Closeable;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* @author NaturalHG
*/
public final class MiraiThreadPool extends ScheduledThreadPoolExecutor implements Closeable { public final class MiraiThreadPool extends ScheduledThreadPoolExecutor implements Closeable {
private static MiraiThreadPool instance = new MiraiThreadPool(); private static MiraiThreadPool instance = new MiraiThreadPool();
......
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
/**
* @author NaturalHG
*/
public final class CharImageUtil { public final class CharImageUtil {
public static String createCharImg(BufferedImage image) { public static String createCharImg(BufferedImage image) {
......
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils;
/** /**
* QQ 在线状态
*
* @author Him188moe * @author Him188moe
* @see net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket
*/ */
public enum ClientLoginStatus { public enum ClientLoginStatus {
/** /**
......
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils;
/**
* @author NaturalHG
*/
public class EventException extends RuntimeException { public class EventException extends RuntimeException {
private final Throwable cause; private final Throwable cause;
......
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils;
/**
* @author NaturalHG
*/
public enum LoggerTextFormat { public enum LoggerTextFormat {
RESET("\33[0m"), RESET("\33[0m"),
......
...@@ -6,6 +6,9 @@ import java.util.* ...@@ -6,6 +6,9 @@ import java.util.*
/** /**
* used to replace old logger * used to replace old logger
*
* @author Him188moe
* @author NaturalHG
*/ */
object MiraiLogger { object MiraiLogger {
infix fun log(o: Any?) = info(o) infix fun log(o: Any?) = info(o)
......
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;
}
}
}
\ No newline at end of file
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)
}
}
}
\ No newline at end of file
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
/** /**
* 仅用于测试时标记, 未来会删除
*
* @author Him188moe * @author Him188moe
*/ */
annotation class TestedSuccessfully internal annotation class TestedSuccessfully
\ No newline at end of file \ No newline at end of file
...@@ -8,6 +8,11 @@ import java.lang.reflect.Field ...@@ -8,6 +8,11 @@ import java.lang.reflect.Field
import java.util.* import java.util.*
import java.util.zip.CRC32 import java.util.zip.CRC32
/**
* @author Him188moe
* @author NaturalHG
*/
@JvmSynthetic @JvmSynthetic
fun ByteArray.toHexString(): String = toHexString(" ") fun ByteArray.toHexString(): String = toHexString(" ")
...@@ -64,6 +69,7 @@ open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream()) ...@@ -64,6 +69,7 @@ open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream())
open fun toUByteArray(): UByteArray = (out as ByteArrayOutputStream).toByteArray().toUByteArray() 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().let { t(it); return it.toByteArray() }
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
......
...@@ -7,6 +7,9 @@ import java.util.Map; ...@@ -7,6 +7,9 @@ import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.function.Supplier; import java.util.function.Supplier;
/**
* @author NaturalHG
*/
public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String, T> { public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String, T> {
public MiraiConfigSection(){ public MiraiConfigSection(){
...@@ -148,7 +151,7 @@ public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String ...@@ -148,7 +151,7 @@ public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedListMap<String
} }
if(content instanceof Map){ if(content instanceof Map){
return new MiraiConfigSection<>( return new MiraiConfigSection<>(
(LinkedHashMap<String, D>) content (LinkedHashMap<String, D>) content
); );
} }
return null; return null;
......
...@@ -11,8 +11,10 @@ import java.util.function.Function; ...@@ -11,8 +11,10 @@ import java.util.function.Function;
/** /**
* 实现了可以直接被继承的 SynchronizedLinkedListMap<K,V> * 实现了可以直接被继承的 SynchronizedLinkedListMap<K,V>
* *
* @param <K> * @param <K> the type of key
* @param <V> * @param <V> the type of value
*
* @author NaturalHG
*/ */
public class MiraiSynchronizedLinkedListMap<K,V> extends AbstractMap<K,V> { public class MiraiSynchronizedLinkedListMap<K,V> extends AbstractMap<K,V> {
......
...@@ -10,6 +10,9 @@ import java.util.concurrent.locks.Lock; ...@@ -10,6 +10,9 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
/**
* @author NaturalHG
*/
public class MiraiSettingListSection extends Vector<Object> implements MiraiSettingSection { public class MiraiSettingListSection extends Vector<Object> implements MiraiSettingSection {
private Lock lock = new ReentrantLock(); private Lock lock = new ReentrantLock();
......
...@@ -9,6 +9,9 @@ import java.util.concurrent.ConcurrentHashMap; ...@@ -9,6 +9,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* @author NaturalHG
*/
public class MiraiSettingMapSection extends ConcurrentHashMap<String, Object> implements MiraiSettingSection { public class MiraiSettingMapSection extends ConcurrentHashMap<String, Object> implements MiraiSettingSection {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
......
...@@ -4,6 +4,9 @@ import org.ini4j.Profile; ...@@ -4,6 +4,9 @@ import org.ini4j.Profile;
import java.io.Closeable; import java.io.Closeable;
/**
* @author NaturalHG
*/
public interface MiraiSettingSection extends Closeable { public interface MiraiSettingSection extends Closeable {
void saveAsSection(Profile.Section section); void saveAsSection(Profile.Section section);
} }
......
...@@ -15,6 +15,8 @@ import java.util.concurrent.ConcurrentHashMap; ...@@ -15,6 +15,8 @@ import java.util.concurrent.ConcurrentHashMap;
* Thread-safe Mirai Config <br> * Thread-safe Mirai Config <br>
* Only supports <code>INI</code> format <br> * Only supports <code>INI</code> format <br>
* Supports {@link Map} and {@link List} * Supports {@link Map} and {@link List}
*
* @author NaturalHG
*/ */
public class MiraiSettings { public class MiraiSettings {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment