diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/PushNotifyPack.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/PushNotifyPack.kt
index 12ddbff03..3d45060cd 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/PushNotifyPack.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/PushNotifyPack.kt
@@ -12,6 +12,10 @@ package net.mamoe.mirai.internal.network.protocol.data.jce
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.utils.io.JceStruct
+import net.mamoe.mirai.internal.utils.io.NestedStructure
+import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer
+import net.mamoe.mirai.internal.utils.io.ProtocolStruct
+import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId
 import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
 
@@ -46,6 +50,7 @@ internal class MsgInfo(
     @TarsId(3) @JvmField val shMsgSeq: Short,
     @TarsId(4) @JvmField val strMsg: String?,
     @TarsId(5) @JvmField val uRealMsgTime: Int?,
+    @param:NestedStructure(VMsgDesensitizationSerializer::class)
     @TarsId(6) @JvmField val vMsg: ByteArray,
     @TarsId(7) @JvmField val uAppShareID: Long?,
     @TarsId(8) @JvmField val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY,
@@ -62,6 +67,15 @@ internal class MsgInfo(
     //@SerialId(19) @JvmField val stC2CTmpMsgHead: TempMsgHead?
 ) : JceStruct
 
+internal object VMsgDesensitizationSerializer : NestedStructureDesensitizer<MsgInfo, ProtocolStruct> {
+    override fun deserialize(context: MsgInfo, byteArray: ByteArray): ProtocolStruct? {
+        return when (context.shMsgType.toUShort().toInt()) {
+            0x210 -> byteArray.loadAs(MsgType0x210.serializer())
+            else -> null
+        }
+    }
+}
+
 
 @Serializable
 internal class ShareData(
diff --git a/mirai-core/src/commonMain/kotlin/utils/io/ProtocolStruct.kt b/mirai-core/src/commonMain/kotlin/utils/io/ProtocolStruct.kt
index e741b48b8..c3692f328 100644
--- a/mirai-core/src/commonMain/kotlin/utils/io/ProtocolStruct.kt
+++ b/mirai-core/src/commonMain/kotlin/utils/io/ProtocolStruct.kt
@@ -9,6 +9,18 @@
 
 package net.mamoe.mirai.internal.utils.io
 
+import kotlin.reflect.KClass
+
 internal interface ProtocolStruct
 internal interface ProtoBuf : ProtocolStruct
-internal interface JceStruct : ProtocolStruct
\ No newline at end of file
+internal interface JceStruct : ProtocolStruct
+
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.VALUE_PARAMETER)
+internal annotation class NestedStructure(
+    val serializer: KClass<out NestedStructureDesensitizer<*, *>>
+)
+
+internal interface NestedStructureDesensitizer<in C : ProtocolStruct, T : ProtocolStruct> {
+    fun deserialize(context: C, byteArray: ByteArray): T?
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt b/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt
index 4df3cfcfc..179ab9721 100644
--- a/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt
+++ b/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt
@@ -11,11 +11,17 @@ package net.mamoe.mirai.internal.notice
 
 import kotlinx.serialization.decodeFromString
 import net.mamoe.mirai.Mirai
+import net.mamoe.mirai.internal.notice.Desensitizer.Companion.generateAndDesensitize
 import net.mamoe.mirai.internal.utils.codegen.*
+import net.mamoe.mirai.internal.utils.io.NestedStructure
+import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer
+import net.mamoe.mirai.internal.utils.io.ProtocolStruct
 import net.mamoe.mirai.utils.*
 import net.mamoe.yamlkt.Yaml
 import net.mamoe.yamlkt.YamlBuilder
 import kotlin.reflect.KType
+import kotlin.reflect.full.createInstance
+import kotlin.reflect.full.findAnnotation
 import kotlin.reflect.typeOf
 
 private val logger: MiraiLogger by lazy { MiraiLogger.Factory.create(Desensitizer::class) }
@@ -179,4 +185,20 @@ private class DesensitizationVisitor(
             desc.value = desensitizer.desensitize(desc.value as ByteArray)
         }
     }
+
+    override fun <T : Any> visitClass(desc: ClassValueDesc<T>) {
+        super.visitClass(desc)
+        desc.properties.replaceAll() { key, value ->
+            val annotation = key.findAnnotation<NestedStructure>()
+            if (annotation != null && value.origin is ByteArray) {
+                val instance = annotation.serializer.objectInstance ?: annotation.serializer.createInstance()
+
+                val result = instance.cast<NestedStructureDesensitizer<ProtocolStruct, ProtocolStruct>>()
+                    .deserialize(desc.origin as ProtocolStruct, value.origin as ByteArray)
+
+                val generate = ConstructorCallCodegenFacade.generateAndDesensitize(result)
+                PlainValueDesc(desc, "$generate.toByteArray()", value.origin)
+            } else value
+        }
+    }
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt
index 647fd5c47..60bdbdd6d 100644
--- a/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt
+++ b/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt
@@ -25,7 +25,7 @@ object ConstructorCallCodegenFacade {
      * Analyze [value] and give its correspondent [ValueDesc].
      */
     fun analyze(value: Any?, type: KType): ValueDesc {
-        if (value == null) return PlainValueDesc("null", null)
+        if (value == null) return PlainValueDesc(null, "null", null)
 
         val clazz = value::class
 
@@ -43,14 +43,15 @@ object ConstructorCallCodegenFacade {
                 prop.cast<KProperty1<Any, Any?>>()
                 map[valueParameter] = analyze(prop.get(value), prop.returnType)
             }
-            return ClassValueDesc(value, map)
+            return ClassValueDesc(null, value, map)
         }
 
-        ArrayValueDesc.createOrNull(value, type)?.let { return it }
+        ArrayValueDesc.createOrNull(value, type, null)?.let { return it }
         if (value is Collection<*>) {
-            return CollectionValueDesc(value, arrayType = type, elementType = type.arguments.first().type!!)
+            return CollectionValueDesc(null, value, arrayType = type, elementType = type.arguments.first().type!!)
         } else if (value is Map<*, *>) {
             return MapValueDesc(
+                null,
                 value.cast(),
                 value.cast(),
                 type,
@@ -61,12 +62,12 @@ object ConstructorCallCodegenFacade {
 
         return when (value) {
             is CharSequence -> {
-                PlainValueDesc('"' + value.toString() + '"', value)
+                PlainValueDesc(null, '"' + value.toString() + '"', value)
             }
             is Char -> {
-                PlainValueDesc("'$value'", value)
+                PlainValueDesc(null, "'$value'", value)
             }
-            else -> PlainValueDesc(value.toString(), value)
+            else -> PlainValueDesc(null, value.toString(), value)
         }
     }
 
diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt
index e9db2d249..767cf6926 100644
--- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt
+++ b/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt
@@ -17,10 +17,23 @@ import kotlin.reflect.typeOf
 
 sealed interface ValueDesc {
     val origin: Any?
+    val parent: ValueDesc?
 
     fun accept(visitor: ValueDescVisitor)
 }
 
+val ValueDesc.parents
+    get() = sequence {
+        var parent = parent
+        do {
+            parent ?: return@sequence
+            yield(parent)
+            parent = parent.parent
+        } while (true);
+    }
+
+inline fun <reified T : ValueDesc> ValueDesc.findParent(): T? = parents.filterIsInstance<T>().firstOrNull()
+
 sealed interface ArrayValueDesc : ValueDesc {
     val value: Any
 
@@ -30,17 +43,27 @@ sealed interface ArrayValueDesc : ValueDesc {
 
     companion object {
         @OptIn(ExperimentalStdlibApi::class)
-        fun createOrNull(array: Any, type: KType): ArrayValueDesc? {
-            if (array is Array<*>) return ObjectArrayValueDesc(array, arrayType = type)
+        fun createOrNull(array: Any, type: KType, parent: ValueDesc?): ArrayValueDesc? {
+            if (array is Array<*>) return ObjectArrayValueDesc(parent, array, arrayType = type)
             return when (array) {
-                is IntArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Int>())
-                is ByteArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Byte>())
-                is ShortArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Short>())
-                is CharArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Char>())
-                is LongArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Long>())
-                is FloatArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Float>())
-                is DoubleArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Double>())
-                is BooleanArray -> PrimitiveArrayValueDesc(array, arrayType = type, elementType = typeOf<Boolean>())
+                is IntArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Int>())
+                is ByteArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Byte>())
+                is ShortArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Short>())
+                is CharArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Char>())
+                is LongArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Long>())
+                is FloatArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Float>())
+                is DoubleArray -> PrimitiveArrayValueDesc(
+                    parent,
+                    array,
+                    arrayType = type,
+                    elementType = typeOf<Double>()
+                )
+                is BooleanArray -> PrimitiveArrayValueDesc(
+                    parent,
+                    array,
+                    arrayType = type,
+                    elementType = typeOf<Boolean>()
+                )
                 else -> return null
             }
         }
