From 795a3123799dd47f0a6418699a010ca0a50df9c4 Mon Sep 17 00:00:00 2001 From: Nacho Date: Tue, 7 Oct 2025 12:52:16 +0800 Subject: [PATCH 1/2] feat(PacketHelper): auto detect xml message --- .../moe/ono/creator/PacketHelperDialog.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/moe/ono/creator/PacketHelperDialog.java b/app/src/main/java/moe/ono/creator/PacketHelperDialog.java index 1c9664d1..b01cf617 100644 --- a/app/src/main/java/moe/ono/creator/PacketHelperDialog.java +++ b/app/src/main/java/moe/ono/creator/PacketHelperDialog.java @@ -66,6 +66,8 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -212,9 +214,21 @@ protected void onCreate() { try { new JSONArray(preContent); } catch (JSONException ex) { - editText.setHint("纯文本..."); - mRgSendType.check(R.id.rb_text); - mRgSendBy.setVisibility(GONE); + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser parser = factory.newPullParser(); + parser.setInput(new java.io.StringReader(preContent)); + while (parser.next() != XmlPullParser.END_DOCUMENT) { + } + editText.setHint("Xml..."); + mRgSendType.check(R.id.rb_xml); + mRgSendBy.setVisibility(GONE); + } catch (Exception er) { + editText.setHint("纯文本..."); + mRgSendType.check(R.id.rb_text); + mRgSendBy.setVisibility(GONE); + } } } } From 88118ceb1b87bf2b1a0f213a7cf9cff0065d49e9 Mon Sep 17 00:00:00 2001 From: Nacho Date: Tue, 7 Oct 2025 12:58:12 +0800 Subject: [PATCH 2/2] feat: add BilibiliAutoPlayCard --- .../hooks/item/chat/BilibiliAutoPlayCard.kt | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 app/src/main/java/moe/ono/hooks/item/chat/BilibiliAutoPlayCard.kt diff --git a/app/src/main/java/moe/ono/hooks/item/chat/BilibiliAutoPlayCard.kt b/app/src/main/java/moe/ono/hooks/item/chat/BilibiliAutoPlayCard.kt new file mode 100644 index 00000000..a3b58d18 --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/item/chat/BilibiliAutoPlayCard.kt @@ -0,0 +1,151 @@ +package moe.ono.hooks.item.chat + +import android.annotation.SuppressLint +import com.google.protobuf.ByteString +import com.google.protobuf.UnknownFieldSet +import com.tencent.mobileqq.fe.FEKit +import de.robv.android.xposed.XC_MethodHook +import kotlinx.io.core.BytePacketBuilder +import kotlinx.io.core.readBytes +import kotlinx.io.core.writeFully +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.protobuf.ProtoBuf +import moe.ono.ext.getUnknownObject +import moe.ono.ext.toInnerValuesString +import moe.ono.hooks._base.BaseSwitchFunctionHookItem +import moe.ono.hooks._core.annotation.HookItem +import moe.ono.hooks.item.developer.QQHookCodec +import moe.ono.hooks.protocol.entries.QQSsoSecureInfo +import moe.ono.loader.hookapi.IHijacker +import moe.ono.util.FunProtoData +import moe.ono.util.Logger +import moe.ono.util.SyncUtils + +@SuppressLint("DiscouragedApi") +@HookItem(path = "聊天与消息/哔哩哔哩自动转播放卡", description = "从哔哩哔哩分享的小程序卡片将会自动转换为点击就自动播放的卡片\n* 需开启 '开发者选项/注入 CodecWarpper'\n* 重启生效") +class BilibiliAutoPlayCard : BaseSwitchFunctionHookItem() { + override fun entry(classLoader: ClassLoader) { + QQHookCodec.hijackers.add(object: IHijacker { + override fun onHandle( + param: XC_MethodHook.MethodHookParam, + uin: String, + cmd: String, + seq: Int, + buffer: ByteArray, + bufferIndex: Int + ): Boolean { + this@BilibiliAutoPlayCard.onHandle(param, uin, cmd, seq, buffer, bufferIndex) + return false + } + override val command: String = "LightAppSvc.mini_app_share.AdaptShareInfo" + }) + } + + @OptIn(ExperimentalSerializationApi::class) + private fun onHandle(param: XC_MethodHook.MethodHookParam, uin: String, cmd: String, seq: Int, buffer: ByteArray, bufferIndex: Int) { + val data = FunProtoData() + data.fromBytes(FunProtoData.getUnpPackage(buffer)) + Logger.d(data.toJSON().toString()) + + val json = data.toJSON().optJSONObject("4") + val appid = json?.optString("2").toString() + + if (appid != "1109937557") return + + val path = json?.optString("11").toString() + val bvid = path.substringAfter("bvid=").substringBefore("&share_source") + + if (bvid.isEmpty()) return + + Logger.d(bvid) + + val changePath = + "pages/packages/fallback/webview/webview.html?url=https%3a%2f%2fwww.bilibili.com%2fblackboard%2fnewplayer.html%3fbvid%3d$bvid%26page%3d1%26autoplay%3d1" + + val mBuffer = replaceField4_11_inPacket(buffer, changePath) + + Logger.d(FunProtoData().also { it.fromBytes(FunProtoData.getUnpPackage(mBuffer)) }.toJSON().toString()) + + if (bufferIndex == 15 && param.args[13] != null) { + // 因为包体改变,重新签名 + val qqSecurityHead = UnknownFieldSet.parseFrom(param.args[13] as ByteArray) + val qqSecurityHeadBuilder = UnknownFieldSet.newBuilder(qqSecurityHead) + qqSecurityHeadBuilder.clearField(24) + val sign = FEKit.getInstance().getSign("LightAppSvc.mini_app_privacy.GetPrivacyInfo", mBuffer, seq, uin) +// FEKit.getInstance().cmdWhiteList.forEach { it -> +// Logger.d(it) +// } + Logger.d("sign ->" + sign.toInnerValuesString()) + qqSecurityHeadBuilder.addField(24, UnknownFieldSet.Field.newBuilder().also { + it.addLengthDelimited( + ByteString.copyFrom( + ProtoBuf.encodeToByteArray(QQSsoSecureInfo( + secSig = sign.sign, + extra = sign.extra, + deviceToken = sign.token + )))) + }.build()) + param.args[13] = qqSecurityHeadBuilder.build().toByteArray() + } + + if (bufferIndex == 15 && param.args[14] != null) { + val qqSecurityHead = UnknownFieldSet.parseFrom(param.args[14] as ByteArray) + val qqSecurityHeadBuilder = UnknownFieldSet.newBuilder(qqSecurityHead) + qqSecurityHeadBuilder.clearField(24) + val sign = FEKit.getInstance().getSign("LightAppSvc.mini_app_privacy.GetPrivacyInfo", mBuffer, seq, uin) + qqSecurityHeadBuilder.addField(24, UnknownFieldSet.Field.newBuilder().also { + it.addLengthDelimited( + ByteString.copyFrom( + ProtoBuf.encodeToByteArray(QQSsoSecureInfo( + secSig = sign.sign, + extra = sign.extra, + deviceToken = sign.token + )))) + }.build()) + param.args[14] = qqSecurityHeadBuilder.build().toByteArray() + } + + param.args[bufferIndex] = BytePacketBuilder().also { + it.writeInt(mBuffer.size + 4) + it.writeFully(mBuffer) + }.build().readBytes() + } + + private fun replaceField4_11_inPacket(packetWithLenPrefix: ByteArray, new11Value: String): ByteArray { + if (packetWithLenPrefix.size <= 4) return packetWithLenPrefix + + val body = packetWithLenPrefix.copyOfRange(4, packetWithLenPrefix.size) + val root = UnknownFieldSet.parseFrom(body) + + val rootBuilder = UnknownFieldSet.newBuilder(root) + + val innerBuilder = if (root.hasField(4)) { + val existingInner = root.getUnknownObject(4) + UnknownFieldSet.newBuilder(existingInner) + } else { + UnknownFieldSet.newBuilder() + } + + innerBuilder.clearField(11) + innerBuilder.addField(11, + UnknownFieldSet.Field.newBuilder().also { f -> + f.addLengthDelimited(ByteString.copyFromUtf8(new11Value)) + }.build() + ) + + rootBuilder.clearField(4) + rootBuilder.addField(4, + UnknownFieldSet.Field.newBuilder().also { f -> + f.addLengthDelimited(innerBuilder.build().toByteString()) + }.build() + ) + + val newBody = rootBuilder.build().toByteArray() + return newBody + } + + override fun targetProcess(): Int { + return SyncUtils.PROC_MSF + } +} \ No newline at end of file