diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
index 927b314e0..c4ade482d 100644
--- a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
@@ -3,8 +3,11 @@ package net.mamoe.mirai.qqandroid
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.utils.BotConfiguration
 import net.mamoe.mirai.utils.Context
+import net.mamoe.mirai.utils.MiraiInternalAPI
 
-internal actual class QQAndroidBot actual constructor(
+@UseExperimental(MiraiInternalAPI::class)
+internal actual class QQAndroidBot
+actual constructor(
     context: Context,
     account: BotAccount,
     configuration: BotConfiguration
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
index f50153147..4077d3a1d 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
@@ -18,16 +18,15 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.QQImpl
 import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
 import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
+import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.*
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
-import net.mamoe.mirai.utils.LockFreeLinkedList
-import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.cryptor.contentToString
-import net.mamoe.mirai.utils.getValue
 import net.mamoe.mirai.utils.io.*
-import net.mamoe.mirai.utils.unsafeWeakRef
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.Volatile
@@ -107,6 +106,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
 
     override suspend fun init() {
         //  delay(5000)
+        MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
 
         this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
             if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt
index b7286f5f9..1826af132 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt
@@ -145,7 +145,7 @@ internal class MsgComm : ProtoBuf {
         @SerialId(1) val lastReadTime: Int = 0,
         @SerialId(2) val peerUin: Long = 0L,
         @SerialId(3) val msgCompleted: Int = 0,
-        @SerialId(4) val msg: List<Msg>,
+        @SerialId(4) val msg: List<Msg>? = null,
         @SerialId(5) val unreadMsgNum: Int = 0,
         @SerialId(8) val c2cType: Int = 0,
         @SerialId(9) val serviceType: Int = 0,
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt
index 6a0411386..f39695cc3 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/SyncCookie.kt
@@ -3,14 +3,15 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.proto
 import kotlinx.serialization.SerialId
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.qqandroid.io.ProtoBuf
+import kotlin.math.absoluteValue
 import kotlin.random.Random
 
 @Serializable
 class SyncCookie(
     @SerialId(1) val time1: Long? = null, // 1580277992
     @SerialId(2) val time: Long, // 1580277992
-    @SerialId(3) val unknown1: Long = Random.nextLong(),// 678328038
-    @SerialId(4) val unknown2: Long = Random.nextLong(), // 1687142153
+    @SerialId(3) val unknown1: Long = Random.nextLong().absoluteValue,// 678328038
+    @SerialId(4) val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153
     @SerialId(5) val const1: Long = const1_, // 1458467940
     @SerialId(11) val const2: Long = const2_, // 2683038258
     @SerialId(12) val unknown3: Long = 0x1d,
@@ -18,8 +19,8 @@ class SyncCookie(
     @SerialId(14) val unknown4: Long = 0
 ) : ProtoBuf
 
-private val const1_: Long = Random.nextLong()
-private val const2_: Long = Random.nextLong()
+private val const1_: Long = Random.nextLong().absoluteValue
+private val const2_: Long = Random.nextLong().absoluteValue
 /*
 
 @Serializable
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
index 825010b16..98a54f1b3 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
@@ -4,6 +4,7 @@ import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import net.mamoe.mirai.data.MultiPacket
 import net.mamoe.mirai.data.Packet
+import net.mamoe.mirai.event.BroadcastControllable
 import net.mamoe.mirai.message.FriendMessage
 import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.qqandroid.QQAndroidBot
@@ -29,7 +30,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.cryptor.contentToString
 import net.mamoe.mirai.utils.currentTimeSeconds
 import net.mamoe.mirai.utils.io.hexToBytes
-import net.mamoe.mirai.utils.io.toUHexString
 import kotlin.math.absoluteValue
 import kotlin.random.Random
 
@@ -39,14 +39,14 @@ internal class MessageSvc {
      */
     internal object PushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
-            discardExact(4)
+            discardExact(4) // don't remove
 
             return decodeUniPacket(RequestPushNotify.serializer())
         }
 
         override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
             network.run {
-                return PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: 0)
+                return PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds)
             }
         }
     }