@@ -48,10 +71,11 @@ sealed interface ArrayValueDesc : ValueDesc {
 }
 
 class ObjectArrayValueDesc(
+    override val parent: ValueDesc?,
     override var value: Array<*>,
     override val origin: Array<*> = value,
     override val arrayType: KType,
-    override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType()
+    override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType(),
 ) : ArrayValueDesc {
     override val elements: MutableList<ValueDesc> by lazy {
         value.mapTo(mutableListOf()) {
@@ -65,6 +89,7 @@ class ObjectArrayValueDesc(
 }
 
 class CollectionValueDesc(
+    override val parent: ValueDesc?,
     override var value: Collection<*>,
     override val origin: Collection<*> = value,
     override val arrayType: KType,
@@ -82,6 +107,7 @@ class CollectionValueDesc(
 }
 
 class MapValueDesc(
+    override val parent: ValueDesc?,
     var value: Map<Any?, Any?>,
     override val origin: Map<Any?, Any?> = value,
     val mapType: KType,
@@ -103,6 +129,7 @@ class MapValueDesc(
 }
 
 class PrimitiveArrayValueDesc(
+    override val parent: ValueDesc?,
     override var value: Any,
     override val origin: Any = value,
     override val arrayType: KType,
@@ -128,6 +155,7 @@ class PrimitiveArrayValueDesc(
 }
 
 class PlainValueDesc(
+    override val parent: ValueDesc?,
     var value: String,
     override val origin: Any?
 ) : ValueDesc {
@@ -141,6 +169,7 @@ class PlainValueDesc(
 }
 
 class ClassValueDesc<T : Any>(
+    override val parent: ValueDesc?,
     override val origin: T,
     val properties: MutableMap<KParameter, ValueDesc>,
 ) : ValueDesc {