1
0
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:
Him188moe 2019-09-10 22:25:13 +08:00
parent 87532418e5
commit ece200d499
34 changed files with 384 additions and 324 deletions

View File

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

View File

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

View File

@ -23,6 +23,9 @@ import java.util.Scanner;
import java.util.concurrent.ExecutionException;
/**
* Mirai 服务器.
* 管理一些基础的事务
*
* @author NaturalHG
*/
public class MiraiServer {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,8 @@ package net.mamoe.mirai.network.packet.action
/**
* 添加好友结果
*
* @author Him188moe
*/
enum class AddFriendResult {
/**

View File

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

View File

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

View File

@ -1,5 +1,8 @@
package net.mamoe.mirai.plugin;
/**
* @author NaturalHG
*/
public class MiraiPluginManager {

View File

@ -1,5 +1,8 @@
package net.mamoe.mirai.task;
/**
* @author NaturalHG
*/
@FunctionalInterface
public interface MiraiTaskExceptionHandler {
void onHandle(Throwable e);

View File

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

View File

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

View File

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

View File

@ -1,7 +1,10 @@
package net.mamoe.mirai.utils;
/**
* QQ 在线状态
*
* @author Him188moe
* @see net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket
*/
public enum ClientLoginStatus {
/**

View File

@ -1,5 +1,8 @@
package net.mamoe.mirai.utils;
/**
* @author NaturalHG
*/
public class EventException extends RuntimeException {
private final Throwable cause;

View File

@ -1,5 +1,8 @@
package net.mamoe.mirai.utils;
/**
* @author NaturalHG
*/
public enum LoggerTextFormat {
RESET("\33[0m"),

View File

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

View File

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

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

View File

@ -1,6 +1,8 @@
package net.mamoe.mirai.utils
/**
* 仅用于测试时标记, 未来会删除
*
* @author Him188moe
*/
annotation class TestedSuccessfully
internal annotation class TestedSuccessfully

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,9 @@ import org.ini4j.Profile;
import java.io.Closeable;
/**
* @author NaturalHG
*/
public interface MiraiSettingSection extends Closeable {
void saveAsSection(Profile.Section section);
}

View File

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