diff --git a/README.md b/README.md
index 1fc4f8c6c..7f1382926 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,20 @@
# Mirai
一个以TIM QQ协议(非web)驱动的JAVA(+Kotlin) QQ机器人服务端核心
-我们坚持免费与开源
+采用服务端-插件模式运行,同时提供独立的协议层库
+**我们承诺项目的所有模块均开源**
-项目处于快速开发阶段
+项目处于开发阶段,学生无法每日大量更新。
+项目还有很多未完善的地方, 欢迎任何的代码贡献, 或是 issue.
部分协议来自网络上开源项目
-一切开发旨在学习, 请勿用于非法用途
+一切开发旨在学习,请勿用于非法用途
-
+## 抢先体验
+核心框架结构已经开发完毕,一些核心功能也测试完成。
+仅需几分钟就可以测试 Mirai.
+现在你可以登录小号来测试 Mirai.
+即使测试消息时未发现冻结情况,我们也无法100%保证账号冻结不会发生。
-A JAVA(+Kotlin) powered open-source project under GPL license
-It use protocols from TIM QQ, that is, it won't be affected by the close of Smart QQ
-The project is all for learning proposes and still in developing stage
-
-
-## 抢先体验
-现在你可以使用 Mirai 内置的一些测试qq号体验 Mirai, 但我们现在还不建议你使用自己的 qq 号登录
1. Clone
2. Import as Maven project
3. Run [MiraiMain](mirai-core/src/main/java/net/mamoe/mirai/MiraiMain.java#L7)
@@ -44,13 +43,11 @@ FriendMessageEvent::class.hookAlways{
![JsssF](.github/J%5DCE%29IK4BU08%28EO~UVLJ%7B%5BF.png)
![](.github/68f8fec9.png)
-不过我们还正在努力做发送图片
+发送图片已经完成,但我们还在开发上传图片至服务器。
+现在你可以通过发送一张图片给机器人账号,再让机器人账号发送这张图片。你可以查看 [Image](src/main/java/net/mamoe/mirai/message/Image.kt)
-## 代码结构
-Network部分使用 Kotlin 完成(因为kt有对 unsigned byte 的支持).
-与插件相关性强(或其他在二次开发中容易接触)的部分尽量使用 Java 完成,
-若使用 Kotlin, 我们会通过 Java interface 实现或 javadoc 帮助未接触过 Kotlin 的开发者.
-即使你完全不了解 Kotlin, 你也可以正常开发.
+## 语言使用说明
+我们使用 Kotlin,但我们也会保留对 Java 和 Java开发者的支持。
# TODO
- [x] 事件(Event)模块
@@ -64,10 +61,11 @@ Network部分使用 Kotlin 完成(因为kt有对 unsigned byte 的支持).
- [ ] Network - Events
- [ ] Bot - Friend/group list
- [ ] Bot - Actions(joining group, adding friend, etc.)
-- [ ] Message Section **(Working on)**
+- [x] Message Section
- [ ] Image uploading **(Working on)**
- [ ] Contact
- [ ] UI
+- [ ] Console
@@ -81,6 +79,14 @@ Network部分使用 Kotlin 完成(因为kt有对 unsigned byte 的支持).
...
```
+# Mirai
+
+
+
+A JAVA(+Kotlin) powered open-source project under GPL license
+It use protocols from TIM QQ, that is, it won't be affected by the close of Smart QQ
+The project is all for learning proposes and still in developing stage
+
# Usage
## Requirements
- Java 11 or higher
diff --git a/document/protocol/Get_tlv_0006.md b/document/protocol/Get_tlv_0006.md
deleted file mode 100644
index b9dbe0c5a..000000000
--- a/document/protocol/Get_tlv_0006.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# TIM Protocol
-
-## Get_tlv_0006
-
-C 构建包, 近 C 使用
-
-#### Var
-type | var name | value/from
----- | ---|---
-?bytes | MD51 | md5(raw password)
-?bytes | MD52 | md5((MD51 + “ 00 00 00 00 ” + g_QQ).hextobytes())
-4bytes |m_loginIP | 服务器提供(Dispose_0825)
-16bytes| m_tgtgtKey| |
-#### Packet data
-
-type | value
----- | ---
-int | random
-hex |00 02
-int |qq
-hex |#_0825data2
-hex |00 00 01
-bytes|MD51
-int |m_loginTime
-byte | 0
-bytes | 12 zero
-int|m_loginIP
-bytes | 8 zero
-hex | 00 10
-hex | 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B
-bytes | m_tgtgtKey
-
-TEA加密以上, key=MD52
\ No newline at end of file
diff --git a/document/protocol/Login Flow.md b/document/protocol/Login Flow.md
deleted file mode 100644
index 86d670a16..000000000
--- a/document/protocol/Login Flow.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# TIM Protocol
-
-## Login Flow
-
-### 服务器确认 - 通过 touch 包
-
-**[Touch](Touch.md)**
-
-C: 发送登录`08 25 31 01`
- Sample:
- ```text
-02 37 13 08 25 31 01 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 9F A7 43 90 2A 9D 29 B5 EA DB 50 7F D3 78 1C AE 31 7E 7E 4F A9 1B B5 C9 8D A8 4C 78 98 13 E1 45 FC 35 2E 22 3D E0 39 1A 3F C6 8B CA 06 A8 F3 B3 6F 95 D8 64 1A B0 E9 29 06 DB 5C F4 9B 32 47 5A B7 10 57 C5 2F C9 D9 7B 17 22 7F 09 A6 8C 30 04 24 0F 1D 61 A1 42 E2 7A AA 15 36 AC 67 9B 7A 4D 42 14 AD F5 2D D2 A3 CA 03
-```
-
-S: 回复`08 25 31 01`(ID与C发送的相同), 告诉C是否需 redirect
-
-**[Redirection](Redirection.md)**
-
-如果需要 redirect
-C: 发送 redirect 包`08 25 31 02`到新的服务器
- Sample pk:
- g_server = 125.39.132.167
-```text
- 02 37 13 08 25 31 02 76 E4 B8 DD 03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B 23 89 DB A3 07 80 49 63 01 76 69 F1 E1 11 32 06 E9 7F E4 6A 6B 98 07 75 EF 0E 1F 81 10 85 86 EB 96 8E 65 78 0F C3 BC F8 FF 51 3E 36 4F 48 3C 78 52 26 3F 4C 20 65 85 69 AC B8 36 B6 50 50 CC 01 4A 35 44 15 5C 80 B9 F7 A7 56 D4 B2 D4 A4 D9 09 56 29 93 39 0C C8 9C 0B F7 2D CE BE B0 D5 4C CE 48 B3 2D 18 28 A2 3C DD 26 C1 F1 6E A1 4B EC 8A 03
-```
-
-如果不需要 redirect
-C: 发送 `08 36 31 03` 到原服务器, 提交密码
- m_loginTime = 5D 59 7D A6
- m_loginIP = B7 5F F8 D4
- m_0825token = 16 5A 4A C4 FE D1 F8 A3 CB B7 37 DD A5 AE 5C F7 04 74 36 91 4E CD 4A E6 EF 43 31 A7 D1 97 CC 6B 93 C7 9B 15 62 FD 11 3E 19 E1 69 62 B3 BC F4 9A E1 17 19 47 CC A3 1E AC
- m_tgtgtKey(由C生成? RANDOM) = DB DE AE DD C7 ED 35 B6 DD 2B 71 6B C4 14 C6 6B
-
-在这个包中会 [getTLV0006](Get_tlv_0006.md),
-
- Sample
-```text
- 02 37 13 08 36 31 03 76 E4 B8 DD 03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 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 00 00 00 10 EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA 12 58 AF 79 C5 60 54 5F A9 30 38 87 E9 B0 68 FA D3 83 A7 6A EA B6 7F 54 10 78 F0 47 60 24 1B E2 91 2D FD 60 F4 C7 DE 3C 7C 56 83 BE B4 66 49 60 5F E0 D3 2B 18 BD 5D 64 28 D1 98 8F 83 84 98 03 97 DE 97 83 5A BD 0B AC 1B 63 7C A2 C8 13 C2 26 8A C1 AA 6D B8 5D 4A 91 E7 C8 7B AF 3C 89 76 DF EA F3 F3 53 AD 69 2F 4C 45 90 69 B7 69 3E 05 C9 DE 1B B1 C9 DE D3 F6 4B 70 3D 27 54 BC D6 2B AB 68 13 2D E7 E3 11 FF 98 3F 1E 51 BC D6 F5 AB 26 DA 53 82 7B 3C 23 99 D8 77 95 32 64 C9 11 C5 8D 40 EA F6 E7 84 C6 B0 94 EE 4A 7E 22 1E 30 34 59 AB D1 66 79 EA A5 D4 AD A2 7D 4D 47 B8 FC 86 BC DD 5D 27 15 94 E0 1B 68 00 DD 5E 5A 09 08 E0 F5 91 EF 98 95 CC 92 B9 A0 EB AC 62 B5 5D DD AA EC 4F 36 48 6E C9 7C 2D 1F 21 98 F5 27 28 E5 8E 4A 51 BC 9A 2A BE 50 31 21 EC DF C8 97 35 58 76 B3 CD F9 92 7A 86 0E C4 1D 90 62 86 99 20 92 6C 12 C9 E2 E9 7F 0B 6B AC 59 00 55 7E B6 45 B1 C4 01 37 A6 1D B3 6E 16 06 96 40 59 CD 59 5D 6F 96 E9 B4 97 0D 55 AE 3B BF FA 54 73 D3 06 B3 47 AA 7E A1 89 F5 04 79 62 7C 11 B4 1C 4D F7 24 92 71 42 17 DC 52 67 9C 66 97 5F 64 1D CD 35 68 7D D5 D7 51 9B BA 29 92 E7 8B 6F B4 74 9E 84 54 5F E8 0D 81 89 15 FB 30 A0 1B AD B2 A3 46 3F F1 A7 A7 A1 A2 A6 D1 7D B0 4E D4 E9 87 AA 20 ED 9A 04 22 5F 57 45 20 05 2B 48 CD 06 4B BC 6F F2 92 D5 09 07 DF 83 DA FC 9D 75 50 C3 75 98 56 8C B3 B0 02 80 FD ED 61 03 00 86 EA E1 03 D2 08 68 B4 1F B9 9C EB 7B 75 9C 2D 94 10 F1 C0 40 E8 D9 9A DB 4A 0F 42 90 78 F6 AB 5B 7D 5A 18 ED 3F 45 8E 1F 98 D0 97 79 51 1D 2D 64 23 8D 30 93 FF C1 B2 05 1D 22 0C E6 51 CD F3 D5 F6 D9 DB 31 EC B2 2F B1 D1 ED F3 54 5F B3 F9 B9 74 0B 10 21 4D 84 52 CD 61 A2 39 51 CD 38 AF 2B DD BD CC 70 76 31 76 51 49 B7 03
-```
-
-**Password Verified**
-S: 发送 `08 36 31 03` 告知登录结果.
-
-若成功, C将会得到:
-- m_0828_rec_decr_key
-- g_clientKey
-- token38
-- token88
-- encryptionKey
-
-若不成功, 理由:
-- 需要验证码:
- //todo
-- 密码错误
-- 未知(重新登录)
-- 冻结
-- 账号不存在
-- 设备锁
-- 被回收
-
-C: 回复 `08 28 04 34`, 请求建立 Session
-Sample
-
-```text
-02 37 13 08 28 04 34 76 E4 B8 DD 02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A 00 38 BA 24 BF EA 76 94 2C 9B 91 A8 8F 0E 7C EC F5 41 77 3C B9 D4 95 50 F2 00 FD CB E3 48 36 FB 89 13 CE E4 EA 76 A2 2F 20 86 F6 0F E0 54 55 6E D4 0B 9B EA 07 6B E1 D4 87 56 F9 99 8F FA 12 8E 22 A6 5A 9D A6 DC C9 B6 5C 5A EC BE BF CC 38 BD E1 5A 23 21 CE 02 31 F1 E1 BD FB 8E 4D E9 59 E6 BB FB B2 36 0C 47 0A C0 F7 94 63 C3 2F AB 6E AE 00 01 1F 5F 60 8E B5 79 97 EB 10 59 A0 29 B3 3B 9C BA 5D 33 C4 2A 57 CA CF 94 7A 2D DD F7 B3 9C BC 65 5D D5 62 53 A8 1D D2 F1 5B DD D7 24 32 63 60 60 DD 33 1F 3A C0 71 38 86 BC 78 D3 7C 7A E1 97 71 AB B7 59 AD 27 32 D5 AF F3 DC 1B 7B 70 3B 08 C0 91 D8 BC F1 C4 DA E3 DA 86 A1 27 8A EE C3 5F D6 25 42 A0 CB 19 7F 08 80 F8 65 2C 27 31 B7 D4 85 C3 49 BD 99 48 FE A9 63 78 6D 18 C3 4E BB F7 8A C4 80 8C 8A 17 EB 47 AF 2A 12 73 71 08 A6 E7 C3 08 2B 9A 6F 8A C2 6C 3B 1A CF 05 D8 57 63 33 AC BD 45 98 C1 85 56 08 0F 9F 36 FD 60 69 BC D0 94 1A 11 4D C6 3E 78 1D F1 67 D2 1D C4 C8 17 2F DC F4 B6 4F 5F F5 EE 8B 73 68 AA 3B BA C6 94 C8 21 1E 95 6D C2 7A BE 8B 1D 92 21 8D 2C D8 B6 86 D1 30 BB 72 34 B9 A0 D2 2E 4C 98 3C 17 E2 B2 6A AD 75 E8 B0 DE F4 1A 6F 15 93 47 B3 4D DA 6F BE A3 47 D3 9B 58 2D 4B A3 76 0E 39 ED A5 C3 0A 34 BA 78 01 AE 20 A3 38 CE BA CD F6 D7 1B C9 E7 4C 83 6E 31 34 25 16 64 BA EE 4B 8D E7 0E 2F C8 08 72 50 AE 91 16 7F 68 14 60 7E D8 3F CC 26 2D F6 BC 65 72 C8 F4 EA 55 E6 1B E0 BF F4 9F 9C FD A9 93 B6 62 78 F0 A1 19 D2 87 6E B8 B7 E3 70 13 09 95 29 C9 05 EC 99 36 5C 96 47 C1 C4 06 5C 23 5C A3 AD B0 39 BC 70 75 3D AA E9 16 03 0E 62 1B D0 78 EA F2 5C FD 9C 04 D9 AB 75 00 F8 37 F1 A8 DD 7B 65 91 D3 58 DE C5 BA 9E AC 13 DE 35 BA 17 DC D1 AB A5 96 C4 99 81 8E 21 4B 2F C1 9B 4C E1 56 A7 5D AC 26 71 EC 49 F0 A6 B1 F5 43 EA AC BE E6 9F A0 C2 E1 68 35 97 7B 81 76 AF 9E BD BC A7 D8 9E FC C0 E8 21 B0 BA 20 6A D0 BD E7 00 59 06 61 A1 DF AA 9F BA F4 5D A6 7B 5B A1 D8 6B B5 E9 72 66 51 8A D3 CE 51 A9 08 C7 11 4B FB 29 2E 6C 48 5B 8A 50 C6 5D 3A C1 9E A1 51 B6 56 DD 6B F5 D2 FD AE AB A4 4A A8 1F 99 BA 7D 4F 62 D7 64 22 31 04 62 36 62 65 96 B3 5A 35 03
-```
-
-**Session**
-S: 发送 `08 28 04 34`, 完成 session 建立, 告知:
-- g_sessionKey
-- g_tlv0105
-- g_loginStatus
-
-**Handshake 1**
-C: 发送 `00 EC 6E 8E`
-```text
-02 37 13 00 EC 6E 8E 76 E4 B8 DD 02 00 00 00 01 01 01 00 00 68 20 C4 28 24 D6 67 13 CE 5F F7 F8 38 79 F4 56 1F CA 13 95 22 4D 7B 5D B6 59 03
-```
-
-S: 回复 `00 EC 6E 8E`
-
-**Handshake 2**
-C: 发送 `00 1D C5 CB`
-```text
-02 37 13 00 1D C5 CB 76 E4 B8 DD 02 00 00 00 01 01 01 00 00 68 20 F3 B2 B9 BF F9 C9 87 EB C2 33 FD BA 6B 16 44 E8 B2 C1 8C 7E 4F 97 01 13 88 D8 00 BF 5F 6C 38 22 E0 50 4F 9B 73 7F 5F 31 64 72 9A C1 11 79 F5 B9 33 C0 EC 81 5E F7 D5 A4 BF C6 29 9F 18 9E C0 99 CE B7 16 E5 E8 BF EE E7 5A C3 5C 28 68 3E 48 18 03
-```
-
-S: 回复 `00 1D C5 CB`
-
-**Handshake 3**
-C: 发送 `00 5C 7B 2E`
-```text
-02 37 13 00 5C 7B 2E 76 E4 B8 DD 02 00 00 00 01 01 01 00 00 68 20 E7 E2 64 22 9C 2F 33 27 A3 8B 4D 9C DE C5 A8 0D 03
-```
-
-S: 回复 `00 5C 7B 2E`
\ No newline at end of file
diff --git a/document/protocol/Password Verified.md b/document/protocol/Password Verified.md
deleted file mode 100644
index 4fcc00a72..000000000
--- a/document/protocol/Password Verified.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# TIM Protocol
-
-## Password Verified
-
-### S -> `08 25 31 02`(may be another)
-
-#### Var
-type | var name | value/from
----- | ---|---
-int |g_qq | qq number
-16bytes | tgtgtKey | |
-
-#### Decryption
-
-//todo
-
-#### Packet data - Requiring
-
-//todo
-
-#### Packet data - Not Requiring
-
-//todo
\ No newline at end of file
diff --git a/document/protocol/Redirection.md b/document/protocol/Redirection.md
deleted file mode 100644
index 856d83e0b..000000000
--- a/document/protocol/Redirection.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# TIM Protocol
-
-## Redirection
-
-### S -> `08 25 31 02`(may be another)
-
-#### Decryption
-
-skip 14 bytes
-if (flag == "08 25 31 02")
- data = decrypt (read bytes 14..length-1, #redirectionKey)
-else data = decrypt (read bytes 14..length-1, #_0825key)
-
-#### Packet data - Requiring
-
-**read byte == 0xFE**
-skip 94 bytes
-String serverIp = read 4 bytes and join them with separator "."
-
-#### Packet data - Not Requiring
-
-**read byte == 0x00**
-skip 4 bytes
-56bytes token0825 = read 56 bytes
-skip 6 bytes
-int loginTime = read int
-skip 1 byte
-String loginIP = read 4 bytes and join them with separator "."
-16bytes tgtgtKey = random 16 bytes
-
-### C -> S - Requiring `08 25 31 02`
-
-#### Var
-
-type | var name | value/from
----- | ---|---
-int | qq | |
-String | server ip | from redirection packet
-
-#### Packet data
-
-type | value
----- | ---
-hex |#head
-hex |#ver
-hex |08 25 31 02
-int |qq
-hex |#fixver
-hex |#redirectionKey
-bytes |[TEA encrypted data](#tea-encrypted-data)
-
-##### TEA encrypted data
-Key : #redirectionKey
-
-type | value
----- | ---
-hex |#_0825data0
-hex |#_0825data2
-int |qq
-hex |00 01 00 00 03 09 00 0C 00 01
-4bytes |g_server(split with "." and convert to byte)
-hex |01 6F A1 58 22 01 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 03 00 19
-hex | #publicKey
-
-#### Note
-
-Send the packet to new server via port 8000
-
-
-### C -> S - Not Requiring(Submitting password) `08 36 31 03`
-
-#### Var
-
-type | var name | value/from
----- | ---|---
-int | qq | |
-String | password | |
-String | device name | UTF8 encoding. Sample: DESKTOP-M19QRYU
-16bytes | tgtgtKey | |
-bytes | MD5_1 | md5(password)
-bytes | MD5_2 | md5(MD5_1 + bytes{0, 0, 0, 0}} + qq.tobytes)
-
-#### Packet data
-
-type | value
----- | ---
-hex |#head
-hex |#ver
-hex |08 36 31 03
-int |qq
-hex |03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03 00 19
-hex |#publicKey
-hex | 00 00 00 10
-hex | EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA
-bytes |[TEA encrypted data](#tea-encrypted--data)
-
-##### TEA encrypted data
-Key : #shareKey
-
-type | value
----- | ---
-hex |01 12
-hex |00 38
-int |token0825(from [Packet data - Not Requiring](#packet-data---not-requiring))
-hex |03 0F
-int | device name length + 2
-int | device name length
-bytes | device name
-hex | 00 05 00 06 00 02
-int | qq
-hex | 00 06 00 78
-bytes | [TLV0006](Get_tlv_0006.md) Using md5 that you just calculated in
-hex | fix = 00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B
-hex | 00 1A 00 40
-bytes | TEAEncrypt(fix, tgtgtKey)
-hex | #_0825data0
-hex | #_0825data2
-int | qq
-hex | 00 00 00 00
-hex | 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6 03 12 00 05 01 00 00 00 01 05 08 00 05 01 00 00 00 00 03 13 00 19 01 01 02 00 10 04 EA 78 D1 A4 FF CD CC 7C B8 D4 12 7D BB 03 AA
-hex | 00 00 00 00
-hex | 01 02 00 62 00 01 04 EB B7 C1 86 F9 08 96 ED 56 84 AB 50 85 2E 48 00 38 E9 AA 2B 4D 26 4C 76 18 FE 59 D5 A9 82 6A 0C 04 B4 49 50 D7 9B B1 FE 5D 97 54 8D 82 F3 22 C2 48 B9 C9 22 69 CA 78 AD 3E 2D E9 C9 DF A8 9E 7D 8C 8D 6B DF 4C D7 34 D0 D3 00 14
-bytes | CRCKey = random 16
-bytes | getCRC(CRCKey) //do it yourself
diff --git a/document/protocol/Touch.md b/document/protocol/Touch.md
deleted file mode 100644
index 469b6da68..000000000
--- a/document/protocol/Touch.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# TIM Protocol
-
-## Touch
-
-### C -> S
-
-#### Var
-type | var name | value/from
----- | ---|---
-int |g_qq | qq number
-int| g_server| server ip
-
-#### Packet data
-
-type | value
----- | ---
-hex | #head
-hex | #ver
-int | 08 25 31 01
-int | g_qq
-hex |#fixVer
-hex |#_0825key
-?bytes |TEA加密1
-hex |#tail
-
-
-TEA加密1, key = #_0825key:
-
-type | value
----- | ---
-hex | #_0825data0
-hex | #_0825data2
-int | g_qq
-hex | 00 00 00 00 03 09 00 08 00 01
-int | g_server
-hex | 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
-hex | #publicKey
-
-### S -> C
-
-[Redirection](Redirection.md)
\ No newline at end of file
diff --git a/mirai-core/pom.xml b/mirai-core/pom.xml
index 4f0afe652..1d8de0d46 100644
--- a/mirai-core/pom.xml
+++ b/mirai-core/pom.xml
@@ -17,6 +17,15 @@
+
+
+ jpcap
+ jpcap
+ 0.1.18-002
+ system
+ ${project.basedir}/lib/jpcap.jar
+
+
org.pcap4j
pcap4j-core
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java b/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java
index 208e6536b..d38157244 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/MiraiServer.java
@@ -198,21 +198,7 @@ public final class MiraiServer {
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";
+ "3034551466----zxcvbnm\n";
private Bot getAvailableBot() throws ExecutionException, InterruptedException {
for (String it : qqList.split("\n")) {
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/event/events/bot/BotEvent.java b/mirai-core/src/main/java/net/mamoe/mirai/event/events/bot/BotEvent.java
index e4ce2830a..9f097a979 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/event/events/bot/BotEvent.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/event/events/bot/BotEvent.java
@@ -10,7 +10,7 @@ import java.util.Objects;
* @author Him188moe
*/
public abstract class BotEvent extends MiraiEvent {
- private final Bot bot;
+ public final Bot bot;
public BotEvent(@NotNull Bot bot) {
this.bot = Objects.requireNonNull(bot);
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/event/events/group/GroupMessageEvent.java b/mirai-core/src/main/java/net/mamoe/mirai/event/events/group/GroupMessageEvent.java
index 77e71e0d9..e1ae59106 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/event/events/group/GroupMessageEvent.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/event/events/group/GroupMessageEvent.java
@@ -10,25 +10,25 @@ import org.jetbrains.annotations.NotNull;
* @author Him188moe
*/
public final class GroupMessageEvent extends GroupEvent {
- private final QQ sender;
- private final MessageChain messageChain;
- private final String messageString;
+ public final QQ sender;
+ public final MessageChain chain;
+ public final String message;
- public GroupMessageEvent(@NotNull Bot bot, @NotNull Group group, @NotNull QQ sender, @NotNull MessageChain messageChain) {
+ public GroupMessageEvent(@NotNull Bot bot, @NotNull Group group, @NotNull QQ sender, @NotNull MessageChain chain) {
super(bot, group);
this.sender = sender;
- this.messageChain = messageChain;
- this.messageString = messageChain.toString();
+ this.chain = chain;
+ this.message = chain.toString();
}
@NotNull
- public MessageChain getMessageChain() {
- return messageChain;
+ public MessageChain getChain() {
+ return chain;
}
@NotNull
- public String getMessageString() {
- return messageString;
+ public String getMessage() {
+ return message;
}
@NotNull
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendEvent.java b/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendEvent.java
index 7e34ba5f4..4cec1d5ee 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendEvent.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendEvent.java
@@ -11,15 +11,15 @@ import java.util.Objects;
* @author Him188moe
*/
public abstract class FriendEvent extends BotEvent {
- private final QQ qq;
+ public final QQ sender;
- public FriendEvent(@NotNull Bot bot, @NotNull QQ qq) {
+ public FriendEvent(@NotNull Bot bot, @NotNull QQ sender) {
super(bot);
- this.qq = Objects.requireNonNull(qq);
+ this.sender = Objects.requireNonNull(sender);
}
@NotNull
- public QQ getQQ() {
- return qq;
+ public QQ getSender() {
+ return sender;
}
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendMessageEvent.java b/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendMessageEvent.java
index 3952db4a3..80548798e 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendMessageEvent.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/event/events/qq/FriendMessageEvent.java
@@ -11,15 +11,15 @@ import java.util.Objects;
* @author Him188moe
*/
public final class FriendMessageEvent extends FriendEvent {
- private final MessageChain messageChain;
+ public final MessageChain message;
- public FriendMessageEvent(@NotNull Bot bot, @NotNull QQ sender, @NotNull MessageChain messageChain) {
+ public FriendMessageEvent(@NotNull Bot bot, @NotNull QQ sender, @NotNull MessageChain message) {
super(bot, sender);
- this.messageChain = Objects.requireNonNull(messageChain);
+ this.message = Objects.requireNonNull(message);
}
@NotNull
public MessageChain message() {
- return messageChain;
+ return message;
}
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java b/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java
index 2a8c11952..7245b721f 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/FaceID.java
@@ -166,7 +166,7 @@ public enum FaceID {
return value;
}
}
- return null;
+ return FaceID.unknown;
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt
index a44184591..a6f31088c 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/Message.kt
@@ -74,7 +74,7 @@ abstract class Message {
/**
* 比较两个 Message 的内容是否相等. 如:
* - [PlainText] 比较 [PlainText.text]
- * - [Image] 比较 [Image.imageID]
+ * - [Image] 比较 [Image.imageId]
*/
abstract infix fun valueEquals(another: Message): Boolean
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/MessageId.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/MessageId.kt
index 4e4f3dcad..024736652 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/MessageId.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/MessageId.kt
@@ -10,13 +10,13 @@ package net.mamoe.mirai.message
*/
object MessageId {
- const val AT: Int = 0x00//todo 不知道是多少
+ const val AT: Int = 0x06
- const val FACE: Int = 0x00//todo 不知道是多少
+ const val FACE: Int = 0x02
const val TEXT: Int = 0x01
- const val IMAGE: Int = 0x06
+ const val IMAGE: Int = 0x03
const val CHAIN: Int = 0xff//仅用于 equals. Packet 中不存在 Chain 概念
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt
index c22d31fe9..a3edd2f3d 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Face.kt
@@ -3,8 +3,10 @@ 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.readLVNumber
import net.mamoe.mirai.network.packet.writeHex
import net.mamoe.mirai.network.packet.writeLVByteArray
+import net.mamoe.mirai.utils.lazyDecode
import net.mamoe.mirai.utils.lazyEncode
/**
@@ -41,5 +43,18 @@ class Face(val id: FaceID) : Message() {
}
return this.id == another.id
}
+
+ companion object {
+ fun ofByteArray(data: ByteArray): Face = lazyDecode(data) {
+ //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
+ it.skip(1)
+
+ val id1 = FaceID.ofId(it.readLVNumber().toInt())//可能这个是id, 也可能下面那个
+ it.skip(it.readByte().toLong())
+ it.readLVNumber()//某id?
+ return@lazyDecode Face(id1)
+ }
+ }
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt
index 6204f9c82..c50cbcc7d 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/Image.kt
@@ -2,30 +2,33 @@ package net.mamoe.mirai.message.defaults
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.network.packet.writeLVString
+import net.mamoe.mirai.network.packet.*
+import net.mamoe.mirai.utils.lazyDecode
import net.mamoe.mirai.utils.lazyEncode
+import net.mamoe.mirai.utils.skip
+import net.mamoe.mirai.utils.toUHexString
/**
* 图片消息.
* 由接收消息时构建, 可直接发送
*
+ * @param imageId 类似 `{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg`. 群的是大写id, 好友的是小写id
+ *
* @author Him188moe
*/
-open class Image internal constructor(val imageID: String) : Message() {
+open class Image internal constructor(val imageId: String) : Message() {
override val type: Int = MessageId.IMAGE
override fun toStringImpl(): String {
- return imageID
+ return imageId
}
override fun toByteArray(): ByteArray = lazyEncode { section ->
- section.writeByte(0x03)//todo 可能是 0x03?
+ section.writeByte(MessageId.IMAGE)
section.writeLVByteArray(lazyEncode { child ->
child.writeByte(0x02)
- child.writeLVString(this.imageID)
+ child.writeLVString(this.imageId)
child.writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
@@ -35,16 +38,51 @@ open class Image internal constructor(val imageID: String) : Message() {
"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.writeBytes(this.imageId)
child.writeByte(0x41)
})
}
override fun valueEquals(another: Message): Boolean {
if (another is Image) {
- return this.imageID == another.imageID
+ return this.imageId == another.imageId
}
return false
}
+
+ companion object {
+ @JvmStatic
+ fun ofByteArray0x06(data: ByteArray): Image = lazyDecode(data) {
+ it.skip(1)
+ println("好友的图片")
+ println(data.toUHexString())
+ val filenameLength = it.readShort()
+ val suffix = it.readString(filenameLength).substringAfter(".")
+ it.skip(data.size - 37 - 1 - filenameLength - 2)
+ val imageId = String(it.readNBytes(36))
+ println(imageId)
+ it.skip(1)//0x41
+ return@lazyDecode Image("{$imageId}.$suffix")
+ }
+
+ @JvmStatic
+ fun ofByteArray0x03(data: ByteArray): Image = lazyDecode(data) {
+ it.skip(1)
+ return@lazyDecode Image(String(it.readLVByteArray()))
+ /*
+ println(String(it.readLVByteArray()))
+ it.readTLVMap()
+ return@lazyDecode Image(String(it.readLVByteArray().cutTail(5).getRight(42)))
+ /
+ it.skip(data.size - 47)
+ val imageId = String(it.readNBytes(42))
+ it.skip(1)//0x41
+ it.skip(1)//0x42
+ it.skip(1)//0x43
+ it.skip(1)//0x41
+
+ return@lazyDecode Image(imageId)*/
+ }
+ }
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt
index e14938561..a6d089dbd 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/PlainText.kt
@@ -2,8 +2,10 @@ package net.mamoe.mirai.message.defaults
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageId
+import net.mamoe.mirai.network.packet.readLVString
import net.mamoe.mirai.network.packet.writeLVByteArray
import net.mamoe.mirai.network.packet.writeLVString
+import net.mamoe.mirai.utils.lazyDecode
import net.mamoe.mirai.utils.lazyEncode
/**
@@ -31,4 +33,11 @@ class PlainText(private val text: String) : Message() {
}
return this.text == another.text
}
+
+ companion object {
+ fun ofByteArray(data: ByteArray): PlainText = lazyDecode(data) {
+ it.skip(1)
+ PlainText(it.readLVString())
+ }
+ }
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt
index a8afd0295..b3cf226b9 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/message/defaults/UnsolvedImage.kt
@@ -3,9 +3,9 @@ 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.image.ServerTryGetImageIDFailedPacket
+import net.mamoe.mirai.network.packet.image.ServerTryGetImageIDResponsePacket
+import net.mamoe.mirai.network.packet.image.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.network.packet.md5
import net.mamoe.mirai.utils.ImageNetworkUtils
import net.mamoe.mirai.utils.toByteArray
@@ -27,16 +27,16 @@ class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImage
constructor(url: URL) : this(File(url.file))
fun upload(session: LoginSession, contact: Contact): CompletableFuture {
- return session.expectPacket {
- toSend { ClientTryGetImageIDPacket(session.bot.account.qqNumber, session.sessionKey, session.bot.account.qqNumber, image) }
+ return session.expectPacket {
+ toSend { ClientTryGetImageIDPacket(session.bot.account.qqNumber, session.sessionKey, contact.number, image) }
expect {
when (it) {
- is ServerTryUploadGroupImageFailedPacket -> {
+ is ServerTryGetImageIDFailedPacket -> {
//已经存在于服务器了
}
- is ServerTryUploadGroupImageSuccessPacket -> {
+ is ServerTryGetImageIDSuccessPacket -> {
val data = image.toByteArray()
if (!ImageNetworkUtils.postImage(it.uKey.toUHexString(), data.size, session.bot.account.qqNumber, contact.number, data)) {
throw RuntimeException("cannot upload image")
@@ -56,8 +56,8 @@ class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImage
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")
+ .plus(md5.copyOfRange(8, 10).toUHexString("")) + "-"
+ .plus(md5.copyOfRange(10, 16).toUHexString("")) + "}." + if (filename.endsWith(".jpeg")) "jpg" else filename.substringAfter(".", "jpg")
}
}
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt
index b22c10713..e0887ba44 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt
@@ -239,10 +239,10 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
*/
inner class Login : Closeable {
private lateinit var token00BA: ByteArray
- private lateinit var token0825: ByteArray
+ private lateinit var token0825: ByteArray//56
private var loginTime: Int = 0
private lateinit var loginIP: String
- private var tgtgtKey: ByteArray = getRandomByteArray(16)
+ private var randomTgtgtKey: ByteArray = getRandomByteArray(16)
/**
* 0828_decr_key
@@ -266,7 +266,8 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
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))
+ println("token0825=" + this.token0825.toUHexString())
+ socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.randomTgtgtKey, packet.token0825))
}
}
@@ -276,9 +277,9 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
}
is ServerVerificationCodeCorrectPacket -> {
- this.tgtgtKey = getRandomByteArray(16)
+ this.randomTgtgtKey = 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))
+ socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.randomTgtgtKey, this.token0825, this.token00BA))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
@@ -337,11 +338,11 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
//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.randomTgtgtKey = packet.tgtgtKey
+ socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, randomTgtgtKey, token0825, packet.tokenUnknown
?: this.token00BA, packet.tlv0006))
} else {
- socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown
+ socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, randomTgtgtKey, token0825, packet.tokenUnknown
?: token00BA, packet.tlv0006))
}
}
@@ -372,8 +373,8 @@ internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler {
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 ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.randomTgtgtKey))
+ is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.randomTgtgtKey))
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt
index 187d7327c..52dfe61cd 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.kt
@@ -2,6 +2,9 @@
package net.mamoe.mirai.network
+import net.mamoe.mirai.utils.hexToBytes
+import net.mamoe.mirai.utils.lazyDecode
+import net.mamoe.mirai.utils.readUnsignedVarInt
import java.net.InetAddress
import java.util.*
import java.util.stream.Collectors
@@ -44,10 +47,28 @@ object Protocol {
* 0825data2
*/
const val constantData2 = "00 00 04 53 00 00 00 01 00 00 15 85 "
- const val key0825 = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"
- const val redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"
- const val publicKey = "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"
- const val shareKey = "1A E9 7F 7D C9 73 75 98 AC 02 E0 80 5F A9 C6 AF"
+
+ /**
+ * 0825 key
+ *
+ * Touch 发出时写入, 并用于加密, 接受 touch response 时解密.
+ */
+ const val touchKey = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"//16
+
+ /**
+ * Redirection 发出时写入, 并用于加密, 接受 Redirection response 时解密.
+ */
+ const val redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"//16
+
+ /**
+ *
+ */
+ const val publicKey = "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"//25
+
+ /**
+ * 没有任何地方写入了这个 key
+ */
+ const val shareKey = "1A E9 7F 7D C9 73 75 98 AC 02 E0 80 5F A9 C6 AF"//16
const val fix0836 = "06 A9 12 97 B7 F8 76 25 AF AF D3 EA B4 C8 BC E7 "
const val key00BA = "C1 9C B8 C8 7B 8C 81 BA 9E 9E 7A 89 E1 7A EC 94"
@@ -56,21 +77,28 @@ object Protocol {
/**
* 0836_622_fix2
*/
- const val passwordSubmissionKey2 = "00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B";
+ const val passwordSubmissionTLV2 = "00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B";
/**
* 0836_622_fix1
*/
- const val passwordSubmissionKey1 = "03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03 00 19";
+ const val passwordSubmissionTLV1 = "03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03"//19
+ // 最新版 03 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 02 01 03
+ // 第一版 1.0.2 03 00 00 00 01 2E 01 00 00 68 13 00 00 00 00 00 02 01 03
+ // 1.0.4 03 00 00 00 01 2E 01 00 00 68 27 00 00 00 00 00 02 01 03
+ // 1.1 03 00 00 00 01 2E 01 00 00 68 3F 00 00 00 00 00 02 01 03
+ // 1.2 03 00 00 00 01 2E 01 00 00 68 44 00 00 00 00 00 02 01 03
/**
* fix_0836_1
+ *
+ * LoginResend 和 PasswordSubmission 时写入, 但随后都使用 shareKey 加密, 收到回复也是用的 share key
*/
- const val key0836 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"
+ const val key0836 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"//16
/**
* 发送/接受消息中的一个const (?)
* length=15
*/
- const val friendMessageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
+ const val messageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
private val hexToByteArrayCacheMap: MutableMap = mutableMapOf()
@@ -96,3 +124,10 @@ object Protocol {
.collect(Collectors.toList()).toUByteArray()
}
+
+fun main() {
+ lazyDecode("03 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 01 01 03".hexToBytes()) {
+ it.skip(7)
+ println(it.readUnsignedVarInt())
+ }
+}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt
index bd68f3323..68e804e9c 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt
@@ -6,9 +6,9 @@ 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.network.packet.image.ServerTryGetImageIDFailedPacket
+import net.mamoe.mirai.network.packet.image.ServerTryGetImageIDResponsePacket
+import net.mamoe.mirai.network.packet.image.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.getGTK
import java.awt.image.BufferedImage
@@ -39,15 +39,15 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
it.onPacketReceived(packet)
}
}
- is ServerTryUploadGroupImageSuccessPacket -> {
+ is ServerTryGetImageIDSuccessPacket -> {
// ImageNetworkUtils.postImage(packet.uKey.toUHexString(), )
}
- is ServerTryUploadGroupImageFailedPacket -> {
+ is ServerTryGetImageIDFailedPacket -> {
}
- is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
+ is ServerTryGetImageIDResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey))
is ServerAccountInfoResponsePacket -> {
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt
index 728bd20c3..8a95cc32a 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt
@@ -2,18 +2,21 @@ package net.mamoe.mirai.network.handler
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.event.events.group.GroupMessageEvent
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
import net.mamoe.mirai.event.hookWhile
+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.UnsolvedImage
import net.mamoe.mirai.network.LoginSession
-import net.mamoe.mirai.network.packet.*
+import net.mamoe.mirai.network.packet.ServerFriendMessageEventPacket
+import net.mamoe.mirai.network.packet.ServerGroupMessageEventPacket
+import net.mamoe.mirai.network.packet.ServerGroupUploadFileEventPacket
+import net.mamoe.mirai.network.packet.ServerPacket
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
/**
* 处理消息事件, 承担消息发送任务.
@@ -31,21 +34,39 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
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 valueEquals "你好" -> it.sender.sendMessage("你好!")
+ it.message.toString().startsWith("复读") -> it.sender.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()
- })
+ /*it.message valueEquals "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
+ image.upload(session, Group(session.bot, 580266363)).get()
+ })*/
+ it.message valueEquals "发图片群2" -> sendGroupMessage(Group(session.bot, 580266363), Image("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg").toChain())
+ /* it.message valueEquals "发图片" -> sendFriendMessage(it.sender, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
+ image.upload(session, it.sender).get()
+ })*/
+ it.message valueEquals "发图片2" -> sendFriendMessage(it.sender, PlainText("test") + Image("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))
}
return@hookWhile true
}
+
+ GroupMessageEvent::class.hookWhile {
+ if (session.socket.isClosed()) {
+ return@hookWhile false
+ }
+
+ when {
+ it.message.contains("复读") -> it.group.sendMessage(it.chain)
+ }
+
+ return@hookWhile true
+
+ }
}
override fun onPacketReceived(packet: ServerPacket) {
@@ -55,20 +76,17 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
}
is ServerFriendMessageEventPacket -> {
- if (ignoreMessage) {
- return
- }
+ 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)
- }
+ if (ignoreMessage) return
- is UnknownServerEventPacket -> {
- //todo
+ if (packet.qq == session.bot.account.qqNumber) return
+
+ GroupMessageEvent(session.bot, session.bot.contacts.getGroupByNumber(packet.groupNumber), session.bot.contacts.getQQ(packet.qq), packet.message).broadcast()
}
is ServerSendFriendMessageResponsePacket,
@@ -86,6 +104,6 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
}
fun sendGroupMessage(group: Group, message: MessageChain) {
- session.socket.sendPacket(ClientSendGroupMessagePacket(group.groupId, session.bot.account.qqNumber, session.sessionKey, message))
+ session.socket.sendPacket(ClientSendGroupMessagePacket(session.bot.account.qqNumber, group.groupId, session.sessionKey, message))
}
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt
index d0d048630..a20c1e81e 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ClientPacket.kt
@@ -122,28 +122,34 @@ fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutp
@Throws(IOException::class)
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
- ByteArrayDataOutputStream().let {
+ val firstMD5 = md5(password)
+ val secondMD5 = md5(firstMD5 + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray())
+
+ this.encryptAndWrite(secondMD5) {
it.writeRandom(4)
it.writeHex("00 02")
it.writeQQ(qq)
it.writeHex(Protocol.constantData2)
it.writeHex("00 00 01")
- val firstMD5 = md5(password)
- val secondMD5 = md5(firstMD5 + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray())
it.write(firstMD5)
it.writeInt(loginTime)
it.writeByte(0)
it.writeZero(4 * 3)
it.writeIP(loginIP)
it.writeZero(8)
- it.writeHex("00 10")
- it.writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")
+ it.writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
+ it.writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
it.write(tgtgtKey)
- this.write(TEA.encrypt(it.toByteArray(), secondMD5))
}
}
+fun main() {
+ println(lazyEncode {
+ it.writeTLV0006(1040400290, "asdHim188moe", System.currentTimeMillis().toInt(), "123.123.123.123", getRandomByteArray(56))
+ }.size)
+}
+
@Tested
fun DataOutputStream.writeCRC32() = writeCRC32(getRandomByteArray(16))
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt
index 7b779d718..fea131438 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt
@@ -2,19 +2,17 @@
package net.mamoe.mirai.network.packet
-import net.mamoe.mirai.message.FaceID
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.Face
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.network.Protocol
-import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.hexToBytes
+import net.mamoe.mirai.utils.lazyDecode
import net.mamoe.mirai.utils.toUHexString
-import java.io.ByteArrayOutputStream
+import net.mamoe.mirai.utils.toUInt
import java.io.DataInputStream
-import java.util.zip.GZIPInputStream
/**
* Packet id: `00 CE` or `00 17`
@@ -59,7 +57,12 @@ abstract class ServerEventPacket(input: DataInputStream, val packetId: ByteArray
/**
* Unknown event
*/
-class UnknownServerEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
+class UnknownServerEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
+ override fun decode() {
+ super.decode()
+ println("UnknownServerEventPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
+ }
+}
/**
* Android 客户端上线
@@ -86,8 +89,8 @@ class ServerGroupUploadFileEventPacket(input: DataInputStream, packetId: ByteArr
class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
var groupNumber: Long = 0
var qq: Long = 0
- lateinit var message: String
- lateinit var messageType: MessageType
+ lateinit var senderName: String
+ lateinit var message: MessageChain
enum class MessageType {
NORMAL,
@@ -107,10 +110,29 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
override fun decode() {
println(this.input.goto(0).readAllBytes().toUHexString())
groupNumber = this.input.goto(51).readInt().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
+ qq = this.input.goto(56).readNBytes(4).toUInt().toLong()
+ this.input.goto(108)
+ this.input.readLVByteArray()
+ input.skip(2)//2个0x00
+ message = input.readSections()
+
+ val map = input.readTLVMap(true)
+ if (map.containsKey(18)) {
+ this.senderName = lazyDecode(map.getValue(18)) {
+ val tlv = it.readTLVMap(true)
+ tlv.printTLVMap()
+
+ when {
+ tlv.containsKey(0x01) -> String(tlv.getValue(0x01))
+ tlv.containsKey(0x02) -> String(tlv.getValue(0x02))
+ else -> "null"
+ }
+ }
+ }
+
+
+ /*
messageType = when (val id = this.input.goto(110 + fontLength + 2).readByte().toInt()) {
0x13 -> MessageType.NORMAL
0x0E -> MessageType.XML
@@ -126,9 +148,9 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
MiraiLogger.debug("ServerGroupMessageEventPacket id=$id")
MessageType.OTHER
}
- }
-
+ }*/
+/*
when (messageType) {
MessageType.NORMAL -> {
val gzippedMessage = this.input.goto(110 + fontLength + 16).readNBytes(this.input.goto(110 + fontLength + 3).readShort().toInt() - 11)
@@ -184,10 +206,26 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
}
}*/
}
- }
+ }*/
}
}
+fun main() {
+ println(String("E7 BE A4".hexToBytes()))
+
+
+ println(".".toByteArray().toUByteArray().toUHexString())
+ //长文本 22 96 29 7B B4 DF 94 AA 00 01 9F 8E 09 18 85 5B 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7E F3 5D 7B 97 57 00 00 F3 32 00 B8 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B 97 56 7F D0 53 BB 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 12 01 00 0F E9 95 BF E6 96 87 E6 9C AC E6 B6 88 E6 81 AF 0E 00 0E 01 00 04 00 00 00 09 07 00 04 00 00 00 01 19 00 35 01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 B8 03 00 C0 03 00 D0 03 00 E8 03 00 12 00 25 05 00 04 00 00 00 01 08 00 04 00 00 00 01 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
+ val packet = ServerGroupMessageEventPacket(("" +
+ "22 96 29 7B B4 DF 94 AA 00 09 8F 37 0A 65 07 2E 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7F 67 5D 7B AE D7 00 00 F3 36 02 E7 00 02 02 00 1B 10 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B AE D6 F4 91 87 BE 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 CB 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 83 81 3B E2 05 00 04 B8 8B 33 79 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 5C 15 36 20 39 32 6B 41 31 43 38 33 38 31 33 62 65 32 62 38 38 62 33 33 37 39 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 41 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 77 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 83 81 3B E2 05 00 04 B8 8B 33 79 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 08 15 37 20 20 38 41 41 41 02 00 14 01 00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0 03 00 CE 02 00 2A 7B 31 46 42 34 43 32 35 45 2D 42 34 46 45 2D 31 32 45 34 2D 46 33 42 42 2D 38 31 39 31 33 37 42 44 39 39 30 39 7D 2E 6A 70 67 04 00 04 B8 27 4B C6 05 00 04 79 5C B1 A3 06 00 04 00 00 00 50 07 00 01 41 08 00 00 09 00 01 01 0B 00 00 14 00 04 03 00 00 00 15 00 04 00 00 00 4E 16 00 04 00 00 00 23 18 00 04 00 00 02 A2 FF 00 5F 15 36 20 39 35 6B 44 31 41 62 38 32 37 34 62 63 36 37 39 35 63 62 31 61 33 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 31 46 42 34 43 32 35 45 2D 42 34 46 45 2D 31 32 45 34 2D 46 33 42 42 2D 38 31 39 31 33 37 42 44 39 39 30 39 7D 2E 6A 70 67 41 42 43 41 0E 00 07 01 00 04 00 00 00 09 19 00 38 01 00 35 AA 02 32 50 03 60 00 68 00 9A 01 29 08 09 20 BF 02 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 B8 03 00 C0 03 00 D0 03 00 E8 03 00 12 00 25 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08 05 00 04 00 00 00 01 08 00 04 00 00 00 01" +
+ "").hexToBytes().dataInputStream(), byteArrayOf(), byteArrayOf())
+ packet.decode()
+ println(packet)
+}
+
+//牛逼[图片]牛逼[图片] 22 96 29 7B B4 DF 94 AA 00 08 74 A4 09 18 8D CC 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7F 64 5D 7B AC BD 00 00 F3 36 02 03 00 02 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B AC BD 12 73 DB A2 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 CB 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 B4 52 77 F1 05 00 04 BC EB 03 B7 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 5C 15 36 20 39 32 6B 41 31 43 62 34 35 32 37 37 66 31 62 63 65 62 30 33 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 41 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 77 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 B4 52 77 F1 05 00 04 BC EB 03 B7 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 08 15 37 20 20 38 41 41 41 0E 00 0E 01 00 04 00 00 00 09 07 00 04 00 00 00 01 19 00 35 01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 B8 03 00 C0 03 00 D0 03 00 E8 03 00 12 00 25 05 00 04 00 00 00 01 08 00 04 00 00 00 01 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
+//牛逼[图片]牛逼 22 96 29 7B B4 DF 94 AA 00 0B C1 0A 09 18 89 93 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7E F5 5D 7B 97 E7 00 00 F3 32 01 8D 00 02 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B 97 E6 FA BE 7F DC 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 CF 02 00 2A 7B 39 44 32 44 45 39 31 41 2D 33 39 38 39 2D 39 35 35 43 2D 44 35 42 34 2D 37 46 41 32 37 38 39 37 38 36 30 39 7D 2E 6A 70 67 04 00 04 97 15 7F 03 05 00 04 79 5C B1 A3 06 00 04 00 00 00 50 07 00 01 41 08 00 00 09 00 01 01 0B 00 00 14 00 04 03 00 00 00 15 00 04 00 00 00 3C 16 00 04 00 00 00 40 18 00 04 00 00 03 CC FF 00 60 15 36 20 39 36 6B 45 31 41 39 37 31 35 37 66 30 33 37 39 35 63 62 31 61 33 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 39 44 32 44 45 39 31 41 2D 33 39 38 39 2D 39 35 35 43 2D 44 35 42 34 2D 37 46 41 32 37 38 39 37 38 36 30 39 7D 2E 6A 70 67 31 32 31 32 41 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 0E 01 00 04 00 00 00 09 07 00 04 00 00 00 01 19 00 35 01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 B8 03 00 C0 03 00 D0 03 00 E8 03 00 12 00 25 05 00 04 00 00 00 01 08 00 04 00 00 00 01 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
+
class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
var qq: Long = 0
lateinit var message: MessageChain
@@ -195,18 +233,21 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
override fun decode() {
input.goto(0)
- println()
- println(input.readAllBytes().toUHexString())
+ println("ServerFriendMessageEventPacket.input=" + input.readAllBytes().toUHexString())
input.goto(0)
qq = input.readUIntAt(0).toLong()
val l1 = input.readShortAt(22)
input.goto(93 + l1)
- input.readVarByteArray()//font
+ input.readLVByteArray()//font
input.skip(2)//2个0x00
message = input.readSections()
- println(message.toObjectString())
+
+ val map: Map = input.readTLVMap(true).withDefault { byteArrayOf() }
+ println(map.getValue(18))
+
+ //19 00 38 01 00 35 AA 02 32 50 03 60 00 68 00 9A 01 29 08 09 20 BF 02 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 B8 03 00 C0 03 00 D0 03 00 E8 03 00 12 00 25 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08 05 00 04 00 00 00 01 08 00 04 00 00 00 01
/*
val offset = unknownLength0 + fontLength//57
@@ -215,50 +256,66 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
input.goto(103 + offset).readString(length.toInt())
}))*/
}
+}
- private fun DataInputStream.readSection(): Message? {
- val messageType = this.readByte().toInt()
- val sectionLength = this.readShort().toLong()//sectionLength: short
- this.skip(1)//message和face是 0x01, image是0x06
- return when (messageType) {
- 0x01 -> PlainText(readVarString())
- 0x02 -> {
- //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
+private fun DataInputStream.readSection(): Message? {
+ val messageType = this.readByte().toInt()
+ val sectionLength = this.readShort().toLong()//sectionLength: short
+ val sectionData = this.readNBytes(sectionLength)
+ return when (messageType) {
+ 0x01 -> PlainText.ofByteArray(sectionData)
+ 0x02 -> Face.ofByteArray(sectionData)
+ 0x03 -> Image.ofByteArray0x03(sectionData)
+ 0x06 -> Image.ofByteArray0x06(sectionData)
- val id1 = FaceID.ofId(readLVNumber().toInt())//可能这个是id, 也可能下面那个
- this.skip(this.readByte().toLong())
- 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}.jpg")//todo 如何确定文件后缀??
- }
- else -> null
+
+ 0x19 -> {//长文本
+ val value = readLVByteArray()
+ //todo 未知压缩算法
+ PlainText(String(value))
+
+ // PlainText(String(GZip.uncompress( value)))
}
+
+ 0x14 -> {//长文本
+ val value = readLVByteArray()
+ println(value.size)
+ println(value.toUHexString())
+ //todo 未知压缩算法
+ this.skip(7)//几个TLV
+ return PlainText(String(value))
+ }
+
+ 0x0E -> {
+ //null
+ null
+ }
+
+ else -> {
+ println("未知的messageType=0x${messageType.toByte().toUHexString()}")
+ println("后文=${this.readAllBytes().toUHexString()}")
+ null
+ }
}
- private fun DataInputStream.readSections(): MessageChain {
- val chain = MessageChain()
- var got: Message? = null
- do {
- if (got != null) {
- chain.concat(got)
- }
- got = this.readSection()
- } while (got != null)
- return chain
- }
}
-fun main() {
- println(String("16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 31 37 36 32 65 42 39 45 32 37 32 31 43 39 36 44 37 39 41 38 32 31 36 45 30 41 44 34 30 42 35 39 35 39 31 38 36 2E 6A 70 67 66 2F 65 64 33 39 30 66 38 34 2D 34 66 38 37 2D 34 36 64 63 2D 62 33 38 35 2D 34 35 35 36 62 35 31 30 61 61 35 33 41".replace(" ", " ").hexToBytes()))
- println(".jpg".toByteArray().size)
+private fun DataInputStream.readSections(): MessageChain {
+ val chain = MessageChain()
+ var got: Message? = null
+ do {
+ if (got != null) {
+ chain.concat(got)
+ }
+ if (this.available() == 0) {
+ return chain
+ }
+ got = this.readSection()
+ } while (got != null)
+ return chain
}
+
/*
牛逼 (10404
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt
index 580664e0f..f51196cd0 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerPacket.kt
@@ -11,7 +11,7 @@ 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.image.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.packet.login.*
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.*
@@ -82,7 +82,9 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
println(bytes.size)
return ServerLoginResponseFailedPacket(when (bytes.size) {
- 63, 319, 135, 351 -> LoginState.WRONG_PASSWORD//这四个其中一个是被冻结
+ 135 -> LoginState.UNKNOWN//账号已经在另一台电脑登录??
+
+ 63, 319, 351 -> LoginState.WRONG_PASSWORD//63不是密码错误, 应该是登录过频繁
//135 -> LoginState.RETYPE_PASSWORD
279 -> LoginState.BLOCKED
263 -> LoginState.UNKNOWN_QQ_NUMBER
@@ -121,9 +123,9 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
"00 A7" -> ServerCanAddFriendResponsePacket(stream)
- "03 88" -> ServerTryUploadGroupImageResponsePacket.Encrypted(stream)
+ "03 88" -> ServerTryGetImageIDResponsePacket.Encrypted(stream)
- else -> throw IllegalArgumentException(idHex)
+ else -> UnknownServerPacket(stream)
}
}.apply { this.idHex = idHex }
}
@@ -197,15 +199,48 @@ fun DataInputStream.readIP(): String {
return buff
}
-fun DataInputStream.readVarString(): String {
- return String(this.readVarByteArray())
+fun DataInputStream.readLVString(): String {
+ return String(this.readLVByteArray())
}
-fun DataInputStream.readVarByteArray(): ByteArray {
+fun DataInputStream.readLVByteArray(): ByteArray {
return this.readNBytes(this.readShort().toInt())
}
-fun DataInputStream.readString(length: Int): String {
+fun DataInputStream.readTLVMap(expectingEOF: Boolean = false): Map {
+ val map = mutableMapOf()
+ var type: Int
+
+ try {
+ type = readUnsignedByte()
+ } catch (e: EOFException) {
+ if (expectingEOF) {
+ return map
+ }
+ throw e
+ }
+
+ while (type != 0xff) {
+ map[type] = this.readLVByteArray()
+
+ try {
+ type = readUnsignedByte()
+ } catch (e: EOFException) {
+ if (expectingEOF) {
+ return map
+ }
+ throw e
+ }
+ }
+ return map
+}
+
+fun Map.printTLVMap() {
+ println(this.mapValues { (_, value) -> value.toUHexString() })
+}
+
+
+fun DataInputStream.readString(length: Number): String {
return String(this.readNBytes(length))
}
@@ -257,6 +292,10 @@ fun DataInputStream.readUIntAt(position: N): UInt {
return this.readNBytes(4).toUInt()
}
+fun DataInputStream.readUInt(): UInt {
+ return this.readNBytes(4).toUInt()
+}
+
fun DataInputStream.readByteAt(position: N): Byte {
this.goto(position)
return this.readByte()
@@ -290,8 +329,8 @@ fun DataInputStream.gotoWhere(matcher: ByteArray): DataInputStream {
if (b != matcher[i]) {
continue@loop //todo goto mark
}
- return this
}
+ return this
}
} while (true)
}
@@ -336,4 +375,6 @@ fun DataInputStream.gotoWhere(matcher: ByteArray) {
} while (true)
}*/
-fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
\ No newline at end of file
+fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
+
+fun ByteArray.getRight(length: Int): ByteArray = this.copyOfRange(this.size - length, this.size)
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt
index 1b9945670..f02c0a1b1 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Touch.kt
@@ -24,7 +24,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
var loginTime: Int = 0
lateinit var loginIP: String
- lateinit var token0825: ByteArray
+ lateinit var token0825: ByteArray//56
enum class Type {
TYPE_08_25_31_01,
@@ -57,7 +57,7 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
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()
+ Type.TYPE_08_25_31_01 -> Protocol.touchKey.hexToBytes()
})).setId(this.idHex)
}
}
@@ -75,9 +75,9 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
override fun encode() {
this.writeQQ(qq)
this.writeHex(Protocol.fixVer)
- this.writeHex(Protocol.key0825)
+ this.writeHex(Protocol.touchKey)
- this.encryptAndWrite(Protocol.key0825) {
+ this.encryptAndWrite(Protocol.touchKey) {
it.writeHex(Protocol.constantData1)
it.writeHex(Protocol.constantData2)
it.writeQQ(qq)
@@ -94,7 +94,6 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
*
* @author Him188moe
*/
-
@PacketId("08 25 31 02")
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/UnknownServerPacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/UnknownServerPacket.kt
index 9452ee380..37ec68e16 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/UnknownServerPacket.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/UnknownServerPacket.kt
@@ -1,5 +1,7 @@
package net.mamoe.mirai.network.packet
+import net.mamoe.mirai.utils.LoggerTextFormat
+import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
@@ -7,6 +9,10 @@ import java.io.DataInputStream
*/
class UnknownServerPacket(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
+ println("UnknownServerPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
+ }
+ override fun toString(): String {
+ return LoggerTextFormat.LIGHT_RED.toString() + super.toString()
}
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt
index 484a19e42..02ed89c4e 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt
@@ -72,15 +72,15 @@ class ClientVerificationCodeSubmitPacket(
it.writeHex("01 03")
it.writeShort(25)
- it.writeHex(Protocol.publicKey)
+ it.writeHex(Protocol.publicKey)//25
it.writeHex("14 00 05 00 00 00 00 00 04")
it.write(verificationCode.toUpperCase().toByteArray())
it.writeHex("00 38")
it.write(verificationToken)
- it.writeHex("00 10")
- it.writeHex(Protocol.key00BAFix)
+ it.writeShort(16)
+ it.writeHex(Protocol.key00BAFix)//16
}
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt
index d60aeacb1..10a7ecc08 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendFriendMessagePacket.kt
@@ -38,7 +38,7 @@ class ClientSendFriendMessagePacket(
it.writeTime()
it.writeRandom(4)
it.writeHex("00 00 00 00 09 00 86")
- it.writeHex(Protocol.friendMessageConst1)//... 85 E9 BB 91
+ it.writeHex(Protocol.messageConst1)//... 85 E9 BB 91
it.writeZero(2)
it.write(message.toByteArray())
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt
index bc480103d..42479a511 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/action/ClientSendGroupMessagePacket.kt
@@ -3,6 +3,7 @@ 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.lazyEncode
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
@@ -11,8 +12,8 @@ import java.io.DataInputStream
*/
@PacketId("00 02")
class ClientSendGroupMessagePacket(
- private val groupId: Long,//不是 number
private val botQQ: Long,
+ private val groupId: Long,//不是 number
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
@@ -26,18 +27,19 @@ class ClientSendGroupMessagePacket(
it.writeByte(0x2A)
it.writeGroup(groupId)
- 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.writeLVByteArray(lazyEncode { child ->
+ child.writeHex("00 01 01")
+ child.writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
- it.writeTime()
- it.writeRandom(4)
- it.writeHex("00 00 00 00 09 00 86")
- it.writeHex(Protocol.friendMessageConst1)
- it.writeZero(2)
+ child.writeTime()
+ child.writeRandom(4)
+ child.writeHex("00 00 00 00 09 00 86")
+ child.writeHex(Protocol.messageConst1)
+ child.writeZero(2)
- //messages
- it.write(bytes)
+ //messages
+ child.write(bytes)
+ })
/*it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt
index 839f9c8a9..f72a59020 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/image/UploadGroupImage.kt
@@ -11,8 +11,8 @@ import java.io.DataInputStream
/**
* 请求上传图片. 将发送图片的 md5, size.
* 服务器返回以下之一:
- * - 服务器已经存有这个图片 [ServerTryUploadGroupImageFailedPacket]
- * - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryUploadGroupImageSuccessPacket]
+ * - 服务器已经存有这个图片 [ServerTryGetImageIDFailedPacket]
+ * - 服务器未存有, 返回一个 key 用于客户端上传 [ServerTryGetImageIDSuccessPacket]
*
* @author Him188moe
*/
@@ -20,7 +20,7 @@ import java.io.DataInputStream
class ClientTryGetImageIDPacket(
private val botNumber: Long,
private val sessionKey: ByteArray,
- private val groupNumberOrQQNumber: Long,//todo 为什么还要有qq number呢? bot不就是了么
+ private val groupNumberOrQQNumber: Long,
private val image: BufferedImage
) : ClientPacket() {
override fun encode() {
@@ -89,18 +89,18 @@ class ClientTryGetImageIDPacket(
}
}
-abstract class ServerTryUploadGroupImageResponsePacket(input: DataInputStream) : ServerPacket(input) {
+abstract class ServerTryGetImageIDResponsePacket(input: DataInputStream) : ServerPacket(input) {
class Encrypted(input: DataInputStream) : ServerPacket(input) {
- fun decrypt(sessionKey: ByteArray): ServerTryUploadGroupImageResponsePacket {
+ fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
println(data.size)
println(data.size)
if (data.size == 209) {
- return ServerTryUploadGroupImageSuccessPacket(data.dataInputStream()).setId(this.idHex)
+ return ServerTryGetImageIDSuccessPacket(data.dataInputStream()).setId(this.idHex)
}
- return ServerTryUploadGroupImageFailedPacket(data.dataInputStream())
+ return ServerTryGetImageIDFailedPacket(data.dataInputStream())
}
}
}
@@ -108,25 +108,21 @@ abstract class ServerTryUploadGroupImageResponsePacket(input: DataInputStream) :
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
-class ServerTryUploadGroupImageSuccessPacket(input: DataInputStream) : ServerTryUploadGroupImageResponsePacket(input) {
+class ServerTryGetImageIDSuccessPacket(input: DataInputStream) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray
override fun decode() {
- uKey = this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u)).readNBytes(128)
+ this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))
+ uKey = this.input.readNBytes(128)
}
}
/**
* 服务器已经存有这个图片
*/
-class ServerTryUploadGroupImageFailedPacket(input: DataInputStream) : ServerTryUploadGroupImageResponsePacket(input) {
+class ServerTryGetImageIDFailedPacket(input: DataInputStream) : ServerTryGetImageIDResponsePacket(input) {
override fun decode() {
}
-}
-
-fun main() {
-
- println(0xff)
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt
index cb6807a56..d6ab27eb2 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/login/ClientLogin.kt
@@ -2,7 +2,6 @@ package net.mamoe.mirai.network.packet.login
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.Tested
import net.mamoe.mirai.utils.hexToBytes
@@ -14,21 +13,23 @@ import java.io.DataOutputStream
* @author Him188moe
*/
@PacketId("08 36 31 03")
-
@Tested
class ClientPasswordSubmissionPacket(
private val qq: Long,
private val password: String,
private val loginTime: Int,
private val loginIP: String,
- private val tgtgtKey: ByteArray,
- private val token0825: ByteArray
+ private val tgtgtKey: ByteArray,//16 random by client
+ private val token0825: ByteArray//56 from server
) : ClientPacket() {
override fun encode() {
this.writeQQ(qq)
- this.writeHex(Protocol.passwordSubmissionKey1)
- this.writeHex(Protocol.publicKey)
+ this.writeHex(Protocol.passwordSubmissionTLV1)
+
+ this.writeShort(25)
+ this.writeHex(Protocol.publicKey)//25
+
this.writeHex("00 00 00 10")
this.writeHex(Protocol.key0836)
@@ -67,25 +68,25 @@ open class ClientLoginResendPacket internal constructor(
) : ClientPacket() {
override fun encode() {
this.writeQQ(qq)
- this.writeHex(Protocol.passwordSubmissionKey1)
- this.writeHex(Protocol.publicKey)
- this.writeHex("00 00 00 10")
- this.writeHex(Protocol.key0836)
+ this.writeHex(Protocol.passwordSubmissionTLV1)
- this.write(TEA.encrypt(object : ByteArrayDataOutputStream() {
- override fun toByteArray(): ByteArray {
- this.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825, tlv0006)
+ this.writeShort(25)
+ this.writeHex(Protocol.publicKey)//25
- this.writeHex("01 10") //tag
- this.writeHex("00 3C")//length
- this.writeHex("00 01")//tag
- this.writeHex("00 38")//length
- this.write(token00BA)//value
+ this.writeHex("00 00 00 10")//=16
+ this.writeHex(Protocol.key0836)//16
- this.writePart2()
- return super.toByteArray()
- }
- }.toByteArray(), Protocol.shareKey.hexToBytes()))
+ this.encryptAndWrite(Protocol.shareKey.hexToBytes()) {
+ it.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825, tlv0006)
+
+ it.writeHex("01 10") //tag
+ it.writeHex("00 3C")//length
+ it.writeHex("00 01")//tag
+ it.writeHex("00 38")//length
+ it.write(token00BA)//value
+
+ it.writePart2()
+ }
}
}
@@ -113,10 +114,10 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I
this.writeTLV0006(qq, password, loginTime, loginIP, tgtgtKey)
}
//fix
- this.writeHex(Protocol.passwordSubmissionKey2)
+ this.writeHex(Protocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag
this.writeHex("00 40")//length
- this.write(TEA.encrypt(Protocol.passwordSubmissionKey2.hexToBytes(), tgtgtKey))
+ this.write(TEA.encrypt(Protocol.passwordSubmissionTLV2.hexToBytes(), tgtgtKey))
this.writeHex(Protocol.constantData1)
this.writeHex(Protocol.constantData2)
this.writeQQ(qq)
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java
index be23a8fcd..1db1ef455 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/ImageNetworkUtils.java
@@ -9,18 +9,18 @@ import java.net.URL;
* @author NaturalHG
*/
public class ImageNetworkUtils {
- public static boolean postImage(String uKeyHex, int fileSize, long qqNumber, long groupCode, byte[] img) throws IOException {
+ public static boolean postImage(String uKeyHex, int fileSize, long botNumber, 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
- String builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" + "&ukey=" +
- uKeyHex.replace(" ", "") +
+ String builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" +
+ "&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize +
"&range=" + "0" +
- "&uin=" + qqNumber +
+ "&uin=" + botNumber +
"&groupcode=" + groupCode;
HttpURLConnection conn = (HttpURLConnection) new URL(builder).openConnection();
- conn.setRequestProperty("User-agent", "QQClient");
- conn.setRequestProperty("Content-length", "" + fileSize);
+ conn.setRequestProperty("User-Agent", "QQClient");
+ conn.setRequestProperty("Content-Length", "" + fileSize);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.getOutputStream().write(img);
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt
index 852e96cb0..7f2487298 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/TEA.kt
@@ -233,8 +233,8 @@ object TEA {
try {
return decrypt(data, 0, data.size)!!
} catch (e: Exception) {
- println("Source: " + data.toUHexString(" "))
- println("Key: " + key.toUHexString(" "))
+ //println("Source: " + data.toUHexString(" "))
+ // println("Key: " + key.toUHexString(" "))
throw e
}
}
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt b/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt
index 788ec66a4..07402be7c 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt
+++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/Utils.kt
@@ -3,15 +3,20 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.Protocol
+import net.mamoe.mirai.network.packet.dataInputStream
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
+import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.lang.reflect.Field
import java.util.*
import java.util.zip.CRC32
+import java.util.zip.GZIPInputStream
+import java.util.zip.GZIPOutputStream
import javax.imageio.ImageIO
+
/**
* @author Him188moe
* @author NaturalHG
@@ -76,6 +81,12 @@ open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream())
@JvmSynthetic
fun lazyEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().also(t).toByteArray()
+@JvmSynthetic
+fun lazyDecode(byteArray: ByteArray, t: (DataInputStream) -> T): T = byteArray.dataInputStream().let(t)
+
+fun DataInputStream.skip(n: Number) {
+ this.skip(n.toLong())
+}
fun getRandomByteArray(length: Int): ByteArray {
val bytes = LinkedList()
@@ -144,4 +155,15 @@ fun BufferedImage.toByteArray(formatName: String = "PNG"): ByteArray {
return lazyEncode {
ImageIO.write(this, formatName, it)
}
+}
+
+object GZip {
+ fun uncompress(bytes: ByteArray): ByteArray = lazyEncode {
+ GZIPInputStream(bytes.inputStream()).transferTo(it)
+ }
+
+ fun compress(bytes: ByteArray): ByteArray = ByteArrayOutputStream().let {
+ GZIPOutputStream(it).write(bytes)
+ return it.toByteArray()
+ }
}
\ No newline at end of file
diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/setting/MiraiSettings.java b/mirai-core/src/main/java/net/mamoe/mirai/utils/setting/MiraiSettings.java
index 3d15139d8..30b8fbe26 100644
--- a/mirai-core/src/main/java/net/mamoe/mirai/utils/setting/MiraiSettings.java
+++ b/mirai-core/src/main/java/net/mamoe/mirai/utils/setting/MiraiSettings.java
@@ -3,12 +3,14 @@ package net.mamoe.mirai.utils.setting;
import net.mamoe.mirai.plugin.MiraiPluginBase;
import org.ini4j.Config;
import org.ini4j.Ini;
+import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -31,16 +33,15 @@ public class MiraiSettings {
this(new File(filename));
}
- public MiraiSettings(File file) {
- if(!file.getName().contains(".")){
+ public MiraiSettings(@NotNull File file) {
+ Objects.requireNonNull(file);
+ if (!file.getName().contains(".")) {
file = new File(file.getPath() + ".ini");
}
this.file = file;
try {
- if (!file.exists()) {
- if (!file.createNewFile()) {
- throw new RuntimeException("cannot create config file " + file);
- }
+ if (!file.exists() && !file.createNewFile()) {
+ throw new RuntimeException("cannot create config file " + file);
}
Config config = new Config();
config.setMultiSection(true);
@@ -58,9 +59,9 @@ public class MiraiSettings {
public synchronized MiraiSettingMapSection getMapSection(String key) {
- if(!cacheSection.containsKey(key)) {
+ if (!cacheSection.containsKey(key)) {
MiraiSettingMapSection section = new MiraiSettingMapSection();
- if(ini.containsKey(key)){
+ if (ini.containsKey(key)) {
section.putAll(ini.get(key));
}
cacheSection.put(key, section);
@@ -69,9 +70,9 @@ public class MiraiSettings {
}
public synchronized MiraiSettingListSection getListSection(String key) {
- if(!cacheSection.containsKey(key)) {
+ if (!cacheSection.containsKey(key)) {
MiraiSettingListSection section = new MiraiSettingListSection();
- if(ini.containsKey(key)){
+ if (ini.containsKey(key)) {
section.addAll(ini.get(key).values());
}
cacheSection.put(key, section);
@@ -80,10 +81,10 @@ public class MiraiSettings {
}
- public synchronized void save(){
- cacheSection.forEach((k,a) -> {
- if(!ini.containsKey(k)) {
- ini.put(k,"",new HashMap<>());
+ public synchronized void save() {
+ cacheSection.forEach((k, a) -> {
+ if (!ini.containsKey(k)) {
+ ini.put(k, "", new HashMap<>());
}
a.saveAsSection(ini.get(k));
});
diff --git a/mirai-core/src/test/java/HexComparator.java b/mirai-core/src/test/java/HexComparator.java
index 66d73c2b5..674b274ff 100644
--- a/mirai-core/src/test/java/HexComparator.java
+++ b/mirai-core/src/test/java/HexComparator.java
@@ -58,6 +58,9 @@ public class HexComparator {
private static final String _1040400290_ = "3E 03 3F A2";
private static final String _1994701021_ = "76 E4 B8 DD";
private static final String _jiahua_ = "B1 89 BE 09";
+ private static final String _Him188moe_ = UtilsKt.toUHexString("Him188moe".getBytes(), " ");
+ private static final String 发图片 = UtilsKt.toUHexString("发图片".getBytes(), " ");
+ private static final String 群 = UtilsKt.toUHexString("发图片".getBytes(), " ");
private static final String SINGLE_PLAIN_MESSAGE_HEAD = "00 00 01 00 09 01";
diff --git a/mirai-core/src/test/java/PacketDebuger.kt b/mirai-core/src/test/java/PacketDebuger.kt
new file mode 100644
index 000000000..3caef2250
--- /dev/null
+++ b/mirai-core/src/test/java/PacketDebuger.kt
@@ -0,0 +1,206 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE")
+
+import jpcap.JpcapCaptor
+import jpcap.packet.IPPacket
+import jpcap.packet.UDPPacket
+import net.mamoe.mirai.network.Protocol
+import net.mamoe.mirai.network.packet.*
+import net.mamoe.mirai.network.packet.login.ServerLoginResponseFailedPacket
+import net.mamoe.mirai.network.packet.login.ServerLoginResponseKeyExchangePacket
+import net.mamoe.mirai.network.packet.login.ServerLoginResponseSuccessPacket
+import net.mamoe.mirai.network.packet.login.ServerLoginResponseVerificationCodeInitPacket
+import net.mamoe.mirai.utils.*
+import java.io.DataInputStream
+
+/**
+ * 模拟登录并抓取到 session key
+ *
+ * @author Him188moe
+ */
+object Main {
+ val localIp = "192.168.3.10"
+
+ @JvmStatic
+ fun main(args: Array) {
+ /*-------------- 第一步绑定网络设备 --------------*/
+ val devices = JpcapCaptor.getDeviceList()
+
+ /*
+ \Device\NPF_{0E7103E4-BF96-4B66-A23B-F6F630D814CD} | Microsoft
+ \Device\NPF_{2CCA31E2-93D5-42F2-92C1-5882E18A8E95} | VMware Virtual Ethernet Adapter
+ \Device\NPF_{A12C8971-858B-4BC8-816C-4077E1636AC5} | VMware Virtual Ethernet Adapter
+ \Device\NPF_{231C4E27-AF20-4362-BCA3-107236CB8A2E} | MS NDIS 6.0 LoopBack Driver
+ \Device\NPF_{500B5537-AA10-4E2F-8F7D-E6BD365BDCD1} | Microsoft
+ \Device\NPF_{A177317B-903A-45B5-8AEA-3698E423ABD6} | Microsoft
+ */
+ /*
+ for (n in devices) {
+ println(n.name + " | " + n.description)
+ }
+ println("-------------------------------------------")
+ exitProcess(0)*/
+
+ var jpcap: JpcapCaptor? = null
+ val caplen = 4096
+ val promiscCheck = true
+
+ jpcap = JpcapCaptor.openDevice(devices[1], caplen, promiscCheck, 50)
+
+
+ /*----------第二步抓包-----------------*/
+ while (true) {
+ assert(jpcap != null)
+ val pk = jpcap!!.packet
+ if (pk is IPPacket && pk.version.toInt() == 4) {
+
+ if (pk is UDPPacket) {
+ if (pk.dst_port != 8000 && pk.src_port != 8000) {
+ continue
+ }
+
+ if (localIp == pk.dst_ip.hostAddress) {//接受
+ dataReceived(pk.data)
+ } else {
+ try {
+ dataSent(pk.data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ //pk.dst_ip
+ }
+ }
+ }
+
+ fun dataReceived(data: ByteArray) {
+ if (!debugStarted) {
+ return
+ }
+
+ packetReceived(ServerPacket.ofByteArray(data))
+ }
+
+ fun packetReceived(packet: ServerPacket) {
+ when (packet) {
+ is ServerTouchResponsePacket.Encrypted -> packetReceived(packet.decrypt())
+ is ServerTouchResponsePacket -> {
+ if (packet.serverIP == null) {
+ loginTime = packet.loginTime
+ loginIp = packet.loginIP
+ token0825 = packet.token0825
+ }
+
+ //then send 08 36 31 03
+ }
+
+ is ServerLoginResponseFailedPacket -> {
+ println("login failed")
+ }
+
+ is ServerLoginResponseKeyExchangePacket.Encrypted -> packetReceived(packet.decrypt(tgtgtKey))
+ is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> packetReceived(packet.decrypt())
+ is ServerLoginResponseSuccessPacket.Encrypted -> packetReceived(packet.decrypt(tgtgtKey))
+
+ is ServerLoginResponseKeyExchangePacket -> {
+ tgtgtKey = packet.tgtgtKey
+ //then 31 04 or 31 06
+ }
+
+ is ServerLoginResponseSuccessPacket -> {
+ sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
+ }
+
+ is ServerSessionKeyResponsePacket.Encrypted -> packetReceived(packet.decrypt(sessionResponseDecryptionKey))
+
+ is ServerSessionKeyResponsePacket -> {
+ sessionKey = packet.sessionKey
+ println("Got sessionKey=" + sessionKey.toUHexString())
+ }
+
+ else -> {
+ }
+ }
+ }
+
+ @Volatile
+ private var debugStarted = false
+
+ private val qq: Int = 1040400290
+ private val password: String = "asdHim188moe"
+
+ lateinit var token0825: ByteArray//56
+ var loginTime: Int = 0
+ lateinit var loginIp: String
+ lateinit var tgtgtKey: ByteArray//16
+ lateinit var sessionKey: ByteArray
+
+ lateinit var sessionResponseDecryptionKey: ByteArray
+
+ fun dataSent(data: ByteArray) {
+ //println("Sent: " + data.toUByteArray().toUHexString())
+
+ lazyDecode(data.cutTail(1)) {
+ it.skip(3)
+ val idHex = it.readNBytes(4).toUHexString()
+ println("qq=" + it.readUInt())
+ println(idHex)
+ when (idHex.substring(0, 5)) {
+ "08 25" -> {
+ debugStarted = true
+ println("Detected touch, debug start!!")
+ }
+
+ "08 36" -> {
+ println("tim的 passwordSubmissionKey1 = " + it.readNBytes(Protocol.passwordSubmissionTLV1.hexToBytes().size).toUHexString())
+ //it.skipHex(Protocol.passwordSubmissionKey1)
+ println(it.readNBytes(2).toUHexString())
+ println("tim的 publicKey = " + it.readNBytes(Protocol.publicKey.hexToBytes().size).toUHexString())
+ println(it.readNBytes(2).toUHexString())
+ println("tim的 key0836=" + it.readLVByteArray().toUHexString())
+ //it.skipHex(Protocol.key0836)
+ val encrypted = it.readAllBytes()
+ println(encrypted.size)
+ println(encrypted.toUHexString())
+ val tlv0006data = lazyDecode(encrypted.decryptBy(Protocol.shareKey)) { section ->
+ section.skip(2 + 2 + 56 + 2)
+ section.skip(section.readShort())//device name
+ section.skip(6 + 4 + 2 + 2)
+
+ //tlv0006, encrypted by pwd md5
+ section.readNBytes(160).decryptBy(lazyEncode { md5(md5(password) + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray()) })
+ }
+ lazyDecode(tlv0006data) { tlv0006 ->
+ tlv0006.skip(4 + 2 + 4)
+ tlv0006.skipHex(Protocol.constantData2)
+ tlv0006.skip(3)
+ tlv0006.skip(16 + 4 + 1 + 4 * 3 + 4 + 8 + 2)
+ tlv0006.skipHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")
+ tgtgtKey = tlv0006.readNBytes(16)
+ }
+ println("Got tgtgtKey=" + tgtgtKey.toUHexString())
+
+ //then receive
+ }
+ else -> {
+ }
+ }
+ }
+ }
+
+ private fun ByteArray.decryptBy(key: ByteArray): ByteArray = TEA.decrypt(this, key)
+
+ private fun ByteArray.decryptBy(key: String): ByteArray = TEA.decrypt(this, key)
+
+
+ private fun DataInputStream.skipHex(uHex: String) {
+ this.skip(uHex.hexToBytes().size.toLong())
+ }
+}
+
+fun main() {
+ val data = "FE A1 06 1C 5F 04 04 F6 E2 F9 B2 A7 48 51 A2 81 4D 92 62 7E 67 29 F8 02 A5 77 97 A5 8F F9 81 1D B3 D9 3D DB 63 5C 22 2F E1 C2 53 67 E8 CD A5 BD AB 5A FB B3 14 48 6C 0D DD 67 EE AC EB A8 08 96 28 A0 20 9F D9 52 B7 DC E5 71 18 68 58 4F E3 31 7E 74 15 A2 3E 4D 11 CA D1 7A 59 D1 EA 8C A0 18 54 E7 4D ED CC D6 4C E3 34 43 3F 20 41 93 94 9D 11 F4 51 8E 5A 3A EA A4 5B EA 69 64 AE 4F DA 16 50 89 93 82 EA B3 DB 68 80 A5 10 78 94 16 7C BC 74 C0 D0 03 C7 BA 33 BD A5 BF 3A 90 B4 FB 66 7E 54 C7 3F A4 42 BC 72 60 A9 4F F0 7A 64 E5 BB AD 59 8B E7 48 0D 0E 5A 58 99 17 77 35 52 C9 28 67 77 81 6B 7F 6F D5 CF 12 DC 31 82 39 E9 F9 6D 91 A6 C7 60 A0 3C 7C 80 29 E9 2E 05 63 BC 59 B0 73 D8 0F 84 E9 D1 88 AC 99 B8 E4 DA 8F 8F E6 F5 06 29 E8 CD 8A A8 38 24 BD 4E BF E6 79 79 9B 91 9E 16 44 FD 87 3B 6E 69 14 AF 32 A0 6E AD AF 5A C8 45 64 F3 4C 3B 20 AA 20 16 A7 FA FF D1 F2 A8 78 5F DE D5 FF 37 76 73 73 52 73 91 32 0D 1C 35 4E 8A 21 29 C2 D7 87 55 B3 6D 65 F6 ED 6D 9E 6A 9E DC 46 6A F9 CC 38 09 72 7A B8 84 D1 4C 76 8B CB 2E AA 05 2A B3 31 0C F3 70 2B 34 70 7F BC 5D 8E 65 4E 91 16 77 CB 7A 07 CE 37 CF 42 D0 99 C6 14 5A 11 B1 7D 1C 7B 9B F4 31 FE 91 0C B0 FD 7B 9D 4B 9D D7 34 CC 1B F3 E0 ED 5B BC 71 D9 D5 D5 A8 83 A9 3E BF 2F A6 90 FB 51 9F 72 CC 0C A5 36 A6 05 55 0C 3F 93 6C 0F DF EA 43 E1 F3 51 10 02 5D 75 F0 83 C6 BD 06 21 6B 07 D6 6E 3A CB 20 21 60 89 3A 77 0E EB 86 F7 45 BE B8 54 5C 3A 45 3A 86 19 A9 75 E6 9C 50 3D 36 F1 51 1E B5 97 41 86 CF F0 6F 0C 0F 7E CF E4 E3 50 F2 6A 19 0A A2 CB 74 88 8C D6 62 EC EC 66 1F 87 D3 6F 1C 83 94 79 CE C9 15 66 07 12 AE A7 9A D9 D1 F2 90 F8 56 28 E7 6E 33 AF 8D 58 3D 8A 7C 49 94 A0 E8 8B 48 77 89 B6 78 13 44 5C A0 D9 A5".hexToBytes()
+ println(TEA.decrypt(data, "E4 23 72 92 79 9C 9C 96 28 9D AF 5C 1D 33 D2 7F".hexToBytes()))
+
+}
\ No newline at end of file