@@ -57,9 +57,6 @@ internal class MessageSvc {
      */
     @UseExperimental(MiraiInternalAPI::class)
     internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
-        val EXTRA_DATA =
-            "08 00 12 33 6D 6F 64 65 6C 3A 78 69 67 6F 6D 69 20 36 3B 6F 73 3A 32 32 3B 76 65 72 73 69 6F 6E 3A 76 32 6D 61 6E 3A 78 69 61 6F 6D 69 73 79 73 3A 4C 4D 59 34 38 5A 18 E4 E1 A4 FF FE 2D 20 E9 E1 A4 FF FE 2D 28 A8 E1 A4 FF FE 2D 30 99 E1 A4 FF FE 2D".hexToBytes()
-
         operator fun invoke(
             client: QQAndroidClient,
             syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
@@ -67,7 +64,7 @@ internal class MessageSvc {
         ): OutgoingPacket = buildOutgoingUniPacket(
             client
         ) {
-            println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
+            //println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
             writeProtoBuf(
                 MsgSvc.PbGetMsgReq.serializer(),
                 MsgSvc.PbGetMsgReq(
@@ -96,7 +93,11 @@ internal class MessageSvc {
          * 不要直接 expect 这个 class. 它可能
          */
         @MiraiInternalAPI
-        open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: MutableList<FriendMessage>) : MultiPacket<FriendMessage>(delegate) {
+        open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: MutableList<FriendMessage>) : MultiPacket<FriendMessage>(delegate),
+            BroadcastControllable {
+            override val shouldBroadcast: Boolean
+                get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP
+
             override fun toString(): String {
                 return "MessageSvc.PbGetMsg.Response($syncFlagFromServer=$syncFlagFromServer, messages=List(size=${this.size}))"
             }
@@ -120,7 +121,7 @@ internal class MessageSvc {
                 return GetMsgSuccess(mutableListOf())
             }
 
-            val messages = resp.uinPairMsgs.asSequence().flatMap { it.msg.asSequence() }.mapNotNull {
+            val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull {
                 when (it.msgHead.msgType) {
                     166 -> {
                         FriendMessage(
@@ -134,7 +135,7 @@ internal class MessageSvc {
                 }
             }.toMutableList()
             if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
-                return GetMsgSuccess(messages)
+                return GetMsgSuccess(mutableListOf(messages.last()))
             }
             return Response(resp.syncFlag, messages)
         }
@@ -146,7 +147,7 @@ internal class MessageSvc {
 
                 MsgSvc.SyncFlag.CONTINUE -> {
                     network.run {
-                        PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds)
+                        PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendWithoutExpect()
                     }
                     return
                 }
@@ -199,8 +200,7 @@ internal class MessageSvc {
                     ),
                     msgSeq = client.atomicNextMessageSequenceId(),
                     msgRand = Random.nextInt().absoluteValue,
-                    syncCookie = client.c2cMessageSync.syncCookie?.takeIf { it.isNotEmpty() }
-                        ?: SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
+                    syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
                     // msgVia = 1
                 )
             )
@@ -217,21 +217,22 @@ internal class MessageSvc {
 
             ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
 
+            val seq = client.atomicNextMessageSequenceId()
             ///return@buildOutgoingUniPacket
             writeProtoBuf(
                 MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
                     routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupId)), // TODO: 2020/1/30 确认这里是 id 还是 internalId
-                    contentHead = MsgComm.ContentHead(pkgNum = 1),
+                    contentHead = MsgComm.ContentHead(pkgNum = 1, divSeq = seq),
                     msgBody = ImMsgBody.MsgBody(
                         richText = ImMsgBody.RichText(
                             elems = message.toRichTextElems()
                         )
                     ),
-                    msgSeq = client.atomicNextMessageSequenceId(),
-                    //msgRand = Random.nextInt() and 0x7FFF,
-                    syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer())
-                    //SyncCookie(currentTimeSeconds, Random.nextLong().absoluteValue, Random.nextLong().absoluteValue).toByteArray(SyncCookie.serializer())
-                    // msgVia = 1
+                    msgSeq = seq,
+                    msgRand = Random.nextInt().absoluteValue,
+                    syncCookie = "08 A0 C2 C4 F1 05 10 A0 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 E4 C2 B1 95 03 48 A1 9F E0 C7 08 58 D3 C2 8F A0 09 60 1D 68 A0 C2 C4 F1 05 70 00".hexToBytes()
+                        ?: SyncCookie(time = currentTimeSeconds + client.timeDifference).toByteArray(SyncCookie.serializer()),
+                    msgVia = 0
                 )
             )
         }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt
index 3503b1192..67d03a3b2 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt
@@ -3,11 +3,10 @@
 package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
 
 import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
 import net.mamoe.mirai.contact.MemberPermission
 import net.mamoe.mirai.message.GroupMessage
 import net.mamoe.mirai.qqandroid.QQAndroidBot
-import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
+import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush
 import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
@@ -22,7 +21,7 @@ internal class OnlinePush {
         @UseExperimental(ExperimentalStdlibApi::class)
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): GroupMessage {
             // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 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 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
-            val pbPushMsg = ProtoBufWithNullableSupport.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
+            val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
 
             val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
 
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt
index 0bda9be52..b9f842dd3 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt
@@ -5,62 +5,123 @@ import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.utils.io.hexToBytes
 
+internal fun NotOnlineImageFromFile.toJceData(): ImMsgBody.NotOnlineImage {
+    return ImMsgBody.NotOnlineImage(
+        filePath = this.filepath,
+        resId = this.resourceId,
+        oldPicMd5 = false,
+        picMd5 = this.md5,
+        fileLen = this.fileLength,
+        picHeight = this.height,
+        picWidth = this.width,
+        bizType = this.bizType,
+        imgType = this.imageType,
+        downloadPath = this.downloadPath
+    )
+}
 
+/*
+notOnlineImage=NotOnlineImage#2050019814 {
+        filePath=41AEF2D4B5BD24CF3791EFC5FEB67D60.jpg
+        fileLen=0x00000350(848)
+        downloadPath=/f2b7e5c0-acb3-4e83-aa5c-c8383840cc91
+        oldVerSendFile=<Empty ByteArray>
+        imgType=0x000003E8(1000)
+        previewsImage=<Empty ByteArray>
+        picMd5=41 AE F2 D4 B5 BD 24 CF 37 91 EF C5 FE B6 7D 60
+        picHeight=0x00000032(50)
+        picWidth=0x00000033(51)
+        resId=/f2b7e5c0-acb3-4e83-aa5c-c8383840cc91
+        flag=<Empty ByteArray>
+        thumbUrl=
+        original=0x00000000(0)
+        bigUrl=
+        origUrl=
+        bizType=0x00000005(5)
+        result=0x00000000(0)
+        index=0x00000000(0)
+        opFaceBuf=<Empty ByteArray>
+        oldPicMd5=false
+        thumbWidth=0x00000000(0)
+        thumbHeight=0x00000000(0)
+        fileId=0x00000000(0)
+        showLen=0x00000000(0)
+        downloadLen=0x00000000(0)
+        _400Url=
+        _400Width=0x00000000(0)
+        _400Height=0x00000000(0)
+        pbReserve=08 01 10 00 32 00 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05
+}
+ */
 internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
-    val elems = mutableListOf<ImMsgBody.Elem>()
+    val elements = mutableListOf<ImMsgBody.Elem>()
 
     this.forEach {
         when (it) {
             is PlainText -> {
-                elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
+                elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
             }
             is At -> {
 
             }
-            is Image -> {
-                elems.add(
-                    ImMsgBody.Elem(
-                        notOnlineImage = ImMsgBody.NotOnlineImage(
-                            filePath = it.id.value, // 错了, 应该是 2B23D705CAD1F2CF3710FE582692FCC4.jpg
-                            fileLen = 1149, // 假的
-                            downloadPath = it.id.value,
-                            imgType = 1000, // 不确定
-                            picMd5 = "2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4".hexToBytes(),
-                            picHeight = 66,
-                            picWidth = 66,
-                            resId = it.id.value,
-                            bizType = 5,
-                            pbReserve = ImMsgBody.PbReserve.DEFAULT // 可能还可以改变 `[动画表情]`
-                        )
-                    )
-                )
+            is NotOnlineImageFromServer -> {
+                elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
+                elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes())))
+            }
+            is NotOnlineImageFromFile -> {
+                elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
+                elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes())))
             }
         }
     }
 
