mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-22 01:22:22 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
db8315689e
@ -8,7 +8,7 @@
|
||||
项目处于开发阶段,学生无法每日大量更新。
|
||||
项目还有很多未完善的地方, 欢迎任何的代码贡献, 或是 issue.
|
||||
部分协议来自网络上开源项目
|
||||
一切开发旨在学习,请勿用于非法用途
|
||||
**一切开发旨在学习,请勿用于非法用途**
|
||||
|
||||
## 抢先体验
|
||||
核心框架结构已经开发完毕,一些核心功能也测试完成。
|
||||
|
@ -17,6 +17,8 @@ kotlin {
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
apply plugin: 'java'
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.kotlinJvm
|
||||
implementation rootProject.ext.reflect
|
||||
@ -28,6 +30,7 @@ kotlin {
|
||||
implementation 'org.jsoup:jsoup:1.12.1'
|
||||
implementation 'org.ini4j:ini4j:0.5.2'
|
||||
|
||||
implementation project(":mirai-protocol-timpc")
|
||||
}
|
||||
}
|
||||
jvmTest {
|
||||
|
@ -1,174 +0,0 @@
|
||||
package net.mamoe.mirai.message;
|
||||
|
||||
/**
|
||||
* @author LamGC
|
||||
* @author Him188moe
|
||||
*/
|
||||
public enum FaceID {
|
||||
unknown(0xff),
|
||||
|
||||
Face_jingya(0),
|
||||
Face_piezui(1),
|
||||
Face_se(2),
|
||||
Face_fadai(3),
|
||||
Face_deyi(4),
|
||||
Face_liulei(5),
|
||||
Face_haixiu(6),
|
||||
Face_bizui(7),
|
||||
Face_shui(8),
|
||||
Face_daku(9),
|
||||
Face_ganga(10),
|
||||
Face_fanu(11),
|
||||
Face_tiaopi(12),
|
||||
Face_ciya(13),
|
||||
Face_weixiao(14),
|
||||
Face_nanguo(15),
|
||||
Face_ku(16),
|
||||
Face_zhuakuang(18),
|
||||
Face_tu(19),
|
||||
Face_touxiao(20),
|
||||
Face_keai(21),
|
||||
Face_baiyan(22),
|
||||
Face_aoman(23),
|
||||
Face_ji_e(24),
|
||||
Face_kun(25),
|
||||
Face_jingkong(26),
|
||||
Face_liuhan(27),
|
||||
Face_hanxiao(28),
|
||||
Face_dabing(29),
|
||||
Face_fendou(30),
|
||||
Face_zhouma(31),
|
||||
Face_yiwen(32),
|
||||
Face_yun(34),
|
||||
Face_zhemo(35),
|
||||
Face_shuai(36),
|
||||
Face_kulou(37),
|
||||
Face_qiaoda(38),
|
||||
Face_zaijian(39),
|
||||
Face_fadou(41),
|
||||
Face_aiqing(42),
|
||||
Face_tiaotiao(43),
|
||||
Face_zhutou(46),
|
||||
Face_yongbao(49),
|
||||
Face_dan_gao(53),
|
||||
Face_shandian(54),
|
||||
Face_zhadan(55),
|
||||
Face_dao(56),
|
||||
Face_zuqiu(57),
|
||||
Face_bianbian(59),
|
||||
Face_kafei(60),
|
||||
Face_fan(61),
|
||||
Face_meigui(63),
|
||||
Face_diaoxie(64),
|
||||
Face_aixin(66),
|
||||
Face_xinsui(67),
|
||||
Face_liwu(69),
|
||||
Face_taiyang(74),
|
||||
Face_yueliang(75),
|
||||
Face_qiang(76),
|
||||
Face_ruo(77),
|
||||
Face_woshou(78),
|
||||
Face_shengli(79),
|
||||
Face_feiwen(85),
|
||||
Face_naohuo(86),
|
||||
Face_xigua(89),
|
||||
Face_lenghan(96),
|
||||
Face_cahan(97),
|
||||
Face_koubi(98),
|
||||
Face_guzhang(99),
|
||||
Face_qiudale(100),
|
||||
Face_huaixiao(101),
|
||||
Face_zuohengheng(102),
|
||||
Face_youhengheng(103),
|
||||
Face_haqian(104),
|
||||
Face_bishi(105),
|
||||
Face_weiqu(106),
|
||||
Face_kuaikule(107),
|
||||
Face_yinxian(108),
|
||||
Face_qinqin(109),
|
||||
Face_xia(110),
|
||||
Face_kelian(111),
|
||||
Face_caidao(112),
|
||||
Face_pijiu(113),
|
||||
Face_lanqiu(114),
|
||||
Face_pingpang(115),
|
||||
Face_shiai(116),
|
||||
Face_piaochong(117),
|
||||
Face_baoquan(118),
|
||||
Face_gouyin(119),
|
||||
Face_quantou(120),
|
||||
Face_chajin(121),
|
||||
Face_aini(122),
|
||||
Face_bu(123),
|
||||
Face_hao(124),
|
||||
Face_zhuanquan(125),
|
||||
Face_ketou(126),
|
||||
Face_huitou(127),
|
||||
Face_tiaosheng(128),
|
||||
Face_huishou(129),
|
||||
Face_jidong(130),
|
||||
Face_jiewu(131),
|
||||
Face_xianwen(132),
|
||||
Face_zuotaiji(133),
|
||||
Face_youtaiji(134),
|
||||
Face_shuangxi(136),
|
||||
Face_bianpao(137),
|
||||
Face_denglong(138),
|
||||
Face_facai(139),
|
||||
Face_K_ge(140),
|
||||
Face_gouwu(141),
|
||||
Face_youjian(142),
|
||||
Face_shuai_qi(143),
|
||||
Face_hecai(144),
|
||||
Face_qidao(145),
|
||||
Face_baojin(146),
|
||||
Face_bangbangtang(147),
|
||||
Face_he_nai(148),
|
||||
Face_xiamian(149),
|
||||
Face_xiangjiao(150),
|
||||
Face_feiji(151),
|
||||
Face_kaiche(152),
|
||||
Face_gaotiezuochetou(153),
|
||||
Face_chexiang(154),
|
||||
Face_gaotieyouchetou(155),
|
||||
Face_duoyun(156),
|
||||
Face_xiayu(157),
|
||||
Face_chaopiao(158),
|
||||
Face_xiongmao(159),
|
||||
Face_dengpao(160),
|
||||
Face_fengche(161),
|
||||
Face_naozhong(162),
|
||||
Face_dasan(163),
|
||||
Face_caiqiu(164),
|
||||
Face_zuanjie(165),
|
||||
Face_shafa(166),
|
||||
Face_zhijin(167),
|
||||
Face_yao(168),
|
||||
Face_shouqiang(169),
|
||||
Face_qingwa(170),
|
||||
|
||||
// TODO: 2019/9/1 添加更多表情
|
||||
|
||||
;
|
||||
|
||||
private final int id;
|
||||
|
||||
FaceID(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static FaceID ofId(int id) {
|
||||
for (FaceID value : FaceID.values()) {
|
||||
if (value.id == id) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return FaceID.unknown;
|
||||
}
|
||||
|
||||
|
||||
}
|
167
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/FaceID.kt
Normal file
167
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/FaceID.kt
Normal file
@ -0,0 +1,167 @@
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
/**
|
||||
* @author LamGC
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
|
||||
enum class FaceID constructor(val id: Int) {
|
||||
unknown(0xff),
|
||||
// TODO: 2019/9/1 添加更多表情
|
||||
jingya(0),
|
||||
piezui(1),
|
||||
se(2),
|
||||
fadai(3),
|
||||
deyi(4),
|
||||
liulei(5),
|
||||
haixiu(6),
|
||||
bizui(7),
|
||||
shui(8),
|
||||
daku(9),
|
||||
ganga(10),
|
||||
fanu(11),
|
||||
tiaopi(12),
|
||||
ciya(13),
|
||||
weixiao(14),
|
||||
nanguo(15),
|
||||
ku(16),
|
||||
zhuakuang(18),
|
||||
tu(19),
|
||||
touxiao(20),
|
||||
keai(21),
|
||||
baiyan(22),
|
||||
aoman(23),
|
||||
ji_e(24),
|
||||
kun(25),
|
||||
jingkong(26),
|
||||
liuhan(27),
|
||||
hanxiao(28),
|
||||
dabing(29),
|
||||
fendou(30),
|
||||
zhouma(31),
|
||||
yiwen(32),
|
||||
yun(34),
|
||||
zhemo(35),
|
||||
shuai(36),
|
||||
kulou(37),
|
||||
qiaoda(38),
|
||||
zaijian(39),
|
||||
fadou(41),
|
||||
aiqing(42),
|
||||
tiaotiao(43),
|
||||
zhutou(46),
|
||||
yongbao(49),
|
||||
dan_gao(53),
|
||||
shandian(54),
|
||||
zhadan(55),
|
||||
dao(56),
|
||||
zuqiu(57),
|
||||
bianbian(59),
|
||||
kafei(60),
|
||||
fan(61),
|
||||
meigui(63),
|
||||
diaoxie(64),
|
||||
aixin(66),
|
||||
xinsui(67),
|
||||
liwu(69),
|
||||
taiyang(74),
|
||||
yueliang(75),
|
||||
qiang(76),
|
||||
ruo(77),
|
||||
woshou(78),
|
||||
shengli(79),
|
||||
feiwen(85),
|
||||
naohuo(86),
|
||||
xigua(89),
|
||||
lenghan(96),
|
||||
cahan(97),
|
||||
koubi(98),
|
||||
guzhang(99),
|
||||
qiudale(100),
|
||||
huaixiao(101),
|
||||
zuohengheng(102),
|
||||
youhengheng(103),
|
||||
haqian(104),
|
||||
bishi(105),
|
||||
weiqu(106),
|
||||
kuaikule(107),
|
||||
yinxian(108),
|
||||
qinqin(109),
|
||||
xia(110),
|
||||
kelian(111),
|
||||
caidao(112),
|
||||
pijiu(113),
|
||||
lanqiu(114),
|
||||
pingpang(115),
|
||||
shiai(116),
|
||||
piaochong(117),
|
||||
baoquan(118),
|
||||
gouyin(119),
|
||||
quantou(120),
|
||||
chajin(121),
|
||||
aini(122),
|
||||
bu(123),
|
||||
hao(124),
|
||||
zhuanquan(125),
|
||||
ketou(126),
|
||||
huitou(127),
|
||||
tiaosheng(128),
|
||||
huishou(129),
|
||||
jidong(130),
|
||||
jiewu(131),
|
||||
xianwen(132),
|
||||
zuotaiji(133),
|
||||
youtaiji(134),
|
||||
shuangxi(136),
|
||||
bianpao(137),
|
||||
denglong(138),
|
||||
facai(139),
|
||||
K_ge(140),
|
||||
gouwu(141),
|
||||
youjian(142),
|
||||
shuai_qi(143),
|
||||
hecai(144),
|
||||
qidao(145),
|
||||
baojin(146),
|
||||
bangbangtang(147),
|
||||
he_nai(148),
|
||||
xiamian(149),
|
||||
xiangjiao(150),
|
||||
feiji(151),
|
||||
kaiche(152),
|
||||
gaotiezuochetou(153),
|
||||
chexiang(154),
|
||||
gaotieyouchetou(155),
|
||||
duoyun(156),
|
||||
xiayu(157),
|
||||
chaopiao(158),
|
||||
xiongmao(159),
|
||||
dengpao(160),
|
||||
fengche(161),
|
||||
naozhong(162),
|
||||
dasan(163),
|
||||
caiqiu(164),
|
||||
zuanjie(165),
|
||||
shafa(166),
|
||||
zhijin(167),
|
||||
yao(168),
|
||||
shouqiang(169),
|
||||
qingwa(170);
|
||||
|
||||
override fun toString(): String {
|
||||
return "$name($id)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun ofId(id: Int): FaceID {
|
||||
for (value in values()) {
|
||||
if (value.id == id) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -20,7 +20,11 @@ class Face(val id: FaceID) : Message() {
|
||||
override val type: MessageKey = Key
|
||||
|
||||
override fun toStringImpl(): String {
|
||||
return String.format("[face%d]", id.id)
|
||||
return "[face${id.id}]"
|
||||
}
|
||||
|
||||
override fun toObjectString(): String {
|
||||
return "Face[$id]"
|
||||
}
|
||||
|
||||
override fun toByteArray(): ByteArray = dataEncode { section ->
|
||||
@ -48,7 +52,7 @@ class Face(val id: FaceID) : Message() {
|
||||
|
||||
override operator fun contains(sub: String): Boolean = false
|
||||
|
||||
internal object PacketHelper {
|
||||
object PacketHelper {
|
||||
fun ofByteArray(data: ByteArray): Face = dataDecode(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
|
||||
|
@ -22,7 +22,11 @@ open class Image(val imageId: String) : Message() {
|
||||
override val type: MessageKey = Key
|
||||
|
||||
override fun toStringImpl(): String {
|
||||
return imageId
|
||||
return "[$imageId]"
|
||||
}
|
||||
|
||||
override fun toObjectString(): String {
|
||||
return "Image[$imageId]"
|
||||
}
|
||||
|
||||
override fun toByteArray(): ByteArray = dataEncode { section ->
|
||||
@ -55,7 +59,7 @@ open class Image(val imageId: String) : Message() {
|
||||
|
||||
override operator fun contains(sub: String): Boolean = false //No string can be contained in a image
|
||||
|
||||
internal object PacketHelper {
|
||||
object PacketHelper {
|
||||
@JvmStatic
|
||||
fun ofByteArray0x06(data: ByteArray): Image = dataDecode(data) {
|
||||
it.skip(1)
|
||||
|
@ -2,7 +2,12 @@ package net.mamoe.mirai.message.defaults
|
||||
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.MessageKey
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.readLVByteArray
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.readNBytes
|
||||
import net.mamoe.mirai.utils.dataDecode
|
||||
import net.mamoe.mirai.utils.dataEncode
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.io.DataInputStream
|
||||
import java.util.*
|
||||
import java.util.stream.Collectors
|
||||
import java.util.stream.Stream
|
||||
@ -95,4 +100,69 @@ class MessageChain : Message {
|
||||
operator fun component1(): Message = this.list[0]
|
||||
operator fun component2(): Message = this.list[1]
|
||||
operator fun component3(): Message = this.list[2]
|
||||
|
||||
object PacketHelper {
|
||||
@JvmStatic
|
||||
fun ofByteArray(byteArray: ByteArray): MessageChain = dataDecode(byteArray) {
|
||||
it.readMessageChain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DataInputStream.readMessage(): Message? {
|
||||
val messageType = this.readByte().toInt()
|
||||
val sectionLength = this.readShort().toLong()//sectionLength: short
|
||||
val sectionData = this.readNBytes(sectionLength)
|
||||
return when (messageType) {
|
||||
0x01 -> PlainText.PacketHelper.ofByteArray(sectionData)
|
||||
0x02 -> Face.PacketHelper.ofByteArray(sectionData)
|
||||
0x03 -> Image.PacketHelper.ofByteArray0x03(sectionData)
|
||||
0x06 -> Image.PacketHelper.ofByteArray0x06(sectionData)
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun DataInputStream.readMessageChain(): MessageChain {
|
||||
val chain = MessageChain()
|
||||
var got: Message? = null
|
||||
do {
|
||||
if (got != null) {
|
||||
chain.concat(got)
|
||||
}
|
||||
if (this.available() == 0) {
|
||||
return chain
|
||||
}
|
||||
got = this.readMessage()
|
||||
} while (got != null)
|
||||
return chain
|
||||
}
|
@ -38,7 +38,7 @@ class PlainText(private val text: String) : Message() {
|
||||
|
||||
override operator fun contains(sub: String): Boolean = this.toString().contains(sub)
|
||||
|
||||
internal object PacketHelper {
|
||||
object PacketHelper {
|
||||
@JvmStatic
|
||||
fun ofByteArray(data: ByteArray): PlainText = dataDecode(data) {
|
||||
it.skip(1)
|
||||
|
@ -39,6 +39,7 @@ object TIMProtocol {
|
||||
*/
|
||||
const val fixVer2 = "02 00 00 00 01 01 01 00 00 68 20"
|
||||
// 02 38 03 00 CD 48 68 3E 03 3F A2 02 00 00 00
|
||||
// 02 00 00 00 01 2E 01 00 00 69 35
|
||||
/**
|
||||
* 0825data1
|
||||
*/
|
||||
@ -105,6 +106,7 @@ object TIMProtocol {
|
||||
* length=15
|
||||
*/
|
||||
const val messageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
|
||||
// TIM最新 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91
|
||||
|
||||
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
|
||||
|
||||
|
@ -2,11 +2,8 @@
|
||||
|
||||
package net.mamoe.mirai.network.protocol.tim.packet
|
||||
|
||||
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.message.defaults.readMessageChain
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
|
||||
import net.mamoe.mirai.utils.dataDecode
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
@ -49,7 +46,7 @@ abstract class ServerEventPacket(input: DataInputStream, val packetId: ByteArray
|
||||
|
||||
@PacketId("00 17")
|
||||
class Encrypted(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
|
||||
fun decrypt(sessionKey: ByteArray): Raw = Raw(decryptBy(sessionKey), packetId).setId(this.idHex)
|
||||
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey), packetId).setId(this.idHex)
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +132,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
|
||||
this.input.goto(108)
|
||||
this.input.readLVByteArray()
|
||||
input.skip(2)//2个0x00
|
||||
message = input.readSections()
|
||||
message = input.readMessageChain()
|
||||
|
||||
val map = input.readTLVMap(true)
|
||||
if (map.containsKey(18)) {
|
||||
@ -262,7 +259,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
|
||||
input.goto(93 + l1)
|
||||
input.readLVByteArray()//font
|
||||
input.skip(2)//2个0x00
|
||||
message = input.readSections()
|
||||
message = input.readMessageChain()
|
||||
|
||||
val map: Map<Int, ByteArray> = input.readTLVMap(true).withDefault { byteArrayOf() }
|
||||
println(map.getValue(18))
|
||||
@ -278,64 +275,6 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
|
||||
}
|
||||
}
|
||||
|
||||
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.PacketHelper.ofByteArray(sectionData)
|
||||
0x02 -> Face.PacketHelper.ofByteArray(sectionData)
|
||||
0x03 -> Image.PacketHelper.ofByteArray0x03(sectionData)
|
||||
0x06 -> Image.PacketHelper.ofByteArray0x06(sectionData)
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
if (this.available() == 0) {
|
||||
return chain
|
||||
}
|
||||
got = this.readSection()
|
||||
} while (got != null)
|
||||
return chain
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
牛逼 (10404
|
||||
|
@ -34,11 +34,22 @@ class ClientSendFriendMessagePacket(
|
||||
writeRandom(2)
|
||||
writeTime()
|
||||
writeHex("00 00" +
|
||||
"00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00")
|
||||
//01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00
|
||||
"00 00 00 00")
|
||||
|
||||
//消息过多要分包发送
|
||||
//如果只有一个
|
||||
writeByte(0x01)
|
||||
writeByte(0)//第几个包
|
||||
writeByte(0)
|
||||
//如果大于一个,
|
||||
//writeByte(0x02)//数量
|
||||
//writeByte(0)//第几个包
|
||||
//writeByte(0x91)//why?
|
||||
|
||||
writeHex("00 01 4D 53 47 00 00 00 00 00")
|
||||
writeTime()
|
||||
writeRandom(4)
|
||||
writeHex("00 00 00 00 09 00 86")
|
||||
writeHex("00 00 00 00 09 00 86")//TIM最新 0C 00 86
|
||||
writeHex(TIMProtocol.messageConst1)//... 85 E9 BB 91
|
||||
writeZero(2)
|
||||
|
||||
@ -56,9 +67,5 @@ class ClientSendFriendMessagePacket(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun main() {
|
||||
|
||||
}
|
||||
@PacketId("00 CD")
|
||||
class ServerSendFriendMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
|
@ -37,18 +37,19 @@ class ClientTryGetImageIDPacket(
|
||||
|
||||
writeZero(2)
|
||||
|
||||
writeHex("5E")
|
||||
writeHex("5B")//原5E
|
||||
writeHex("08")
|
||||
writeHex("01 12 03 98 01 01 10 01")
|
||||
|
||||
writeHex("1A")
|
||||
writeHex("5A")
|
||||
writeHex("57")//原5A
|
||||
|
||||
writeHex("08")
|
||||
writeUVarInt(groupNumberOrQQNumber)
|
||||
writeUVarInt(groupNumberOrQQNumber)//FB D2 D8 94
|
||||
|
||||
writeByte(0x02)
|
||||
writeHex("10")
|
||||
writeUVarInt(botNumber)
|
||||
writeUVarInt(botNumber)//A2 FF 8C F0
|
||||
|
||||
writeHex("18 00")
|
||||
|
||||
@ -57,10 +58,13 @@ class ClientTryGetImageIDPacket(
|
||||
write(md5(byteArray))
|
||||
|
||||
writeHex("28")
|
||||
writeUVarInt(byteArray.size.toUInt())
|
||||
writeUVarInt(byteArray.size.toUInt())//E2 0D
|
||||
|
||||
writeHex("32")
|
||||
writeHex("1A")
|
||||
//28 00 5A 00 53 00 41 00 58 00 40 00 57 00 4B 00 52 00 4A 00 5A 00 31 00 7E 00 38 01 48 01 50 38 58 34 60 04 6A 05 32 36 39 33 33 70 00 78 03 80 01 00
|
||||
|
||||
|
||||
writeHex("37 00 4D 00 32 00 25 00 4C 00 31 00 56 00 32 00 7B 00 39 00 30 00 29 00 52 00")
|
||||
|
||||
writeHex("38 01")
|
||||
|
@ -84,6 +84,11 @@ fun <R> dataDecode(byteArray: ByteArray, t: (DataInputStream) -> R): R = byteArr
|
||||
|
||||
fun <R> ByteArray.decode(t: (DataInputStream) -> R): R = this.dataInputStream().let(t)
|
||||
|
||||
fun ByteArray.decryptBy(key: ByteArray): ByteArray = TEA.decrypt(this, key)
|
||||
|
||||
fun ByteArray.decryptBy(key: String): ByteArray = TEA.decrypt(this, key)
|
||||
|
||||
|
||||
fun DataInputStream.skip(n: Number) {
|
||||
this.skip(n.toLong())
|
||||
}
|
||||
|
@ -69,7 +69,6 @@ fun DataOutputStream.writeVarInt(signedInt: Int) {
|
||||
this.writeUVarInt(encodeZigZag32(signedInt))
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeUVarInt(uint: UInt) {
|
||||
return writeUVarInt(uint.toLong())
|
||||
|
@ -3,11 +3,12 @@ apply plugin: "java"
|
||||
|
||||
dependencies {
|
||||
implementation project(':mirai-core')
|
||||
compile 'com.google.protobuf:protobuf-java:3.5.0'
|
||||
compile files('./lib/jpcap.jar')
|
||||
|
||||
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2'
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib:1.3.50'
|
||||
compile rootProject.ext.coroutineCommon
|
||||
compile rootProject.ext.kotlinJvm
|
||||
|
||||
compile group: 'com.google.protobuf', name: 'protobuf-java', version: rootProject.ext.protobuf_version
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
@ -1,373 +0,0 @@
|
||||
import kotlin.ranges.IntRange;
|
||||
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMProtocol;
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacketKt;
|
||||
import net.mamoe.mirai.utils.UtilsKt;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* This could be used to check packet encoding..
|
||||
* but better to run under UNIX
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public class HexComparator {
|
||||
|
||||
/**
|
||||
* a string result
|
||||
*/
|
||||
|
||||
private static final String RED = "\033[31m";
|
||||
|
||||
private static final String GREEN = "\033[33m";
|
||||
|
||||
private static final String UNKNOWN = "\033[30m";
|
||||
|
||||
private static final String BLUE = "\033[34m";
|
||||
|
||||
public static final List<HexReader> consts = new LinkedList<>() {{
|
||||
add(new HexReader("90 5E 39 DF 00 02 76 E4 B8 DD 00"));
|
||||
}};
|
||||
|
||||
private static class ConstMatcher {
|
||||
private static final List<Field> CONST_FIELDS = new LinkedList<>() {{
|
||||
List.of(TIMProtocol.class).forEach(aClass -> Arrays.stream(aClass.getDeclaredFields()).peek(this::add).forEach(Field::trySetAccessible));
|
||||
List.of(TestConsts.class).forEach(aClass -> Arrays.stream(aClass.getDeclaredFields()).peek(this::add).forEach(Field::trySetAccessible));
|
||||
}};
|
||||
|
||||
@SuppressWarnings({"unused", "NonAsciiCharacters"})
|
||||
private static class TestConsts {
|
||||
private static final String NIU_BI = UtilsKt.toUHexString("牛逼".getBytes(), " ");
|
||||
private static final String _1994701021 = ClientPacketKt.toUHexString(1994701021, " ");
|
||||
private static final String _1040400290 = ClientPacketKt.toUHexString(1040400290, " ");
|
||||
private static final String _580266363 = ClientPacketKt.toUHexString(580266363, " ");
|
||||
|
||||
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";
|
||||
|
||||
private static final String MESSAGE_TAIL_10404 = "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00".replace(" ", " ");
|
||||
//private static final String MESSAGE_TAIL2_10404 ="".replace(" ", " ");
|
||||
|
||||
}
|
||||
|
||||
private final List<Match> matches = new LinkedList<>();
|
||||
|
||||
private ConstMatcher(String hex) {
|
||||
CONST_FIELDS.forEach(field -> {
|
||||
for (IntRange match : match(hex, field)) {
|
||||
matches.add(new Match(match, field.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getMatchedConstName(int hexNumber) {
|
||||
for (Match match : this.matches) {
|
||||
if (match.range.contains(hexNumber)) {
|
||||
return match.constName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<IntRange> match(String hex, Field field) {
|
||||
final String constValue;
|
||||
try {
|
||||
constValue = ((String) field.get(null)).trim();
|
||||
if (constValue.length() / 3 <= 3) {//Minimum numbers of const hex bytes
|
||||
return new LinkedList<>();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ClassCastException ignored) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
return new LinkedList<>() {{
|
||||
int index = -1;
|
||||
while ((index = hex.indexOf(constValue, index + 1)) != -1) {
|
||||
add(new IntRange(index / 3, (index + constValue.length()) / 3));
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
private static class Match {
|
||||
private IntRange range;
|
||||
private String constName;
|
||||
|
||||
Match(IntRange range,String constName){
|
||||
this.range = range;
|
||||
this.constName = constName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildConstNameChain(int length, ConstMatcher constMatcher, StringBuilder constNameBuilder) {
|
||||
//System.out.println(constMatcher.matches);
|
||||
for (int i = 0; i < length; i++) {
|
||||
constNameBuilder.append(" ");
|
||||
String match = constMatcher.getMatchedConstName(i / 4);
|
||||
if (match != null) {
|
||||
int appendedNameLength = match.length();
|
||||
constNameBuilder.append(match);
|
||||
while (match.equals(constMatcher.getMatchedConstName(i++ / 4))) {
|
||||
if (appendedNameLength-- < 0) {
|
||||
constNameBuilder.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
constNameBuilder.append(" ".repeat(match.length() % 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String compare(String hex1s, String hex2s) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String[] hex1 = hex1s.trim().replace("\n", "").split(" ");
|
||||
String[] hex2 = hex2s.trim().replace("\n", "").split(" ");
|
||||
ConstMatcher constMatcher1 = new ConstMatcher(hex1s);
|
||||
ConstMatcher constMatcher2 = new ConstMatcher(hex2s);
|
||||
|
||||
if (hex1.length == hex2.length) {
|
||||
builder.append(GREEN).append("长度一致:").append(hex1.length);
|
||||
} else {
|
||||
builder.append(RED).append("长度不一致").append(hex1.length).append("/").append(hex2.length);
|
||||
}
|
||||
|
||||
|
||||
StringBuilder numberLine = new StringBuilder();
|
||||
StringBuilder hex1ConstName = new StringBuilder();
|
||||
StringBuilder hex1b = new StringBuilder();
|
||||
StringBuilder hex2b = new StringBuilder();
|
||||
StringBuilder hex2ConstName = new StringBuilder();
|
||||
int dif = 0;
|
||||
|
||||
int length = Math.max(hex1.length, hex2.length) * 4;
|
||||
buildConstNameChain(length, constMatcher1, hex1ConstName);
|
||||
buildConstNameChain(length, constMatcher2, hex2ConstName);
|
||||
|
||||
|
||||
for (int i = 0; i < Math.max(hex1.length, hex2.length); ++i) {
|
||||
String h1 = null;
|
||||
String h2 = null;
|
||||
boolean isDif = false;
|
||||
if (hex1.length <= i) {
|
||||
h1 = RED + "__";
|
||||
isDif = true;
|
||||
} else {
|
||||
String matchedConstName = constMatcher1.getMatchedConstName(i);
|
||||
if (matchedConstName != null) {
|
||||
h1 = BLUE + hex1[i];
|
||||
}
|
||||
}
|
||||
if (hex2.length <= i) {
|
||||
h2 = RED + "__";
|
||||
isDif = true;
|
||||
} else {
|
||||
String matchedConstName = constMatcher2.getMatchedConstName(i);
|
||||
if (matchedConstName != null) {
|
||||
h2 = BLUE + hex2[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (h1 == null && h2 == null) {
|
||||
h1 = hex1[i];
|
||||
h2 = hex2[i];
|
||||
if (h1.equals(h2)) {
|
||||
h1 = GREEN + h1;
|
||||
h2 = GREEN + h2;
|
||||
} else {
|
||||
h1 = RED + h1;
|
||||
h2 = RED + h2;
|
||||
isDif = true;
|
||||
}
|
||||
} else {
|
||||
if (h1 == null) {
|
||||
h1 = RED + hex1[i];
|
||||
}
|
||||
if (h2 == null) {
|
||||
h2 = RED + hex2[i];
|
||||
}
|
||||
}
|
||||
|
||||
numberLine.append(UNKNOWN).append(getFixedNumber(i)).append(" ");
|
||||
hex1b.append(" ").append(h1).append(" ");
|
||||
hex2b.append(" ").append(h2).append(" ");
|
||||
if (isDif) {
|
||||
++dif;
|
||||
}
|
||||
|
||||
//doConstReplacement(hex1b);
|
||||
//doConstReplacement(hex2b);
|
||||
}
|
||||
|
||||
return (builder.append(" ").append(dif).append(" 个不同").append("\n")
|
||||
.append(numberLine).append("\n")
|
||||
.append(hex1ConstName).append("\n")
|
||||
.append(hex1b).append("\n")
|
||||
.append(hex2b).append("\n")
|
||||
.append(hex2ConstName).append("\n")
|
||||
)
|
||||
.toString();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void doConstReplacement(StringBuilder builder) {
|
||||
String mirror = builder.toString();
|
||||
HexReader hexs = new HexReader(mirror);
|
||||
for (AtomicInteger i = new AtomicInteger(0); i.get() < builder.length(); i.addAndGet(1)) {
|
||||
hexs.setTo(i.get());
|
||||
consts.forEach(a -> {
|
||||
hexs.setTo(i.get());
|
||||
List<Integer> posToPlaceColor = new LinkedList<>();
|
||||
AtomicBoolean is = new AtomicBoolean(false);
|
||||
|
||||
a.readFully((c, d) -> {
|
||||
if (c.equals(hexs.readHex())) {
|
||||
posToPlaceColor.add(d);
|
||||
} else {
|
||||
is.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (is.get()) {
|
||||
AtomicInteger adder = new AtomicInteger();
|
||||
posToPlaceColor.forEach(e -> {
|
||||
builder.insert(e + adder.getAndAdd(BLUE.length()), BLUE);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFixedNumber(int number) {
|
||||
if (number < 10) {
|
||||
return "00" + number;
|
||||
}
|
||||
if (number < 100) {
|
||||
return "0" + number;
|
||||
}
|
||||
return String.valueOf(number);
|
||||
}
|
||||
|
||||
private static String getClipboardString() {
|
||||
Transferable trans = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
|
||||
if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
try {
|
||||
return (String) trans.getTransferData(DataFlavor.stringFlavor);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
while (true) {
|
||||
System.out.println("Hex1: ");
|
||||
var hex1 = scanner.nextLine();
|
||||
System.out.println("Hex2: ");
|
||||
var hex2 = scanner.nextLine();
|
||||
System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
|
||||
System.out.println(HexComparator.compare(hex1, hex2));
|
||||
System.out.println();
|
||||
}
|
||||
/*
|
||||
System.out.println(HexComparator.compare(
|
||||
//mirai
|
||||
|
||||
"2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC Protocol.messageConst1 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n"
|
||||
,
|
||||
//e
|
||||
"2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC Protocol.messageConst1 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65"
|
||||
));
|
||||
|
||||
|
||||
/*
|
||||
System.out.println(HexComparator.compare(
|
||||
//e
|
||||
"90 5E 39 DF 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 7B 7B 7B 7B 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC",
|
||||
//mirai
|
||||
"6F 0B DF 92 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 E9 E9 E9 E9 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC\n\n\n"
|
||||
));*/
|
||||
}
|
||||
}
|
||||
|
||||
class HexReader {
|
||||
private String s;
|
||||
private int pos = 0;
|
||||
private int lastHaxPos = 0;
|
||||
|
||||
|
||||
public HexReader(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public String readHex() {
|
||||
boolean isStr = false;
|
||||
String next = "";
|
||||
for (; pos < s.length() - 2; ++pos) {
|
||||
|
||||
char s1 = ' ';
|
||||
if (pos != 0) {
|
||||
s1 = this.s.charAt(0);
|
||||
}
|
||||
char s2 = this.s.charAt(pos + 1);
|
||||
char s3 = this.s.charAt(pos + 2);
|
||||
char s4 = ' ';
|
||||
if (this.s.length() != (this.pos + 3)) {
|
||||
s4 = this.s.charAt(pos + 3);
|
||||
}
|
||||
if (
|
||||
Character.isSpaceChar(s1) && Character.isSpaceChar(s4)
|
||||
&&
|
||||
(Character.isDigit(s2) || Character.isAlphabetic(s2))
|
||||
&&
|
||||
(Character.isDigit(s3) || Character.isAlphabetic(s3))
|
||||
) {
|
||||
this.pos += 2;
|
||||
this.lastHaxPos = this.pos + 1;
|
||||
return String.valueOf(s2) + s3;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void readFully(BiConsumer<String, Integer> processor) {
|
||||
this.reset();
|
||||
String nextHax = this.readHex();
|
||||
while (!nextHax.equals(" ")) {
|
||||
processor.accept(nextHax, this.lastHaxPos);
|
||||
nextHax = this.readHex();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTo(int pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.pos = 0;
|
||||
}
|
||||
}
|
||||
|
291
mirai-debug/src/main/java/HexComparator.kt
Normal file
291
mirai-debug/src/main/java/HexComparator.kt
Normal file
@ -0,0 +1,291 @@
|
||||
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant")
|
||||
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.toUHexString
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.awt.Toolkit
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.lang.reflect.Field
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Hex 比较器, 并着色已知常量
|
||||
*
|
||||
* This could be used to check packet encoding..
|
||||
* but better to run under UNIX
|
||||
*
|
||||
* @author NaturalHG
|
||||
* @author Him188moe
|
||||
*/
|
||||
object HexComparator {
|
||||
|
||||
private val RED = "\u001b[31m"
|
||||
|
||||
private val GREEN = "\u001b[33m"
|
||||
|
||||
private val UNKNOWN = "\u001b[30m"
|
||||
|
||||
private val BLUE = "\u001b[34m"
|
||||
|
||||
|
||||
private val clipboardString: String?
|
||||
get() {
|
||||
val trans = Toolkit.getDefaultToolkit().systemClipboard.getContents(null)
|
||||
if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
try {
|
||||
return trans.getTransferData(DataFlavor.stringFlavor) as String
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
class ConstMatcher constructor(hex: String) {
|
||||
|
||||
private val matches = LinkedList<Match>()
|
||||
|
||||
object TestConsts {
|
||||
val NIU_BI = "牛逼".toByteArray().toUHexString()
|
||||
val _1994701021 = 1994701021.toUHexString(" ")
|
||||
val _1040400290 = 1040400290.toUHexString(" ")
|
||||
val _580266363 = 580266363.toUHexString(" ")
|
||||
|
||||
val _1040400290_ = "3E 03 3F A2"
|
||||
val _1994701021_ = "76 E4 B8 DD"
|
||||
val _jiahua_ = "B1 89 BE 09"
|
||||
val _Him188moe_ = "Him188moe".toByteArray().toUHexString()
|
||||
val 发图片 = "发图片".toByteArray().toUHexString()
|
||||
val 群 = "群".toByteArray().toUHexString()
|
||||
|
||||
val SINGLE_PLAIN_MESSAGE_HEAD = "00 00 01 00 09 01"
|
||||
|
||||
val MESSAGE_TAIL_10404 = "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"
|
||||
.replace(" ", " ")
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
object PacketIds {
|
||||
val heartbeat = "00 58"
|
||||
val friendmsg = "00 CD"
|
||||
}
|
||||
|
||||
init {
|
||||
CONST_FIELDS.forEach { field ->
|
||||
for (match in match(hex, field)) {
|
||||
matches.add(Match(match, field.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMatchedConstName(hexNumber: Int): String? {
|
||||
for (match in this.matches) {
|
||||
if (match.range.contains(hexNumber)) {
|
||||
return match.constName
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private class Match internal constructor(val range: IntRange, val constName: String)
|
||||
|
||||
companion object {
|
||||
private val CONST_FIELDS: List<Field> = listOf(
|
||||
TestConsts::class.java,
|
||||
TIMProtocol::class.java,
|
||||
PacketIds::class.java
|
||||
).map { it.declaredFields }.flatMap { fields ->
|
||||
fields.map { field ->
|
||||
field.trySetAccessible()
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun match(hex: String, field: Field): List<IntRange> {
|
||||
val constValue: String
|
||||
try {
|
||||
constValue = (field.get(null) as String).trim { it <= ' ' }
|
||||
if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes
|
||||
return LinkedList()
|
||||
}
|
||||
} catch (e: IllegalAccessException) {
|
||||
throw RuntimeException(e)
|
||||
} catch (ignored: ClassCastException) {
|
||||
return LinkedList()
|
||||
}
|
||||
|
||||
return object : LinkedList<IntRange>() {
|
||||
init {
|
||||
var index = -1
|
||||
index = hex.indexOf(constValue, index + 1)
|
||||
while (index != -1) {
|
||||
add(IntRange(index / 3, (index + constValue.length) / 3))
|
||||
|
||||
index = hex.indexOf(constValue, index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) {
|
||||
//System.out.println(constMatcher.matches);
|
||||
var i = 0
|
||||
while (i < length) {
|
||||
constNameBuilder.append(" ")
|
||||
val match = constMatcher.getMatchedConstName(i / 4)
|
||||
if (match != null) {
|
||||
var appendedNameLength = match.length
|
||||
constNameBuilder.append(match)
|
||||
while (match == constMatcher.getMatchedConstName(i++ / 4)) {
|
||||
if (appendedNameLength-- < 0) {
|
||||
constNameBuilder.append(" ")
|
||||
}
|
||||
}
|
||||
|
||||
constNameBuilder.append(" ".repeat(match.length % 4))
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
fun compare(hex1s: String, hex2s: String): String {
|
||||
val builder = StringBuilder()
|
||||
|
||||
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val constMatcher1 = ConstMatcher(hex1s)
|
||||
val constMatcher2 = ConstMatcher(hex2s)
|
||||
|
||||
if (hex1.size == hex2.size) {
|
||||
builder.append(GREEN).append("长度一致:").append(hex1.size)
|
||||
} else {
|
||||
builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size)
|
||||
}
|
||||
|
||||
|
||||
val numberLine = StringBuilder()
|
||||
val hex1ConstName = StringBuilder()
|
||||
val hex1b = StringBuilder()
|
||||
val hex2b = StringBuilder()
|
||||
val hex2ConstName = StringBuilder()
|
||||
var dif = 0
|
||||
|
||||
val length = max(hex1.size, hex2.size) * 4
|
||||
buildConstNameChain(length, constMatcher1, hex1ConstName)
|
||||
buildConstNameChain(length, constMatcher2, hex2ConstName)
|
||||
|
||||
|
||||
for (i in 0 until max(hex1.size, hex2.size)) {
|
||||
var h1: String? = null
|
||||
var h2: String? = null
|
||||
var isDif = false
|
||||
if (hex1.size <= i) {
|
||||
h1 = RED + "__"
|
||||
isDif = true
|
||||
} else {
|
||||
val matchedConstName = constMatcher1.getMatchedConstName(i)
|
||||
if (matchedConstName != null) {
|
||||
h1 = BLUE + hex1[i]
|
||||
}
|
||||
}
|
||||
if (hex2.size <= i) {
|
||||
h2 = RED + "__"
|
||||
isDif = true
|
||||
} else {
|
||||
val matchedConstName = constMatcher2.getMatchedConstName(i)
|
||||
if (matchedConstName != null) {
|
||||
h2 = BLUE + hex2[i]
|
||||
}
|
||||
}
|
||||
|
||||
if (h1 == null && h2 == null) {
|
||||
h1 = hex1[i]
|
||||
h2 = hex2[i]
|
||||
if (h1 == h2) {
|
||||
h1 = GREEN + h1
|
||||
h2 = GREEN + h2
|
||||
} else {
|
||||
h1 = RED + h1
|
||||
h2 = RED + h2
|
||||
isDif = true
|
||||
}
|
||||
} else {
|
||||
if (h1 == null) {
|
||||
h1 = RED + hex1[i]
|
||||
}
|
||||
if (h2 == null) {
|
||||
h2 = RED + hex2[i]
|
||||
}
|
||||
}
|
||||
|
||||
numberLine.append(UNKNOWN).append(getFixedNumber(i)).append(" ")
|
||||
hex1b.append(" ").append(h1).append(" ")
|
||||
hex2b.append(" ").append(h2).append(" ")
|
||||
if (isDif) {
|
||||
++dif
|
||||
}
|
||||
|
||||
//doConstReplacement(hex1b);
|
||||
//doConstReplacement(hex2b);
|
||||
}
|
||||
|
||||
return builder.append(" ").append(dif).append(" 个不同").append("\n")
|
||||
.append(numberLine).append("\n")
|
||||
.append(hex1ConstName).append("\n")
|
||||
.append(hex1b).append("\n")
|
||||
.append(hex2b).append("\n")
|
||||
.append(hex2ConstName).append("\n")
|
||||
.toString()
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun getFixedNumber(number: Int): String {
|
||||
if (number < 10) {
|
||||
return "00$number"
|
||||
}
|
||||
return if (number < 100) {
|
||||
"0$number"
|
||||
} else number.toString()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val scanner = Scanner(System.`in`)
|
||||
while (true) {
|
||||
println("Hex1: ")
|
||||
val hex1 = scanner.nextLine()
|
||||
println("Hex2: ")
|
||||
val hex2 = scanner.nextLine()
|
||||
println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
|
||||
println(HexComparator.compare(hex1, hex2))
|
||||
println()
|
||||
}
|
||||
/*
|
||||
System.out.println(HexComparator.compare(
|
||||
//mirai
|
||||
|
||||
"2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC Protocol.messageConst1 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n"
|
||||
,
|
||||
//e
|
||||
"2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC Protocol.messageConst1 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65"
|
||||
));
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
System.out.println(HexComparator.compare(
|
||||
//e
|
||||
"90 5E 39 DF 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 7B 7B 7B 7B 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC",
|
||||
//mirai
|
||||
"6F 0B DF 92 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 E9 E9 E9 E9 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC\n\n\n"
|
||||
));*/
|
||||
}
|
@ -3,14 +3,16 @@
|
||||
import jpcap.JpcapCaptor
|
||||
import jpcap.packet.IPPacket
|
||||
import jpcap.packet.UDPPacket
|
||||
import net.mamoe.mirai.message.defaults.readMessageChain
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.UnknownServerEventPacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.UnknownServerPacket
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* 模拟登录并抓取到 session key
|
||||
* 抓包分析器
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ -59,8 +61,8 @@ object Main {
|
||||
dataReceived(pk.data)
|
||||
} else {
|
||||
try {
|
||||
println("size = " + pk.data.size)
|
||||
dataSent(pk.data)
|
||||
println()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@ -74,24 +76,47 @@ object Main {
|
||||
|
||||
|
||||
/**
|
||||
* 从 TIM 内存中读取.
|
||||
* 可从 TIM 内存中读取
|
||||
*
|
||||
* 方法:
|
||||
* 在 Common.dll 中搜索
|
||||
* 1. x32dbg 附加 TIM
|
||||
* 2. `符号` 中找到 common.dll
|
||||
* 3. 搜索函数 `oi_symmetry_encrypt2` (TEA 加密函数)
|
||||
* 4. 双击跳转
|
||||
* 5. 断点并在TIM发送消息以触发
|
||||
* 6. 运行到 `mov eax,dword ptr ss:[ebp+10]`
|
||||
* 7. 从 eax 开始的 16个 bytes 便是 `sessionKey`
|
||||
*/
|
||||
const val sessionKey: String = "70 BD 1E 12 20 C1 25 12 A0 F8 4F 0D C0 A0 97 0E"
|
||||
val sessionKey: ByteArray = "48 C0 11 42 2D FD 8F 36 6E BA BF FD D3 AA B7 AE".hexToBytes()
|
||||
|
||||
fun dataReceived(data: ByteArray) {
|
||||
//println("--------------")
|
||||
//println("接收数据包")
|
||||
//println("raw packet = " + data.toUHexString())
|
||||
packetReceived(ServerPacket.ofByteArray(data))
|
||||
}
|
||||
|
||||
fun packetReceived(packet: ServerPacket) {
|
||||
when (packet) {
|
||||
is ServerEventPacket.Raw.Encrypted -> {
|
||||
val sessionKey = "8B 45 10 0F 10 00 66 0F 38 00 05 20 39 18 64 0F".hexToBytes()
|
||||
println("! ServerEventPacket.Raw.Encrypted")
|
||||
packetReceived(packet.decrypt(sessionKey))
|
||||
println("! decrypt succeed")
|
||||
}
|
||||
|
||||
is ServerEventPacket.Raw -> packetReceived(packet.distribute())
|
||||
|
||||
is UnknownServerEventPacket -> {
|
||||
println("--------------")
|
||||
println("未知事件ID=" + packet.packetId.toUHexString())
|
||||
println("未知事件: " + packet.input.readAllBytes().toUHexString())
|
||||
}
|
||||
|
||||
is ServerEventPacket -> {
|
||||
println("事件")
|
||||
println(packet)
|
||||
}
|
||||
|
||||
is UnknownServerPacket -> {
|
||||
//ignore
|
||||
}
|
||||
|
||||
else -> {
|
||||
@ -99,47 +124,45 @@ object Main {
|
||||
}
|
||||
}
|
||||
|
||||
fun dataSent(rawPacket: ByteArray) = rawPacket.cutTail(1).decode { packet ->
|
||||
println("---------------------------")
|
||||
packet.skip(3)//head
|
||||
val idHex = packet.readNBytes(4).toUHexString()
|
||||
println("发出包ID = $idHex")
|
||||
packet.skip(TIMProtocol.fixVer2.hexToBytes().size + 1 + 5 - 3 + 1)
|
||||
|
||||
val encryptedBody = packet.readAllBytes()
|
||||
println("body = ${encryptedBody.toUHexString()}")
|
||||
|
||||
encryptedBody.decode { data ->
|
||||
|
||||
fun dataSent(data: ByteArray) {
|
||||
data.cutTail(1).decode { base ->
|
||||
base.skip(3)
|
||||
val idHex = base.readNBytes(4).toUHexString()
|
||||
println("发出包$idHex")
|
||||
when (idHex.substring(0, 5)) {
|
||||
"00 CD" -> {
|
||||
println("好友消息发出: ")
|
||||
dataDecode(data) {
|
||||
//it.readShort()
|
||||
//println(it.readUInt())
|
||||
println(it.readNBytes(TIMProtocol.fixVer2.hexToBytes().size + 1 + 5 - 3 + 1).toUHexString())
|
||||
it.readAllBytes().let {
|
||||
println("解密")
|
||||
println(it.size)
|
||||
println(it.toUHexString())
|
||||
println(it.decryptBy(sessionKey).toUHexString())
|
||||
}
|
||||
println("好友消息")
|
||||
|
||||
val raw = data.readAllBytes()
|
||||
println("解密前数据: " + raw.toUHexString())
|
||||
val messageData = raw.decryptBy(sessionKey)
|
||||
println("解密结果: " + messageData.toUHexString())
|
||||
println("尝试解消息")
|
||||
messageData.decode {
|
||||
it.skip(
|
||||
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
|
||||
+ 1
|
||||
)
|
||||
val chain = it.readMessageChain()
|
||||
println(chain.toObjectString())
|
||||
}
|
||||
}
|
||||
|
||||
"03 88" -> {
|
||||
println("上传图片-获取图片ID")
|
||||
data.skip(8)
|
||||
val body = data.readAllBytes().decryptBy(sessionKey)
|
||||
println(body.toUHexString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
00 19
|
||||
tim的 publicKey = 02 F4 07 37 2D F1 82 1D 45 E8 30 14 41 74 AF E3 03 AB 29 D7 82 D9 E2 E5 89
|
||||
00 00
|
||||
tim的 key0836=70 BE 41 20 3A FA 05 B2 2D 66 2E 29 33 55 99 7E
|
||||
552
|
||||
76 AF AE 95 EB 89 BE B5 1C 83 D2 87 23 3B 5A 3B 6B 4C 78 AD F9 93 86 CA 13 D7 86 B5 0C D1 84 FB 2B ED 59 26 42 3B E0 6F 1A 91 A5 98 91 20 25 3F 6D C0 F6 FC 27 3D F8 34 EA 50 95 8C 2A BB 22 73 BD 76 60 2A 6B 68 51 07 4A 2F 37 6D 97 42 51 C5 14 47 96 3A A9 6B 8F 66 F8 D4 F4 52 22 13 D5 CC 9F B1 B4 06 BC 4B 35 B6 CF D8 CB 70 0F 0C E6 AA D9 12 E9 A2 C7 7F D8 24 7E 1B 2D 97 67 DA 34 0A FD 8E 44 D3 58 50 0D F0 0A 20 08 0A 46 28 68 0A 06 17 36 84 94 2C 97 2A 22 32 7B 01 67 3F E4 90 71 88 B2 F9 7B 7B AC 1A 00 CD 54 4A D7 AE 71 68 B3 FB E5 F3 94 9A C2 A1 C3 CA A5 4E AB 2C B0 78 AD EE 63 3F E6 24 6E AC 31 A5 00 F4 DB C7 4B 65 44 7B 92 87 30 7D 73 B3 21 81 C8 99 33 06 65 28 0C 98 56 EF 41 DC 64 79 55 69 AD B7 F4 A4 CF 4A 28 4B 3B E3 5A 2B C1 72 20 95 D9 8E 9F 1E A5 DE 9A DD 39 0B BE 76 A8 BE 95 9D 7C C2 C5 A8 3A D3 76 B6 D4 ED 15 34 5D 3C 8E 96 C6 93 64 78 A1 89 78 DA F8 17 E5 96 75 5F B6 97 FC 41 18 A4 54 67 BA 3B ED 97 27 B7 E3 90 81 1E DC 8D 17 25 46 2D 08 0D BB 95 D0 CB C8 9B 78 36 2D 70 E3 C6 4C 21 E9 C0 02 69 3B C5 F7 91 6B 62 D8 E4 10 F0 01 5B 7F 1A 3E 9F 1A D4 D3 A9 2B 4A C2 BD 6D 8B B0 0A AE A4 E9 72 71 F4 39 28 CE 18 42 ED FD BB 61 08 B1 95 93 8E F6 29 D7 B6 CB 15 2A AA AF A7 81 AD DF 3B D5 3F 47 29 AB 61 0C 86 48 82 93 AE 8C 2C 32 CC 83 83 68 08 C6 9D 10 81 82 BA 92 24 0E ED 71 B1 83 E1 08 D0 01 BB DF E2 26 D0 20 DF 8C 95 E1 A6 42 C2 A2 E7 85 00 E6 AA 54 A8 0C 5D BB 8D 46 37 AD 47 88 38 B9 D7 3B 48 13 13 81 3B A5 05 4D 32 24 A4 CE 08 73 6D 89 FD 6D CC F5 AB 8B 6A 39 4B 9D 30 33 73 F1 01 7F E4 43 03 72 44 67 3A 24 28 40 51 2B EB 48 EB F9 05 A9 3C 20 EB 4D B7 45 56 D3 4E BD A0 B5 40 65 D1 16 57 73 A4 81 B1 A6 8C 3F 68 28 AA EB 83
|
||||
*/
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user