-    return elems
+    return elements
 }
 
 
+internal class NotOnlineImageFromServer(
+    internal val delegate: ImMsgBody.NotOnlineImage
+) : NotOnlineImage() {
+    override val resourceId: String
+        get() = delegate.resId
+    override val md5: ByteArray
+        get() = delegate.picMd5
+    override val filepath: String
+        get() = delegate.filePath
+    override val fileLength: Int
+        get() = delegate.fileLen
+    override val height: Int
+        get() = delegate.picHeight
+    override val width: Int
+        get() = delegate.picWidth
+    override val bizType: Int
+        get() = delegate.bizType
+    override val imageType: Int
+        get() = delegate.imgType
+    override val downloadPath: String
+        get() = delegate.downloadPath
+
+}
+
 internal fun ImMsgBody.RichText.toMessageChain(): MessageChain {
     val message = MessageChain(initialCapacity = elems.size)
 
     elems.forEach {
         when {
             it.notOnlineImage != null -> message.add(
-                Image(
-                    ImageIdQQA(
-                        it.notOnlineImage.resId,
-                        it.notOnlineImage.origUrl
-                    )
-                )
+                NotOnlineImageFromServer(it.notOnlineImage)
             )
             it.customFace != null -> message.add(
-                Image(
-                    ImageIdQQA(
-                        it.customFace.filePath,
-                        it.customFace.origUrl
-                    )
+                NotOnlineImageFromFile(
+                    it.customFace.filePath,
+                    it.customFace.md5,
+                    it.customFace.origUrl,
+                    it.customFace.downloadLen,
+                    it.customFace.height,
+                    it.customFace.width,
+                    it.customFace.bizType,
+                    it.customFace.imageType,
+                    it.customFace.filePath
                 )
             )
             it.text != null -> message.add(it.text.str.toMessage())
@@ -70,12 +131,5 @@ internal fun ImMsgBody.RichText.toMessageChain(): MessageChain {
     return message
 }
 
-internal class ImageIdQQA(
-    override val value: String,
-    originalLink: String
-) : ImageId {
-    val link: ImageLink =
-        ImageLinkQQA("http://gchat.qpic.cn$originalLink")
-}
 
 internal inline class ImageLinkQQA(override val original: String) : ImageLink
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
index 889564387..417c682ec 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -8,7 +8,6 @@ import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.use
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.AddFriendResult
-import net.mamoe.mirai.data.ImageLink
 import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.utils.GroupNotFoundException
@@ -104,11 +103,9 @@ abstract class Bot : CoroutineScope {
 
     // region actions
 
-    abstract suspend fun Image.getLink(): ImageLink
+    abstract suspend fun Image.downloadAsByteArray(): ByteArray
 
-    suspend fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
-
-    suspend fun Image.download(): ByteReadPacket = getLink().download()
+    abstract suspend fun Image.download(): ByteReadPacket
 
     /**
      * 添加一个好友
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
index b712fa29e..c200aef27 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
@@ -36,7 +36,7 @@ interface Contact : CoroutineScope {
      */
     suspend fun sendMessage(message: MessageChain)
 
-    suspend fun uploadImage(image: ExternalImage): ImageId
+    suspend fun uploadImage(image: ExternalImage): Image
 }
 
 suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain())
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt
index f725d1d56..2b1365daa 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt
@@ -37,7 +37,7 @@ class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedLis
 
 operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
     forEach { if (it.id == id) return it }
-    throw NoSuchElementException()
+    throw NoSuchElementException("No such contact with id $id")
 }
 
 fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt
index 94679ab57..721b57942 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt
@@ -6,7 +6,6 @@ import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.EventPacket
-import net.mamoe.mirai.data.ImageLink
 import net.mamoe.mirai.event.events.BotEvent
 import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.utils.*
@@ -65,17 +64,14 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
 
     suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
     suspend inline fun Image.send() = this.sendTo(subject)
-    suspend inline fun ImageId.send() = this.sendTo(subject)
     suspend inline fun Message.send() = this.sendTo(subject)
     suspend inline fun String.send() = this.toMessage().sendTo(subject)
 
     // endregion
 
     // region Image download
-    suspend inline fun Image.getLink(): ImageLink = with(bot) { getLink() }
-
-    suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
-    suspend inline fun Image.download(): ByteReadPacket = getLink().download()
+    suspend inline fun Image.downloadAsByteArray(): ByteArray = bot.run { downloadAsByteArray() }
+    suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
     // endregion
 
     fun At.qq(): QQ = bot.getQQ(this.target)