From e2f27cb36a9e6e78ffaaca061a300517f7af4c0a Mon Sep 17 00:00:00 2001 From: fuqiuluo Date: Tue, 16 Jul 2024 20:56:09 +0800 Subject: [PATCH] fix #339 --- app/build.gradle.kts | 2 +- app/src/main/cpp/shamrock.cpp | 3 +- .../moe/fuqiuluo/shamrock/MainActivity.kt | 15 +- .../ui/service/handlers/ModuleHandler.kt | 1 + .../internal/MultifunctionalProvider.kt | 2 +- .../moe/fuqiuluo/shamrock/ui/tools/tabs.kt | 10 +- .../oidb/cmd0x11c5/NtV2RichMediaReq.kt | 3 +- .../oidb/cmd0x11c5/NtV2RichMediaRsp.kt | 4 +- .../data/troop/TroopMemberNickInfo.java | 54 ++++ .../mobileqq/msf/sdk/MsfMessagePair.java | 18 ++ .../ITroopMemberListRepoApi.java | 75 +++++ .../fuqiuluo/qqinterface/servlet/BaseSvc.kt | 203 ------------- .../fuqiuluo/qqinterface/servlet/CardSvc.kt | 12 +- .../fuqiuluo/qqinterface/servlet/ChatSvc.kt | 3 +- .../fuqiuluo/qqinterface/servlet/FileSvc.kt | 45 ++- .../fuqiuluo/qqinterface/servlet/FriendSvc.kt | 7 +- .../fuqiuluo/qqinterface/servlet/GProSvc.kt | 25 +- .../fuqiuluo/qqinterface/servlet/GroupSvc.kt | 157 ++++++---- .../fuqiuluo/qqinterface/servlet/LbsSvc.kt | 11 +- .../fuqiuluo/qqinterface/servlet/MsgSvc.kt | 16 +- .../fuqiuluo/qqinterface/servlet/PacketSvc.kt | 3 +- .../fuqiuluo/qqinterface/servlet/QFavSvc.kt | 7 +- .../fuqiuluo/qqinterface/servlet/QSafeSvc.kt | 6 +- .../fuqiuluo/qqinterface/servlet/TicketSvc.kt | 21 +- .../qqinterface/servlet/VisitorSvc.kt | 4 +- .../qqinterface/servlet/ark/ArkMsgSvc.kt | 4 +- .../qqinterface/servlet/ark/LightAppSvc.kt | 6 +- .../msg/converter/NtMsgElementConverter.kt | 42 +-- .../servlet/msg/maker/ElemMaker.kt | 29 +- .../servlet/msg/maker/NtMsgElementMaker.kt | 10 +- .../servlet/transfile/NtV2RichMediaSvc.kt | 60 +++- .../servlet/transfile/RichProtoSvc.kt | 86 +++++- .../shamrock/helper/LocalCacheHelper.kt | 4 +- .../moe/fuqiuluo/shamrock/helper/LogCenter.kt | 12 +- .../fuqiuluo/shamrock/helper/MessageHelper.kt | 62 ++++ .../fuqiuluo/shamrock/remote/HTTPServer.kt | 16 +- .../remote/action/handlers/OcrImage.kt | 4 +- .../remote/action/handlers/SendMsgByResid.kt | 5 +- .../shamrock/remote/api/GenerateQSign.kt | 81 +---- .../shamrock/remote/api/ProtocolAction.kt | 6 +- .../shamrock/remote/api/TestAction.kt | 2 - .../fuqiuluo/shamrock/remote/plugin/Auth.kt | 1 - .../shamrock/remote/service/PacketReceiver.kt | 69 ----- .../service/api/GlobalEventTransmitter.kt | 4 +- .../remote/service/config/ShamrockConfig.kt | 277 +---------------- .../remote/service/config/ShamrockConfigV0.kt | 285 ++++++++++++++++++ .../remote/service/listener/AioListener.kt | 8 +- .../service/listener/PrimitiveListener.kt | 21 +- .../moe/fuqiuluo/shamrock/tools/AndroidX.kt | 16 + .../java/moe/fuqiuluo/shamrock/tools/Trpc.kt | 59 ++++ .../fuqiuluo/shamrock/utils/PlatformUtils.kt | 3 +- .../fuqiuluo/shamrock/xposed/XposedEntry.kt | 12 +- .../shamrock/xposed/helper/AppTalker.kt | 20 +- .../shamrock/xposed/helper/MSFHandler.kt | 74 +++++ .../xposed/helper/NTServiceFetcher.kt | 7 +- .../shamrock/xposed/helper/PacketHandler.kt | 59 ---- .../shamrock/xposed/helper/QQInterfaces.kt | 145 +++++++++ .../xposed/helper/internal/DataRequester.kt | 113 ------- .../xposed/helper/internal/DynamicReceiver.kt | 78 ----- .../shamrock/xposed/hooks/DataReceiver.kt | 67 ---- .../shamrock/xposed/hooks/DynamicBroadcast.kt | 58 ++++ .../shamrock/xposed/hooks/FixLibraryLoad.kt | 2 +- .../shamrock/xposed/hooks/HookWrapperCodec.kt | 139 --------- .../xposed/hooks/InitRemoteService.kt | 2 - .../shamrock/xposed/hooks/IpcService.kt | 44 --- .../shamrock/xposed/hooks/PatchMsfCore.kt | 44 +++ .../shamrock/xposed/hooks/PullConfig.kt | 85 +----- .../xposed/hooks/interacts/IInteract.kt | 7 + .../shamrock/xposed/hooks/interacts/Init.kt | 25 ++ .../xposed/hooks/interacts/SwitchStatus.kt | 21 ++ .../xposed/hooks/interacts/UpdateConfig.kt | 18 ++ .../shamrock/xposed/ipc/ShamrockIpc.kt | 45 --- .../shamrock/xposed/loader/NativeLoader.kt | 12 +- 73 files changed, 1433 insertions(+), 1523 deletions(-) create mode 100644 qqinterface/src/main/java/com/tencent/mobileqq/data/troop/TroopMemberNickInfo.java create mode 100644 qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java create mode 100644 qqinterface/src/main/java/com/tencent/qqnt/troopmemberlist/ITroopMemberListRepoApi.java delete mode 100644 xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfigV0.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/tools/AndroidX.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/MSFHandler.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/QQInterfaces.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DynamicBroadcast.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PatchMsfCore.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/IInteract.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/Init.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/SwitchStatus.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/UpdateConfig.kt delete mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 24c3666..a44a164 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { minSdk = 27 targetSdk = 34 versionCode = getVersionCode() - versionName = "1.0.9" + ".r${getGitCommitCount()}." + getVersionName() + versionName = "1.1.1.onebot" + ".r${getGitCommitCount()}." + getVersionName() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/cpp/shamrock.cpp b/app/src/main/cpp/shamrock.cpp index 9e90b5c..5de0ee4 100644 --- a/app/src/main/cpp/shamrock.cpp +++ b/app/src/main/cpp/shamrock.cpp @@ -12,7 +12,8 @@ extern "C" JNIEXPORT jstring JNICALL -Java_moe_fuqiuluo_shamrock_xposed_hooks_PullConfig_testNativeLibrary(JNIEnv *env, jobject thiz) { +Java_moe_fuqiuluo_shamrock_xposed_hooks_PullConfig_00024Companion_testNativeLibrary(JNIEnv *env, + jobject thiz) { return env->NewStringUTF("加载Shamrock库成功~"); } diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt b/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt index 5b0b155..0750d06 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt @@ -52,7 +52,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource @@ -64,6 +63,7 @@ import androidx.compose.ui.unit.sp import androidx.core.view.WindowCompat import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import moe.fuqiuluo.shamrock.ui.app.AppRuntime import moe.fuqiuluo.shamrock.ui.app.Logger @@ -79,7 +79,7 @@ import moe.fuqiuluo.shamrock.ui.theme.RANDOM_SUB_TITLE import moe.fuqiuluo.shamrock.ui.theme.RANDOM_TITLE import moe.fuqiuluo.shamrock.ui.theme.ShamrockTheme import moe.fuqiuluo.shamrock.ui.tools.NoIndication -import moe.fuqiuluo.shamrock.ui.tools.ShamrockTab +import moe.fuqiuluo.shamrock.ui.tools.ShamrockTabV2 import moe.fuqiuluo.shamrock.ui.tools.getShamrockVersion class MainActivity : ComponentActivity() { @@ -87,6 +87,15 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { + LaunchedEffect(Unit) { + while (true) { + delay(5_000) // Delay in milliseconds + broadcastToModule { + putExtra("__cmd", "switch_status") + } + } + } + CompositionLocalProvider( LocalIndication provides NoIndication ) { @@ -336,7 +345,7 @@ private fun AnimatedTab( } } - ShamrockTab( + ShamrockTabV2( selected = curSelected, onClick = { scope.launch { diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt index 547b074..381d185 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt @@ -30,6 +30,7 @@ abstract class ModuleHandler { } } putExtra("__hash", callbackId) + putExtra("__cmd", cmd) } } } \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt index bb1aba1..a11f26e 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt @@ -58,7 +58,7 @@ class MultifunctionalProvider: ContentProvider() { inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) { val intent = Intent() - intent.action = "moe.fuqiuluo.xqbot.dynamic" + intent.action = "moe.fuqiuluo.onebot.dynamic" intent.intentBuilder() sendBroadcast(intent) } \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt index ec54545..87f6fce 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt @@ -242,13 +242,13 @@ private fun Placeable.PlacementScope.placeTextAndIcon( } @Composable -fun ShamrockTab( +fun ShamrockTabV2( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - text: @Composable (() -> Unit)? = null, - icon: @Composable (() -> Unit)? = null, + text: (@Composable (() -> Unit))? = null, + icon: (@Composable (() -> Unit))? = null, selectedContentColor: Color = GlobalColor.TabSelected, unselectedContentColor: Color = selectedContentColor, indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor), @@ -262,7 +262,7 @@ fun ShamrockTab( ProvideTextStyle(style, content = text) } } - ShamrockTab( + ShamrockTabV2( selected, onClick, modifier, @@ -277,7 +277,7 @@ fun ShamrockTab( } @Composable -fun ShamrockTab( +fun ShamrockTabV2( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, diff --git a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt index 569c922..df01afe 100644 --- a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt +++ b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt @@ -94,7 +94,8 @@ data class DeleteReq( @Serializable data class DownloadRkeyReq( - @ProtoNumber(1) val types: List + @ProtoNumber(1) val types: List, + @ProtoNumber(2) val downloadType: Int ) @Serializable diff --git a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt index bf5fd13..b938bbe 100644 --- a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt +++ b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt @@ -52,11 +52,11 @@ data class DownloadRkeyRsp( @Serializable data class RKeyInfo( - @ProtoNumber(1) val rkey: String?, + @ProtoNumber(1) val rkey: String, @ProtoNumber(2) val rkeyTtlSec: ULong?, @ProtoNumber(3) val storeId: UInt = 0u, @ProtoNumber(4) val rkeyCreateTime: UInt?, - @ProtoNumber(4) val type: UInt?, + @ProtoNumber(4) val type: UInt, ) @Serializable diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/data/troop/TroopMemberNickInfo.java b/qqinterface/src/main/java/com/tencent/mobileqq/data/troop/TroopMemberNickInfo.java new file mode 100644 index 0000000..7795483 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/data/troop/TroopMemberNickInfo.java @@ -0,0 +1,54 @@ +package com.tencent.mobileqq.data.troop; + +import org.jetbrains.annotations.NotNull; + +public class TroopMemberNickInfo { + @NotNull + public final String getAutoRemark() { + return null; + } + + @NotNull + public final String getColorNick() { + return null; + } + + public final int getColorNickId() { + return 0; + } + + @NotNull + public final String getFriendNick() { + return null; + } + + @NotNull + public final String getHBShowName() { + return null; + } + + @NotNull + public final String getRemarkFromFriend() { + return null; + } + + @NotNull + public final String getRemarkFromFriendV2() { + return null; + } + + @NotNull + public final String getShowName() { + return null; + } + + @NotNull + public final String getTroopNick() { + return null; + } + + @NotNull + public final String getTroopUin() { + return null; + } +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java b/qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java new file mode 100644 index 0000000..e6c82db --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java @@ -0,0 +1,18 @@ +package com.tencent.mobileqq.msf.sdk; + +import com.tencent.qphone.base.remote.FromServiceMsg; +import com.tencent.qphone.base.remote.ToServiceMsg; + +public class MsfMessagePair { + public FromServiceMsg fromServiceMsg; + public String sendProcess; + public ToServiceMsg toServiceMsg; + + public MsfMessagePair(String str, ToServiceMsg toServiceMsg, FromServiceMsg fromServiceMsg) { + + } + + public MsfMessagePair(ToServiceMsg toServiceMsg, FromServiceMsg fromServiceMsg) { + + } +} \ No newline at end of file diff --git a/qqinterface/src/main/java/com/tencent/qqnt/troopmemberlist/ITroopMemberListRepoApi.java b/qqinterface/src/main/java/com/tencent/qqnt/troopmemberlist/ITroopMemberListRepoApi.java new file mode 100644 index 0000000..7c83122 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/qqnt/troopmemberlist/ITroopMemberListRepoApi.java @@ -0,0 +1,75 @@ +package com.tencent.qqnt.troopmemberlist; + +import androidx.lifecycle.LifecycleOwner; + +import com.tencent.mobileqq.data.troop.TroopMemberInfo; +import com.tencent.mobileqq.data.troop.TroopMemberNickInfo; +import com.tencent.mobileqq.qroute.QRouteApi; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +import kotlin.Deprecated; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; + +public interface ITroopMemberListRepoApi extends QRouteApi { + //void fetchGagTroopMemberInfo(@Nullable String str, @Nullable LifecycleOwner lifecycleOwner, boolean z, @NotNull String str2, @Nullable f fVar); + + //void fetchTroopMemberInfo(@Nullable String str, @Nullable String str2, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3, @Nullable g gVar); + + //void fetchTroopMemberInfoWithExtInfo(@Nullable String str, @Nullable String str2, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3, @Nullable g gVar); + + //void fetchTroopMemberList(@Nullable String str, @Nullable LifecycleOwner lifecycleOwner, boolean z, @NotNull String str2, @Nullable f fVar); + + //void fetchTroopMemberListWithExtInfo(@Nullable String str, @Nullable LifecycleOwner lifecycleOwner, boolean z, @NotNull String str2, @Nullable f fVar); + + + + void fetchTroopMemberName(@Nullable String str, @Nullable String str2, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3, @Nullable Function1 cb); + + void fetchTroopMemberName(@Nullable String str, @Nullable String str2, @NotNull String str3, @Nullable Function1 cb); + + + + void fetchTroopMemberUid(@Nullable String str, @NotNull Function2 function2); + + void fetchTroopMemberUid(@NotNull List list, @NotNull Function2, Unit> function2); + + void fetchTroopMemberUin(@Nullable String str, @NotNull Function2 function2); + + void fetchTroopMemberUin(@NotNull List list, @NotNull Function2, Unit> function2); + + //void fetchTroopMemberUinListInfo(@Nullable String str, @Nullable List list, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str2, @Nullable f fVar); + + //void fetchTroopMemberUinListInfoWithExtInfo(@Nullable String str, @Nullable List list, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str2, @Nullable f fVar); + + //@Nullable + //TroopMemberInfo getTroopMemberFromCacheOrFetchAsync(@Nullable String str, @Nullable String str2, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3, @Nullable g gVar); + + int getTroopMemberInfoDBVersion(); + + @Deprecated(message = "兼容旧逻辑,过渡用,新逻辑不要使用") + @Nullable + TroopMemberInfo getTroopMemberInfoSync(@Nullable String groupId, @Nullable String userId, @Nullable LifecycleOwner lifecycleOwner, @NotNull String from); + + //@Deprecated(message = "兼容旧逻辑,过渡用,新逻辑不要使用") + //@Nullable + //TroopMemberInfo getTroopMemberInfoSync(@Nullable String str, @Nullable String str2, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3, long j); + + //@Deprecated(message = "兼容旧逻辑,过渡用,新逻辑不要使用") + //@Nullable + //TroopMemberInfo getTroopMemberWithExtFromCacheOrFetchAsync(@Nullable String str, @Nullable String str2, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3, @Nullable g gVar); + + //@Deprecated(message = "兼容旧逻辑,过渡用,新逻辑不要使用") + //@Nullable + //TroopMemberInfo getTroopMemberWithExtInfoSync(@Nullable String str, @Nullable String str2, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str3); + + boolean isTroopMemberInfoDBInited(@NotNull String str); + + //void preLoadTroopMemberUinListInfo(@Nullable String str, @Nullable List list, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str2, @Nullable f fVar); +} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt deleted file mode 100644 index 74f2ef7..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt +++ /dev/null @@ -1,203 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.qqinterface.servlet - -import android.os.Bundle -import com.tencent.mobileqq.app.QQAppInterface -import com.tencent.mobileqq.msf.core.MsfCore -import com.tencent.mobileqq.msf.service.MsfService -import com.tencent.mobileqq.pb.ByteStringMicro -import com.tencent.qphone.base.remote.ToServiceMsg -import com.tencent.qqnt.kernel.api.IKernelService -import io.ktor.utils.io.core.BytePacketBuilder -import io.ktor.utils.io.core.readBytes -import io.ktor.utils.io.core.writeFully -import io.ktor.utils.io.core.writeInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.encodeToByteArray - -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import protobuf.oidb.TrpcOidb -import mqq.app.MobileQQ -import protobuf.auto.toByteArray -import tencent.im.oidb.oidb_sso -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.resume - -internal abstract class BaseSvc { - companion object Default: CoroutineScope { - val currentUin: String - get() = app.currentAccountUin - - val app: QQAppInterface - get() = AppRuntimeFetcher.appRuntime as QQAppInterface - - fun createToServiceMsg(cmd: String): ToServiceMsg { - return ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd) - } - - suspend fun sendOidbAW(cmd: String, cmdId: Int, serviceId: Int, data: ByteArray, trpc: Boolean = false, timeout: Long = 5000L): ByteArray? { - val seq = MsfService.getCore().nextSeq - val buffer = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { continuation -> - launch(Dispatchers.Default) { - DynamicReceiver.register(IPCRequest(cmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - continuation.resume(buffer) - }) - } - if (trpc) sendTrpcOidb(cmd, cmdId, serviceId, data, seq) - else sendOidb(cmd, cmdId, serviceId, data, seq) - } - }.also { - if (it == null) - DynamicReceiver.unregister(seq) - }?.copyOf() - try { - if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) { - val builder = BytePacketBuilder() - val deBuffer = DeflateTools.uncompress(buffer.slice(4)) - builder.writeInt(deBuffer.size) - builder.writeFully(deBuffer) - return builder.build().readBytes() - } - } catch (_: Exception) { } - return buffer - } - - suspend fun sendBufferAW(cmd: String, isPb: Boolean, data: ByteArray, timeout: Long = 5000L): ByteArray? { - val seq = MsfService.getCore().nextSeq - val buffer = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { continuation -> - launch(Dispatchers.Default) { - DynamicReceiver.register(IPCRequest(cmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - continuation.resume(buffer) - }) - sendBuffer(cmd, isPb, data, seq) - } - } - }.also { - if (it == null) - DynamicReceiver.unregister(seq) - }?.copyOf() - try { - if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) { - val builder = BytePacketBuilder() - val deBuffer = DeflateTools.uncompress(buffer.slice(4)) - builder.writeInt(deBuffer.size) - builder.writeFully(deBuffer) - return builder.build().readBytes() - } - } catch (_: Exception) { } - return buffer - } - - fun sendOidb(cmd: String, cmdId: Int, serviceId: Int, buffer: ByteArray, seq: Int = -1, trpc: Boolean = false) { - if (trpc) { - sendTrpcOidb(cmd, cmdId, serviceId, buffer, seq) - return - } - val to = createToServiceMsg(cmd) - val oidb = oidb_sso.OIDBSSOPkg() - oidb.uint32_command.set(cmdId) - oidb.uint32_service_type.set(serviceId) - oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(buffer)) - oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext())) - to.putWupBuffer(oidb.toByteArray()) - to.addAttribute("req_pb_protocol_flag", true) - if (seq != -1) { - to.addAttribute("shamrock_seq", seq) - } - app.sendToService(to) - } - - fun sendTrpcOidb(cmd: String, cmdId: Int, serviceId: Int, buffer: ByteArray, seq: Int = -1) { - val to = createToServiceMsg(cmd) - - val oidb = TrpcOidb( - cmd = cmdId, - service = serviceId, - buffer = buffer, - flag = 1 - ) - to.putWupBuffer(oidb.toByteArray()) - - to.addAttribute("req_pb_protocol_flag", true) - if (seq != -1) { - to.addAttribute("shamrock_seq", seq) - } - app.sendToService(to) - } - - fun sendBuffer(cmd: String, isPb: Boolean, buffer: ByteArray, seq: Int = MsfService.getCore().nextSeq) { - val toServiceMsg = ToServiceMsg("mobileqq.service", app.currentUin, cmd) - toServiceMsg.putWupBuffer(buffer) - toServiceMsg.addAttribute("req_pb_protocol_flag", isPb) - toServiceMsg.addAttribute("shamrock_seq", seq) - app.sendToService(toServiceMsg) - } - - @OptIn(ExperimentalCoroutinesApi::class) - override val coroutineContext: CoroutineContext by lazy { - Dispatchers.IO.limitedParallelism(12) - } - } - - protected fun send(toServiceMsg: ToServiceMsg) { - app.sendToService(toServiceMsg) - } - - protected suspend fun sendAW(toServiceMsg: ToServiceMsg, timeout: Long = 5000L): ByteArray? { - val seq = MsfService.getCore().nextSeq - val buffer = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { continuation -> - launch(Dispatchers.Default) { - DynamicReceiver.register(IPCRequest(toServiceMsg.serviceCmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - continuation.resume(buffer) - }) - toServiceMsg.addAttribute("shamrock_seq", seq) - send(toServiceMsg) - } - } - }.also { - if (it == null) DynamicReceiver.unregister(seq) - }?.copyOf() - try { - if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) { - val builder = BytePacketBuilder() - val deBuffer = DeflateTools.uncompress(buffer.slice(4)) - builder.writeInt(deBuffer.size) - builder.writeFully(deBuffer) - return builder.build().readBytes() - } - } catch (_: Exception) { } - return buffer - } - - protected fun sendExtra(cmd: String, builder: (Bundle) -> Unit) { - val toServiceMsg = createToServiceMsg(cmd) - builder(toServiceMsg.extraData) - app.sendToService(toServiceMsg) - } - - protected fun sendPb(cmd: String, buffer: ByteArray, seq: Int) { - val toServiceMsg = createToServiceMsg(cmd) - toServiceMsg.putWupBuffer(buffer) - toServiceMsg.addAttribute("req_pb_protocol_flag", true) - toServiceMsg.addAttribute("shamrock_seq", seq) - app.sendToService(toServiceMsg) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt index a113b74..9f4a19b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt @@ -21,12 +21,14 @@ import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.tools.decodeToOidb +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.Packet import tencent.im.oidb.cmd0x11b2.oidb_0x11b2 import tencent.im.oidb.oidb_sso import kotlin.coroutines.resume -internal object CardSvc: BaseSvc() { +internal object CardSvc: QQInterfaces() { private val GetModelShowLock by lazy { Mutex() } @@ -46,7 +48,8 @@ internal object CardSvc: BaseSvc() { val resp = sendBufferAW("VipCustom.GetCustomOnlineStatus", false, uniPacket.encode()) ?: error("unable to fetch contact model_show") - Packet.decodePacket(resp, "rsp", GetCustomOnlineStatusRsp()).sBuffer + val buffer = resp.wupBuffer + Packet.decodePacket(buffer, "rsp", GetCustomOnlineStatusRsp()).sBuffer } } @@ -79,10 +82,9 @@ internal object CardSvc: BaseSvc() { reqBody.uin.set(peerId) reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$peerId") - val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray()) + val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray()) ?: error("unable to fetch contact ark_json_text") - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = fromServiceMsg.decodeToOidb() val rsp = oidb_0x11b2.BusinessCardV3Rsp() rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray()) return rsp.signed_ark_msg.get() diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt index b839ef9..5cc090a 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt @@ -1,11 +1,12 @@ package moe.fuqiuluo.qqinterface.servlet import kotlinx.serialization.encodeToByteArray +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import protobuf.auto.toByteArray import protobuf.oidb.cmd0x9082.Oidb0x9082 -internal object ChatSvc: BaseSvc() { +internal object ChatSvc: QQInterfaces() { fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) { val serviceId = if (isSet) 1 else 2 sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, Oidb0x9082( diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt index dfebd80..c87f62a 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt @@ -6,9 +6,11 @@ import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY +import moe.fuqiuluo.shamrock.tools.decodeToOidb import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.utils.DeflateTools +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.symbols.decodeProtobuf import protobuf.oidb.cmd0x6d7.CreateFolderReq import protobuf.oidb.cmd0x6d7.DeleteFolderReq @@ -21,8 +23,10 @@ import tencent.im.oidb.cmd0x6d8.oidb_0x6d8 import tencent.im.oidb.oidb_sso import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo import protobuf.auto.toByteArray +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds -internal object FileSvc: BaseSvc() { +internal object FileSvc: QQInterfaces() { suspend fun createFileFolder(groupId: Long, folderName: String, parentFolderId: String = "/"): Result { val data = Oidb0x6d7ReqBody( createFolder = CreateFolderReq( @@ -32,10 +36,9 @@ internal object FileSvc: BaseSvc() { folderName = folderName ) ).toByteArray() - val resultBuffer = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data) + val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data) ?: return Result.failure(Exception("unable to fetch result")) - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(resultBuffer.slice(4)) + val oidbPkg = fromServiceMsg.decodeToOidb() val rsp = oidbPkg.bytes_bodybuffer.get() .toByteArray() .decodeProtobuf() @@ -46,21 +49,20 @@ internal object FileSvc: BaseSvc() { } suspend fun deleteGroupFolder(groupId: Long, folderUid: String): Boolean { - val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( + val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( deleteFolder = DeleteFolderReq( groupCode = groupId.toULong(), appId = 3u, folderId = folderUid ) ).toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(buffer.slice(4)) + val oidbPkg = fromServiceMsg.decodeToOidb() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf() return rsp.deleteFolder?.retCode == 0 } suspend fun moveGroupFolder(groupId: Long, folderUid: String, newParentFolderUid: String): Boolean { - val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, Oidb0x6d7ReqBody( + val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, Oidb0x6d7ReqBody( moveFolder = MoveFolderReq( groupCode = groupId.toULong(), appId = 3u, @@ -68,14 +70,13 @@ internal object FileSvc: BaseSvc() { parentFolderId = "/" ) ).toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(buffer.slice(4)) + val oidbPkg = fromServiceMsg.decodeToOidb() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf() return rsp.moveFolder?.retCode == 0 } suspend fun renameFolder(groupId: Long, folderUid: String, name: String): Boolean { - val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( + val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( renameFolder = RenameFolderReq( groupCode = groupId.toULong(), appId = 3u, @@ -83,8 +84,7 @@ internal object FileSvc: BaseSvc() { folderName = name ) ).toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(buffer.slice(4)) + val oidbPkg = fromServiceMsg.decodeToOidb() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf() return rsp.renameFolder?.retCode == 0 } @@ -101,8 +101,7 @@ internal object FileSvc: BaseSvc() { } val result = sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(result.slice(4)) + val oidbPkg = result.decodeToOidb() val rsp = oidb_0x6d6.RspBody().apply { mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray()) } @@ -120,8 +119,8 @@ internal object FileSvc: BaseSvc() { val fileCnt: Int val limitCnt: Int if (rspGetFileCntBuffer != null) { - oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg() - .mergeFrom(rspGetFileCntBuffer.slice(4)) + oidb_0x6d8.RspBody().mergeFrom( + rspGetFileCntBuffer.decodeToOidb() .bytes_bodybuffer.get() .toByteArray() ).group_file_cnt_rsp.apply { @@ -141,8 +140,8 @@ internal object FileSvc: BaseSvc() { val totalSpace: Long val usedSpace: Long if (rspGetFileSpaceBuffer != null) { - oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg() - .mergeFrom(rspGetFileSpaceBuffer.slice(4)) + oidb_0x6d8.RspBody().mergeFrom( + rspGetFileSpaceBuffer.decodeToOidb() .bytes_bodybuffer.get() .toByteArray()).group_space_rsp.apply { totalSpace = uint64_total_space.get() @@ -187,15 +186,13 @@ internal object FileSvc: BaseSvc() { uint32_show_onlinedoc_folder.set(0) }) - }.toByteArray(), timeout = 15_000L) + }.toByteArray(), timeout = 15.seconds) return kotlin.runCatching { val files = arrayListOf() val dirs = arrayListOf() if (rspGetFileListBuffer != null) { - val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(rspGetFileListBuffer.slice(4).let { - if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it - }) + val oidb = rspGetFileListBuffer.decodeToOidb() oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray()) .file_list_info_rsp.apply { @@ -242,7 +239,7 @@ internal object FileSvc: BaseSvc() { GroupFileList(files, dirs) }.onFailure { - LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer.toHexString()}", Level.ERROR) + LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer?.wupBuffer?.toHexString()}", Level.ERROR) } } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt index 953239b..b79781c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt @@ -13,13 +13,15 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import moe.fuqiuluo.shamrock.tools.decodeToObject import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.AppRuntime import tencent.mobileim.structmsg.structmsg import kotlin.coroutines.resume -internal object FriendSvc: BaseSvc() { +internal object FriendSvc: QQInterfaces() { suspend fun getFriendList(refresh: Boolean): Result> { val runtime = AppRuntimeFetcher.appRuntime @@ -91,8 +93,7 @@ internal object FriendSvc: BaseSvc() { ArrayList() } else { try { - val msg = structmsg.RspSystemMsgNew() - msg.mergeFrom(respBuffer.slice(4)) + val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew()) return msg.friendmsgs.get() } catch (err: Throwable) { requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt index daea9dc..8f21e49 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt @@ -19,9 +19,12 @@ import moe.fuqiuluo.qqinterface.servlet.structures.SlowModeInfo import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY +import moe.fuqiuluo.shamrock.tools.decodeToObject +import moe.fuqiuluo.shamrock.tools.decodeToOidb import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.symbols.decodeProtobuf import protobuf.auto.toByteArray import protobuf.guild.GetGuildFeedsReq @@ -44,7 +47,7 @@ import protobuf.qweb.QWebRsp import tencent.im.oidb.oidb_sso import kotlin.coroutines.resume -internal object GProSvc: BaseSvc() { +internal object GProSvc: QQInterfaces() { fun getSelfTinyId(): ULong { val service = app.getRuntimeService(IGPSService::class.java, "all") return service.selfTinyId.toULong() @@ -57,12 +60,8 @@ internal object GProSvc: BaseSvc() { u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u) ), guildInfo = Oidb0xf57GuildInfo(guildId = guildId) - ).toByteArray()) - val body = oidb_sso.OIDBSSOPkg() - if (respBuffer == null) { - return Result.failure(Exception("unable to send packet")) - } - body.mergeFrom(respBuffer.slice(4)) + ).toByteArray()) ?: return Result.failure(Exception("unable to send packet")) + val body = respBuffer.decodeToOidb() return runCatching { body.bytes_bodybuffer.get() .toByteArray() @@ -71,7 +70,7 @@ internal object GProSvc: BaseSvc() { } suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result { - val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq( + val fromServiceMsg = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq( seq = 10, qua = PlatformUtils.getQUA(), deviceInfo = DEFAULT_DEVICE_INFO, @@ -92,7 +91,7 @@ internal object GProSvc: BaseSvc() { QWebExtInfo("tiny_id", getSelfTinyId().toString()), ) ).toByteArray()) ?: return Result.failure(Exception("unable to send packet")) - val webRsp = buffer.slice(4).decodeProtobuf() + val webRsp = fromServiceMsg.decodeToObject() if(webRsp.buffer == null) return Result.failure(Exception("server error")) val wupBuffer = webRsp.buffer!! val feeds = wupBuffer.decodeProtobuf() @@ -188,12 +187,8 @@ internal object GProSvc: BaseSvc() { memberId = 0uL, tinyId = memberTinyId, guildId = guildId - ).toByteArray()) - val body = oidb_sso.OIDBSSOPkg() - if (respBuffer == null) { - return Result.failure(Exception("unable to send packet")) - } - body.mergeFrom(respBuffer.slice(4)) + ).toByteArray()) ?: return Result.failure(Exception("unable to send packet")) + val body = respBuffer.decodeToOidb() return runCatching { body.bytes_bodybuffer.get().toByteArray().decodeProtobuf().userInfo!! } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt index e7df6ba..6109f01 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt @@ -10,13 +10,16 @@ import com.tencent.mobileqq.app.BusinessHandlerFactory import com.tencent.mobileqq.app.QQAppInterface import com.tencent.mobileqq.data.troop.TroopInfo import com.tencent.mobileqq.data.troop.TroopMemberInfo +import com.tencent.mobileqq.data.troop.TroopMemberNickInfo import com.tencent.mobileqq.pb.ByteStringMicro +import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.troop.api.ITroopInfoService import com.tencent.mobileqq.troop.api.ITroopMemberInfoService import com.tencent.protofile.join_group_link.join_group_link import com.tencent.qphone.base.remote.ToServiceMsg import com.tencent.qqnt.kernel.nativeinterface.MemberInfo import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.troopmemberlist.ITroopMemberListRepoApi import friendlist.stUinInfo import io.ktor.client.call.body import io.ktor.client.request.forms.MultiPartFormDataContent @@ -66,13 +69,18 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject import moe.fuqiuluo.shamrock.tools.asLong import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.asStringOrNull +import moe.fuqiuluo.shamrock.tools.decodeToObject +import moe.fuqiuluo.shamrock.tools.decodeToOidb import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.putBuf32Long import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils +import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_65_VER +import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.MobileQQ import protobuf.auto.toByteArray import protobuf.oidb.cmd0xf16.Oidb0xf16 @@ -94,8 +102,9 @@ import java.lang.reflect.Method import java.lang.reflect.Modifier import java.nio.ByteBuffer import kotlin.coroutines.resume +import kotlin.time.Duration.Companion.seconds -internal object GroupSvc: BaseSvc() { +internal object GroupSvc: QQInterfaces() { private const val GET_MEMBER_ROLE_BY_NT = false private val RefreshTroopMemberInfoLock by lazy { @@ -112,15 +121,14 @@ internal object GroupSvc: BaseSvc() { private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method suspend fun getGroupRemainAtAllRemain (groupId: Long): Result { - val buffer = sendOidbAW("OidbSvcTrpcTcp.0x8a7_0", 2215, 0, cmd0x8a7.ReqBody().apply { + val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x8a7_0", 2215, 0, cmd0x8a7.ReqBody().apply { uint32_sub_cmd.set(1) uint32_limit_interval_type_for_uin.set(2) uint32_limit_interval_type_for_group.set(1) uint64_uin.set(getLongUin()) uint64_group_code.set(groupId) }.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout")) - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = fromServiceMsg.decodeToOidb() if(body.uint32_result.get() != 0) { return Result.failure(RuntimeException(body.str_error_msg.get())) } @@ -133,7 +141,7 @@ internal object GroupSvc: BaseSvc() { )) } suspend fun getProhibitedMemberList(groupId: Long): Result> { - val buffer = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply { + val fromServiceMsg = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply { uint64_group_code.set(groupId) uint64_start_uin.set(0) uint32_identify_flag.set(6) @@ -142,8 +150,7 @@ internal object GroupSvc: BaseSvc() { uint32_shutup_timestap.set(0) }) }.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout")) - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = fromServiceMsg.decodeToOidb() if(body.uint32_result.get() != 0) { return Result.failure(RuntimeException(body.str_error_msg.get())) } @@ -241,10 +248,10 @@ internal object GroupSvc: BaseSvc() { toServiceMsg.extraData.putLong("troop_code", groupId) toServiceMsg.extraData.putBoolean("is_admin", false) toServiceMsg.extraData.putInt("from", 0) - val buffer = sendAW(toServiceMsg) + val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时")) val uniPacket = UniPacket(true) uniPacket.encodeName = "utf-8" - uniPacket.decode(buffer) + uniPacket.decode(fromServiceMsg.wupBuffer) val respBatchProcess = uniPacket.getByClass("RespBatchProcess", RespBatchProcess()) val batchRespInfo = oidb_0x88d.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg() .mergeFrom(respBatchProcess.batch_response_list.first().buffer) @@ -308,7 +315,7 @@ internal object GroupSvc: BaseSvc() { info.dwFlag = 1 createToServiceMsg.extraData.putSerializable("vecUinInfo", arrayListOf(info)) createToServiceMsg.extraData.putLong("dwNewSeq", 0L) - send(createToServiceMsg) + sendToServiceMsg(createToServiceMsg) return true } @@ -324,13 +331,12 @@ internal object GroupSvc: BaseSvc() { } suspend fun setEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair { - val buffer = sendOidbAW("OidbSvc.0xeac_1", 3756, 1, oidb_0xeac.ReqBody().apply { + val fromServiceMsg = sendOidbAW("OidbSvc.0xeac_1", 3756, 1, oidb_0xeac.ReqBody().apply { group_code.set(groupId) msg_seq.set(seq.toInt()) msg_random.set(rand.toInt()) }.toByteArray()) ?: return Pair(false, "unknown error") - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = fromServiceMsg.decodeToOidb() val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) return if (result.wording.has()) { LogCenter.log("设置群精华失败: ${result.wording.get()}") @@ -342,16 +348,12 @@ internal object GroupSvc: BaseSvc() { } suspend fun deleteEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair { - val buffer = sendOidbAW("OidbSvc.0xeac_2", 3756, 2, oidb_0xeac.ReqBody().apply { + val fromServiceMsg = sendOidbAW("OidbSvc.0xeac_2", 3756, 2, oidb_0xeac.ReqBody().apply { group_code.set(groupId) msg_seq.set(seq.toInt()) msg_random.set(rand.toInt()) - }.toByteArray()) - val body = oidb_sso.OIDBSSOPkg() - if (buffer == null) { - return Pair(false, "unknown error") - } - body.mergeFrom(buffer.slice(4)) + }.toByteArray()) ?: return Pair(false, "unknown error") + val body = fromServiceMsg.decodeToOidb() val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) return if (result.wording.has()) { LogCenter.log("移除群精华失败: ${result.wording.get()}") @@ -565,10 +567,9 @@ internal object GroupSvc: BaseSvc() { reqBody.get_ark.set(true) reqBody.type.set(1) reqBody.group_code.set(groupId) - val buffer = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray()) + val fromServiceMsg = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray()) ?: error("unable to fetch contact ark_json_text") - val body = join_group_link.RspBody() - body.mergeFrom(buffer.slice(4)) + val body = fromServiceMsg.decodeToObject(join_group_link.RspBody()) return body.signed_ark.get().toStringUtf8() } @@ -600,8 +601,7 @@ internal object GroupSvc: BaseSvc() { req.uint32_rich_card_name_ver.set(1) val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray()) if (respBuffer != null) { - val rsp = group_member_info.RspBody() - rsp.mergeFrom(respBuffer.slice(4)) + val rsp = respBuffer.decodeToObject(group_member_info.RspBody()) if (rsp.msg_meminfo.str_location.has()) { info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8() } @@ -627,15 +627,49 @@ internal object GroupSvc: BaseSvc() { } } + fun getTroopMemberInfoByUinFromNt( + groupId: Long, + uin: Long + ): Result { + return kotlin.runCatching { + val api = QRoute.api(ITroopMemberListRepoApi::class.java) + api.getTroopMemberInfoSync(groupId.toString(), uin.toString(), null, groupId.toString()) + ?: throw Exception("获取群成员信息失败: NT兼容接口已废弃") + } + } + + suspend fun getTroopMemberInfoByUinV3( + groupId: Long, + uin: Long + ): TroopMemberNickInfo? { + if (PlatformUtils.getQQVersionCode() > QQ_9_0_65_VER) { + val api = QRoute.api(ITroopMemberListRepoApi::class.java) + return withTimeoutOrNull(5.seconds) { + suspendCancellableCoroutine { continuation -> + api.fetchTroopMemberName(groupId.toString(), uin.toString(), null, groupId.toString()) { + continuation.resume(it) + } + } + } + } else { + return null + } + } + suspend fun getTroopMemberInfoByUinV2( groupId: Long, uin: Long, refresh: Boolean = false ): Result { - val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") - var info = service.getTroopMember(groupId.toString(), uin.toString()) - if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) { - info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull() + var info: TroopMemberInfo? = null + if (PlatformUtils.getQQVersionCode() <= QQ_9_0_65_VER) { + val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") + info = service.getTroopMember(groupId.toString(), uin.toString()) + if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) { + info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull() + } + } else { + info = getTroopMemberInfoByUinFromNt(groupId, uin).getOrNull() } if (info == null) { info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let { @@ -645,35 +679,36 @@ internal object GroupSvc: BaseSvc() { } } } - try { - if (info != null && (info.alias == null || info.alias.isBlank())) { - val req = group_member_info.ReqBody() - req.uint64_group_code.set(groupId) - req.uint64_uin.set(uin) - req.bool_new_client.set(true) - req.uint32_client_type.set(1) - req.uint32_rich_card_name_ver.set(1) - val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000) - if (respBuffer != null) { - val rsp = group_member_info.RspBody() - rsp.mergeFrom(respBuffer.slice(4)) - if (rsp.msg_meminfo.str_location.has()) { - info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8() - } - if (rsp.msg_meminfo.uint32_age.has()) { - info.age = rsp.msg_meminfo.uint32_age.get().toByte() - } - if (rsp.msg_meminfo.bytes_group_honor.has()) { - val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray() - val honor = troop_honor.GroupUserCardHonor() - honor.mergeFrom(honorBytes) - info.level = honor.level.get() - // 10315: medal_id not real group level + if (PlatformUtils.getQQVersionCode() <= QQ_9_0_8_VER) { + try { + if (info != null && (info.alias == null || info.alias.isBlank())) { + val req = group_member_info.ReqBody() + req.uint64_group_code.set(groupId) + req.uint64_uin.set(uin) + req.bool_new_client.set(true) + req.uint32_client_type.set(1) + req.uint32_rich_card_name_ver.set(1) + val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2.seconds) + if (respBuffer != null) { + val rsp = respBuffer.decodeToObject(group_member_info.RspBody()) + if (rsp.msg_meminfo.str_location.has()) { + info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8() + } + if (rsp.msg_meminfo.uint32_age.has()) { + info.age = rsp.msg_meminfo.uint32_age.get().toByte() + } + if (rsp.msg_meminfo.bytes_group_honor.has()) { + val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray() + val honor = troop_honor.GroupUserCardHonor() + honor.mergeFrom(honorBytes) + info.level = honor.level.get() + // 10315: medal_id not real group level + } } } + } catch (err: Throwable) { + LogCenter.log(err.stackTraceToString(), Level.WARN) } - } catch (err: Throwable) { - LogCenter.log(err.stackTraceToString(), Level.WARN) } return if (info != null) { Result.success(info) @@ -682,7 +717,7 @@ internal object GroupSvc: BaseSvc() { } } - suspend fun getTroopMemberInfoByUinViaNt( + private suspend fun getTroopMemberInfoByUinViaNt( groupId: Long, qq: Long, timeout: Long = 5000L @@ -711,7 +746,7 @@ internal object GroupSvc: BaseSvc() { return if (info != null) { Result.success(info) } else { - Result.failure(Exception("获取群成员信息失败")) + Result.failure(Exception("[NT]获取群成员信息失败")) } } } @@ -933,7 +968,7 @@ internal object GroupSvc: BaseSvc() { } val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray()) ?: return Result.failure(Exception("操作失败")) - val rsp = structmsg.RspSystemMsgAction().mergeFrom(respBuffer.slice(4)) + val rsp = respBuffer.decodeToObject(structmsg.RspSystemMsgAction()) return if (rsp.head.result.has()) { if (rsp.head.result.get() == 0) { Result.success(rsp.msg_detail.get()) @@ -984,8 +1019,7 @@ internal object GroupSvc: BaseSvc() { ArrayList() } else { try { - val msg = structmsg.RspSystemMsgNew() - msg.mergeFrom(respBuffer.slice(4)) + val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew()) return msg.groupmsgs.get().orEmpty() } catch (err: Throwable) { requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1) @@ -1178,8 +1212,7 @@ internal object GroupSvc: BaseSvc() { return if (buffer == null) { Result.failure(Exception("操作失败")) } else { - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = buffer.decodeToOidb() val rsp = oidb_0xeb7.RspBody() rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray()) val doneInfo = rsp.signInWriteRsp.doneInfo diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt index 18ceb75..349a512 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt @@ -6,10 +6,12 @@ import com.tencent.mobileqq.msf.service.MsfService import com.tencent.proto.lbsshare.LBSShare import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import moe.fuqiuluo.shamrock.helper.IllegalParamsException +import moe.fuqiuluo.shamrock.tools.decodeToObject import moe.fuqiuluo.shamrock.tools.slice +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import kotlin.math.roundToInt -internal object LbsSvc: BaseSvc() { +internal object LbsSvc: QQInterfaces() { suspend fun tryShareLocation(chatType: Int, peerId: Long, lat: Double, lon: Double): Result { val req = LbsSendInfo.SendMessageReq() req.uint64_peer_account.set(peerId) @@ -24,8 +26,8 @@ internal object LbsSvc: BaseSvc() { }.getOrNull()) req.str_lat.set(lat.toString()) req.str_lng.set(lon.toString()) - sendPb("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", req.toByteArray(), MsfService.getCore().nextSeq) - + sendBuffer("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", true, req.toByteArray()) + //sendPb("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", req.toByteArray(), MsfService.getCore().nextSeq) return Result.success(Unit) } @@ -50,8 +52,7 @@ internal object LbsSvc: BaseSvc() { req.imei.set("") val buffer = sendBufferAW("LbsShareSvr.location", true, req.toByteArray()) ?: return Result.failure(Exception("获取位置失败")) - val resp = LBSShare.LocationResp() - resp.mergeFrom(buffer.slice(4)) + val resp = buffer.decodeToObject(LBSShare.LocationResp()) val location = resp.mylbs return Result.success(location.addr.get()) } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt index fda842b..dcf6ba9 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt @@ -24,6 +24,7 @@ import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.shamrock.xposed.helper.msgService import moe.fuqiuluo.symbols.decodeProtobuf import protobuf.auto.toByteArray @@ -33,9 +34,10 @@ import java.util.* import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.random.Random +import kotlin.time.Duration.Companion.seconds -internal object MsgSvc : BaseSvc() { - private suspend fun prepareTempChatFromGroup( +internal object MsgSvc : QQInterfaces() { + suspend fun prepareTempChatFromGroup( groupId: String, peerId: String ): Result { @@ -418,13 +420,9 @@ internal object MsgSvc : BaseSvc() { ) ).toByteArray() - val buffer = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 30_000) + val buffer = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 30.seconds) ?: return Result.failure(Exception("unable to upload multi message, response timeout")) - val rsp = runCatching { - buffer.slice(4).decodeProtobuf() - }.getOrElse { - buffer.decodeProtobuf() - } + val rsp = buffer.decodeToObject() val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message")) return Result.success(MessageSegment( type = "forward", @@ -456,7 +454,7 @@ internal object MsgSvc : BaseSvc() { true, req.toByteArray() ) ?: return Result.failure(Exception("unable to get multi message")) - val rsp = buffer.slice(4).decodeProtobuf() + val rsp = buffer.decodeToObject() val zippedPayload = DeflateTools.ungzip( rsp.recvResult?.payload ?: return Result.failure(Exception("payload is empty")) ) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt index 15f1976..5903179 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt @@ -13,6 +13,7 @@ import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg import moe.fuqiuluo.shamrock.tools.broadcast import moe.fuqiuluo.shamrock.utils.DeflateTools +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.MobileQQ import protobuf.auto.toByteArray import protobuf.message.* @@ -21,7 +22,7 @@ import protobuf.push.MessagePush import kotlin.coroutines.resume import kotlin.text.toByteArray -internal object PacketSvc : BaseSvc() { +internal object PacketSvc : QQInterfaces() { /** * 伪造收到Json卡片消息 */ diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt index 9b8bd0f..1801343 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt @@ -23,6 +23,7 @@ import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.MD5 import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import protobuf.fav.WeiyunAddRichMediaReq import protobuf.fav.WeiyunAuthor import protobuf.fav.WeiyunCollectCommInfo @@ -49,7 +50,7 @@ import kotlin.coroutines.resume /** * QQ收藏相关接口 */ -internal object QFavSvc: BaseSvc() { +internal object QFavSvc: QQInterfaces() { private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also { it.isIpv6 = false it.mIp = "collector.weiyun.com" @@ -275,7 +276,7 @@ internal object QFavSvc: BaseSvc() { override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {} } - val vi = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin) + val vi = ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin) //LogCenter.log(pSKey) httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST httpNetReq.mSendData = BytePacketBuilder().apply { @@ -381,7 +382,7 @@ internal object QFavSvc: BaseSvc() { } private fun getWeiYunPSKey(): String { - val pskey = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager) + val pskey = ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager) .getPskey(app.currentAccountUin, 16L, arrayOf("weiyun.com"), WeiYunPSKeyPromise) return if (pskey != null) pskey.getPSkey("weiyun.com") else "" } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt index 3f7e9f0..6bab74b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt @@ -4,12 +4,12 @@ import QQService.SvcDevLoginInfo import QQService.SvcReqGetDevLoginInfo import QQService.SvcRspGetDevLoginInfo import com.qq.jce.wup.UniPacket -import moe.fuqiuluo.qqinterface.servlet.BaseSvc +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.MobileQQ import mqq.app.Packet import oicq.wlogin_sdk.tools.util -internal object QSafeSvc: BaseSvc() { +internal object QSafeSvc: QQInterfaces() { suspend fun getOnlineClients(): ArrayList? { val req = SvcReqGetDevLoginInfo() @@ -26,7 +26,7 @@ internal object QSafeSvc: BaseSvc() { val resp = sendBufferAW("StatSvc.GetDevLoginInfo", false, uniPacket.encode()) ?: return null - return Packet.decodePacket(resp, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo + return Packet.decodePacket(resp.wupBuffer, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt index 415bf8f..8275871 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt @@ -8,13 +8,15 @@ import io.ktor.client.request.get import io.ktor.client.request.header import moe.fuqiuluo.shamrock.remote.service.data.BigDataTicket import moe.fuqiuluo.shamrock.tools.GlobalClientNoRedirect +import moe.fuqiuluo.shamrock.tools.decodeToOidb import moe.fuqiuluo.shamrock.tools.slice +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.MobileQQ import mqq.manager.TicketManager import oicq.wlogin_sdk.request.Ticket import tencent.im.oidb.oidb_sso -internal object TicketSvc: BaseSvc() { +internal object TicketSvc: QQInterfaces() { object SigType { const val WLOGIN_A5 = 2 const val WLOGIN_RESERVED = 16 @@ -109,23 +111,23 @@ internal object TicketSvc: BaseSvc() { } fun getTicket(uin: String, id: Int): Ticket? { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getLocalTicket(uin, id) + return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getLocalTicket(uin, id) } fun getStWeb(uin: String): String { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getStweb(uin) + return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getStweb(uin) } fun getSKey(uin: String): String { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getSkey(uin) + return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getSkey(uin) } fun getRealSkey(uin: String): String { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getRealSkey(uin) + return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getRealSkey(uin) } fun getPSKey(uin: String): String { - val manager = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager) + val manager = ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager) manager.reloadCache(MobileQQ.getContext()) return manager.getSuperkey(uin) ?: "" } @@ -135,14 +137,13 @@ internal object TicketSvc: BaseSvc() { req.domains.set(domain.toList()) val buffer = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray()) ?: return Result.failure(Exception("getLessPSKey failed")) - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = buffer.decodeToOidb() val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) return Result.success(rsp.private_keys.get()) } suspend fun getPSKey(uin: String, domain: String): String? { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPskey(uin, domain).let { + return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPskey(uin, domain).let { if (it.isNullOrBlank()) getLessPSKey(domain).getOrNull()?.firstOrNull()?.key?.get() else it @@ -150,7 +151,7 @@ internal object TicketSvc: BaseSvc() { } fun getPt4Token(uin: String, domain: String): String? { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPt4Token(uin, domain) + return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPt4Token(uin, domain) } suspend fun GetHttpCookies(appid: String, daid: String, jumpurl: String): String? { diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt index 57f6dc5..0eb53ca 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt @@ -1,6 +1,8 @@ package moe.fuqiuluo.qqinterface.servlet -internal object VisitorSvc: BaseSvc() { +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces + +internal object VisitorSvc: QQInterfaces() { const val FROM_C2C_AIO = 2 const val FROM_CONDITION_SEARCH = 9 const val FROM_CONTACTS_TAB = 5 diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt index 0a3c47d..75aff68 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt @@ -1,11 +1,11 @@ package moe.fuqiuluo.qqinterface.servlet.ark import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77 -internal object ArkMsgSvc: BaseSvc() { +internal object ArkMsgSvc: QQInterfaces() { fun tryShareMusic( chatType: Int, peerId: Long, diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt index 6f35f45..b38c93c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt @@ -1,8 +1,8 @@ package moe.fuqiuluo.qqinterface.servlet.ark -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo import moe.fuqiuluo.shamrock.utils.PlatformUtils +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.symbols.decodeProtobuf import protobuf.auto.toByteArray import protobuf.lightapp.AdaptShareInfoReq @@ -11,7 +11,7 @@ import protobuf.qweb.DEFAULT_DEVICE_INFO import protobuf.qweb.QWebReq import protobuf.qweb.QWebRsp -internal object LightAppSvc: BaseSvc() { +internal object LightAppSvc: QQInterfaces() { suspend fun adaptShareJumpUrl( arkAppInfo: ArkAppInfo, coverUrl: String, @@ -37,7 +37,7 @@ internal object LightAppSvc: BaseSvc() { webURL = url, ).toByteArray(), traceId = app.account + "_0_0", - ).toByteArray())?.decodeProtobuf()?.buffer?.decodeProtobuf() + ).toByteArray())?.wupBuffer?.decodeProtobuf()?.buffer?.decodeProtobuf() if (rsp == null || rsp.json.isNullOrEmpty()) return Result.failure(Exception("unable to adapt ShareInfo")) return Result.success(rsp.json!!) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt index 2cff2c6..4408d01 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt @@ -182,48 +182,14 @@ internal object NtMsgElementConverter { /* PicElement{picSubType=0,fileName=A655FCDADABC40D0CEAF6F9AF92937CD.jpg,fileSize=142865,picWidth=886,picHeight=1920,original=false,md5HexStr=a655fcdadabc40d0ceaf6f9af92937cd,sourcePath=null,thumbPath=null,transferStatus=2,progress=0,picType=1000,invalidState=0,fileUuid=CgoxMDI5Mzc0MTE1EhTnucgrUbp3MJjjagUM2-VxSQ5V7hiR3Agg_goo9ZCZt-HNhANQgJqeAQ,fileSubId=,thumbFileSize=0,fileBizId=null,downloadIndex=null,summary=,emojiFrom=null,emojiWebUrl=null,emojiAd=EmojiAD{url=,desc=,},emojiMall=EmojiMall{packageId=0,emojiId=0,},emojiZplan=EmojiZPlan{actionId=0,actionName=,actionType=0,playerNumber=0,peerUid=0,bytesReserveInfo=,},originImageMd5=,originImageUrl=null,importRichMediaContext=null,isFlashPic=false,} */ + + val url = RichProtoSvc.getTempPicDownloadUrl(chatType, originalUrl, md5, image, storeId) + return MessageSegment( type = "image", data = hashMapOf( "file" to md5, - "url" to when (chatType) { - MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( - originalUrl = originalUrl, - md5 = md5, - fileId = image.fileUuid, - width = image.picWidth.toUInt(), - height = image.picHeight.toUInt(), - sha = "", - fileSize = image.fileSize.toULong(), - peer = peerId - ) - - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl( - originalUrl = originalUrl, - md5 = md5, - fileId = image.fileUuid, - width = image.picWidth.toUInt(), - height = image.picHeight.toUInt(), - sha = "", - fileSize = image.fileSize.toULong(), - peer = peerId, - storeId = storeId - ) - - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl( - originalUrl = originalUrl, - md5 = md5, - fileId = image.fileUuid, - width = image.picWidth.toUInt(), - height = image.picHeight.toUInt(), - sha = "", - fileSize = image.fileSize.toULong(), - peer = peerId, - subPeer = subPeer - ) - - else -> throw UnsupportedOperationException("Not supported chat type: $chatType") - }, + "url" to url, "subType" to image.picSubType, "type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show" ) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt index 58f15e5..a56bacf 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt @@ -124,18 +124,23 @@ internal class ElemMaker { else -> { qq = qqStr.toLong() type = 0 - "@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2( - peerId.toLong(), - qq, - true - ).let { - val info = it.getOrNull() - if (info == null) - LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR) - else info.troopnick - .ifNullOrEmpty(info.friendnick) - .ifNullOrEmpty(qqStr) - }) + val name = (data["name"].asStringOrNull + ?: GroupSvc.getTroopMemberInfoByUinV3(peerId.toLong(), qq).let { + it?.troopNick + .ifNullOrEmpty(it?.friendNick) + .ifNullOrEmpty(it?.showName) + .ifNullOrEmpty(it?.autoRemark) + .ifNullOrEmpty(it?.colorNick) + } + ?: GroupSvc.getTroopMemberInfoByUinV2(peerId.toLong(), qq, true).let { + val info = it.getOrNull() + if (info == null) + LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR) + else info.troopnick + .ifNullOrEmpty(info.friendnick) + .ifNullOrEmpty(qqStr) + }) + "@$name" } } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt index f064e76..9dc518c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt @@ -856,7 +856,15 @@ internal object NtMsgElementMaker { .ifNullOrEmpty(qqStr) }" } else { - at.content = "@$qqStr" + at.content = "@${ + GroupSvc.getTroopMemberInfoByUinV3(peerId.toLong(), qq).let { + it?.troopNick + .ifNullOrEmpty(it?.friendNick) + .ifNullOrEmpty(it?.showName) + .ifNullOrEmpty(it?.autoRemark) + .ifNullOrEmpty(it?.colorNick) + } ?: qqStr + }" } } else { at.content = "@$name" diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt index 9c93ee8..8341ccf 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt @@ -16,26 +16,29 @@ import com.tencent.qqnt.kernelpublic.nativeinterface.Contact import kotlinx.atomicfu.atomic import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig +import moe.fuqiuluo.shamrock.tools.decodeToObject +import moe.fuqiuluo.shamrock.tools.decodeToTrpcOidb import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.slice +import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.utils.AudioUtils import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.MediaType import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.shamrock.xposed.helper.msgService import moe.fuqiuluo.symbols.decodeProtobuf import protobuf.auto.toByteArray -import protobuf.oidb.TrpcOidb import protobuf.oidb.cmd0x11c5.ClientMeta import protobuf.oidb.cmd0x11c5.CodecConfigReq import protobuf.oidb.cmd0x11c5.CommonHead import protobuf.oidb.cmd0x11c5.DownloadExt import protobuf.oidb.cmd0x11c5.DownloadReq +import protobuf.oidb.cmd0x11c5.DownloadRkeyReq +import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp import protobuf.oidb.cmd0x11c5.FileInfo import protobuf.oidb.cmd0x11c5.FileType import protobuf.oidb.cmd0x11c5.IndexNode @@ -57,8 +60,9 @@ import kotlin.random.Random import kotlin.random.nextUInt import kotlin.random.nextULong import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds -internal object NtV2RichMediaSvc: BaseSvc() { +internal object NtV2RichMediaSvc: QQInterfaces() { private val requestIdSeq = atomic(2L) fun fetchGroupResUploadTo(): String { @@ -320,6 +324,40 @@ internal object NtV2RichMediaSvc: BaseSvc() { return Result.success(result) } + suspend fun getTempNtRKey(): Result { + runCatching { + val req = NtV2RichMediaReq( + head = MultiMediaReqHead( + commonHead = CommonHead( + requestId = requestIdSeq.incrementAndGet().toULong(), + cmd = 202u + ), + sceneInfo = SceneInfo( + requestType = 2u, + businessType = 1u, + sceneType = 0u, + ), + clientMeta = ClientMeta(2u) + ), + downloadRkey = DownloadRkeyReq( + types = listOf(10, 20), + downloadType = 2 + ) + ).toByteArray() + val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x9067_202", 0x9067, 202, req, true) + if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { + return Result.failure(Exception("failed to fetch NtTempRKey: ${fromServiceMsg?.wupBuffer?.toHexString()}")) + } + val trpc = fromServiceMsg.decodeToTrpcOidb() + trpc.buffer.decodeProtobuf().downloadRkeyRsp?.let { + return Result.success(it) + } + }.onFailure { + return Result.failure(it) + } + return Result.failure(Exception("failed to fetch NtTempRKey")) + } + /** * 获取NT图片的RKEY */ @@ -386,8 +424,9 @@ internal object NtV2RichMediaSvc: BaseSvc() { ) ) ).toByteArray() - val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)?.slice(4) - buffer?.decodeProtobuf()?.buffer?.decodeProtobuf()?.download?.rkeyParam?.let { + val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true) + ?: return Result.failure(Exception("no response")) + buffer.decodeToTrpcOidb().buffer.decodeProtobuf().download?.rkeyParam?.let { return Result.success(it) } }.onFailure { @@ -470,17 +509,16 @@ internal object NtV2RichMediaSvc: BaseSvc() { ).toByteArray() val buffer = when (chatType) { MsgConstant.KCHATTYPEGROUP -> { - sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3_000)?.slice(4) + sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3.seconds) ?: return Result.failure(Exception("no response: timeout")) } MsgConstant.KCHATTYPEC2C -> { - sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3_000)?.slice(4) + sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3.seconds) ?: return Result.failure(Exception("no response: timeout")) } - else -> return Result.failure(Exception("unknown chat type: $chatType")) } - val rspBuffer = buffer.decodeProtobuf().buffer + val rspBuffer = buffer.decodeToTrpcOidb().buffer val rsp = rspBuffer.decodeProtobuf() if (rsp.upload == null) { return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}")) @@ -522,7 +560,7 @@ internal object NtV2RichMediaSvc: BaseSvc() { ) ), ).toByteArray())!! - val rsp = rspBuffer.decodeProtobuf() + val rsp = rspBuffer.decodeToObject() .msgTryUpImgRsp!!.first() TryUpPicData( uKey = rsp.ukey, diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt index f37a6cc..9277877 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt @@ -6,18 +6,20 @@ import com.tencent.mobileqq.transfile.FileMsg import com.tencent.mobileqq.transfile.api.IProtoReqManager import com.tencent.mobileqq.transfile.protohandler.RichProto import com.tencent.mobileqq.transfile.protohandler.RichProtoProc +import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.kernel.nativeinterface.PicElement import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.ExperimentalSerializationApi -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.getNtPicRKey import moe.fuqiuluo.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.hex2ByteArray +import moe.fuqiuluo.shamrock.tools.decodeToOidb import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.symbols.decodeProtobuf import mqq.app.MobileQQ import protobuf.auto.toByteArray @@ -38,7 +40,7 @@ private const val GPRO_PIC = "gchat.qpic.cn" private const val MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn" private const val C2C_PIC = "c2cpicdw.qpic.cn" -internal object RichProtoSvc: BaseSvc() { +internal object RichProtoSvc: QQInterfaces() { suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String { val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody( msgCmd = 1200, @@ -53,8 +55,7 @@ internal object RichProtoSvc: BaseSvc() { supportEncrypt = 0 ) ).toByteArray()) ?: return "" - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = buffer.decodeToOidb() body.bytes_bodybuffer .get().toByteArray() .decodeProtobuf() @@ -79,8 +80,7 @@ internal object RichProtoSvc: BaseSvc() { str_file_id.set(fileId) }) }.toByteArray()) ?: return "" - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = buffer.decodeToOidb() val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) if (body.uint32_result.get() != 0 || result.download_file_rsp.int32_ret_code.get() != 0) { @@ -128,8 +128,7 @@ internal object RichProtoSvc: BaseSvc() { } return "" } else { - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) + val body = buffer.decodeToOidb() val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom( body.bytes_bodybuffer.get().toByteArray() ).bytes_cmd_0x346_rsp_body.get().toByteArray()) @@ -150,6 +149,75 @@ internal object RichProtoSvc: BaseSvc() { } } + suspend fun getTempPicDownloadUrl( + chatType: Int, + originalUrl: String, + md5: String, + image: PicElement, + storeId: Int = 0, + peer: String? = null, + subPeer: String? = null, + ): String { + val isNtServer = originalUrl.startsWith("/download") + if (isNtServer) { + val tmpRKey = NtV2RichMediaSvc.getTempNtRKey() + if (tmpRKey.isSuccess) { + val tmpRKeyRsp = tmpRKey.getOrThrow() + val tmpRKeyMap = hashMapOf() + tmpRKeyRsp.rkeys?.forEach { rKeyInfo -> + tmpRKeyMap[rKeyInfo.type] = rKeyInfo.rkey + } + val rkey = tmpRKeyMap[when(chatType) { + MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> 10u + MsgConstant.KCHATTYPEC2C -> 20u + MsgConstant.KCHATTYPEGUILD -> 10u + else -> 0u + }] + if (rkey != null) { + return "https://$MULTIMEDIA_DOMAIN$originalUrl$rkey" + } + } + } + return when (chatType) { + MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> getGroupPicDownUrl( + originalUrl = originalUrl, + md5 = md5, + fileId = image.fileUuid, + width = image.picWidth.toUInt(), + height = image.picHeight.toUInt(), + sha = "", + fileSize = image.fileSize.toULong(), + peer = peer ?: "0" + ) + + MsgConstant.KCHATTYPEC2C -> getC2CPicDownUrl( + originalUrl = originalUrl, + md5 = md5, + fileId = image.fileUuid, + width = image.picWidth.toUInt(), + height = image.picHeight.toUInt(), + sha = "", + fileSize = image.fileSize.toULong(), + peer = peer ?: "0", + storeId = storeId + ) + + MsgConstant.KCHATTYPEGUILD -> getGuildPicDownUrl( + originalUrl = originalUrl, + md5 = md5, + fileId = image.fileUuid, + width = image.picWidth.toUInt(), + height = image.picHeight.toUInt(), + sha = "", + fileSize = image.fileSize.toULong(), + peer = peer ?: "0", + subPeer = subPeer ?: "0" + ) + + else -> throw UnsupportedOperationException("Not supported chat type: $chatType") + } + } + suspend fun getGroupPicDownUrl( originalUrl: String, md5: String, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LocalCacheHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LocalCacheHelper.kt index 9783f55..ebc4a7c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LocalCacheHelper.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LocalCacheHelper.kt @@ -1,11 +1,11 @@ package moe.fuqiuluo.shamrock.helper -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.shamrock.utils.FileUtils +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.MobileQQ import java.io.File -internal object LocalCacheHelper: BaseSvc() { +internal object LocalCacheHelper: QQInterfaces() { // 获取外部储存data目录 private val dataDir = MobileQQ.getContext().getExternalFilesDir(null)!! .parentFile!!.resolve("Tencent") diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LogCenter.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LogCenter.kt index 469bd9b..9a28849 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LogCenter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/LogCenter.kt @@ -9,8 +9,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.xposed.hooks.toast -import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester +import moe.fuqiuluo.shamrock.tools.toast +import moe.fuqiuluo.shamrock.xposed.helper.AppTalker import mqq.app.MobileQQ import java.io.File import java.util.Date @@ -60,10 +60,10 @@ internal object LogCenter { } // 把日志广播到主进程 GlobalScope.launch(Dispatchers.Default) { - DataRequester.request("send_message", bodyBuilder = { + AppTalker.talk("send_message") { put("string", string) put("level", level.id) - }) + } } if (!LogFile.exists()) { @@ -89,10 +89,10 @@ internal object LogCenter { } // 把日志广播到主进程 GlobalScope.launch(Dispatchers.Default) { - DataRequester.request("send_message", bodyBuilder = { + AppTalker.talk("send_message") { put("string", log) put("level", level.id) - }) + } } if (!LogFile.exists()) { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt index a57c63f..c110569 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt @@ -1,9 +1,12 @@ package moe.fuqiuluo.shamrock.helper import com.tencent.mobileqq.qroute.QRoute +import com.tencent.qqnt.kernel.api.IKernelService import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgElement +import com.tencent.qqnt.kernel.nativeinterface.MsgRecord +import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo import com.tencent.qqnt.kernelpublic.nativeinterface.Contact import com.tencent.qqnt.msg.api.IMsgService import kotlinx.coroutines.DelicateCoroutinesApi @@ -16,12 +19,15 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import moe.fuqiuluo.qqinterface.servlet.MsgSvc +import moe.fuqiuluo.qqinterface.servlet.MsgSvc.prepareTempChatFromGroup import moe.fuqiuluo.qqinterface.servlet.msg.maker.ElemMaker import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.helper.db.MessageMapping import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.tools.* +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces.Companion.app +import moe.fuqiuluo.shamrock.xposed.helper.msgService import protobuf.message.RichText import kotlin.coroutines.resume import kotlin.math.abs @@ -48,6 +54,28 @@ internal object MessageHelper { return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback) } + suspend fun sendMessage(contact: Contact, msgs: ArrayList, retry: Int, msgId: SendMsgResult): Result { + if (contact.chatType == MsgConstant.KCHATTYPETEMPC2CFROMGROUP) { + prepareTempChatFromGroup(contact.guildId, contact.peerUid).getOrThrow() + } + return withTimeoutOrNull(5000) { + suspendCancellableCoroutine { + QRoute.api(IMsgService::class.java).sendMsg(contact, msgId.qqMsgId, msgs) { code: Int, msg: String -> + if (code == 0) { + it.resume(msgId.qqMsgId) + } else { + LogCenter.log("消息发送失败: $code:$msg", Level.WARN) + it.resume(null) + } + } + } + }?.let { Result.success(SendMsgResult( + msgHashId = msgId.msgHashId, + qqMsgId = it, + msgTime = System.currentTimeMillis() + )) } ?: resendMsg(contact, msgId.qqMsgId, retry, msgHashId = msgId.msgHashId) + } + suspend fun resendMsg( chatType: Int, peerId: String, @@ -246,6 +274,40 @@ internal object MessageHelper { } } + suspend fun getTempChatInfo(chatType: Int, uid: String): Result { + val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService + ?: return Result.failure(Exception("获取消息服务失败")) + val info: TempChatInfo = withTimeoutOrNull(5000) { + suspendCancellableCoroutine { + msgService.getTempChatInfo(chatType, uid) { code, msg, tempChatInfo -> + if (code == 0) { + it.resume(tempChatInfo) + } else { + LogCenter.log("获取临时会话信息失败: $code:$msg", Level.ERROR) + it.resume(null) + } + } + } + } ?: return Result.failure(Exception("获取临时会话信息失败")) + return Result.success(info) + } + + suspend fun generateContact(record: MsgRecord): Contact { + val peerId = when (record.chatType) { + MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> record.senderUid + MsgConstant.KCHATTYPEGUILD -> record.channelId + else -> record.peerUin.toString() + } + return Contact(record.chatType, peerId, if (record.chatType == MsgConstant.KCHATTYPEGUILD) { + record.guildId + } else if(record.chatType == MsgConstant.KCHATTYPETEMPC2CFROMGROUP) { + val tempInfo = getTempChatInfo(record.chatType, peerId).getOrThrow() + tempInfo.groupCode + } else { + null + }) + } + suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact { val peerId = when (chatType) { MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt index 59ff336..f8d83e6 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt @@ -1,5 +1,6 @@ package moe.fuqiuluo.shamrock.remote +import com.tencent.mobileqq.app.QQAppInterface import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.engine.ApplicationEngine @@ -21,7 +22,9 @@ import moe.fuqiuluo.shamrock.remote.plugin.Auth import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester +import moe.fuqiuluo.shamrock.tools.ShamrockVersion +import moe.fuqiuluo.shamrock.xposed.helper.AppTalker +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces.Companion.app import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader import org.slf4j.LoggerFactory import java.security.KeyStore @@ -138,10 +141,13 @@ internal object HTTPServer { isServiceStarted = true currServerPort = port LogCenter.log("Start HTTP Server: http://0.0.0.0:$currServerPort/") - DataRequester.request("success", values = mapOf( - "port" to currServerPort, - "voice" to NativeLoader.isVoiceLoaded - )) + AppTalker.talk("success") { + put("account", app.currentAccountUin) + put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown") + put("voice", NativeLoader.isVoiceLoaded) + put("core_version", ShamrockVersion) + put("port", currServerPort) + } } fun isActive(): Boolean { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt index 034b6be..27ff8cf 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/OcrImage.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.remote.action.ActionSession @@ -17,6 +16,7 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.MD5 +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.symbols.OneBotHandler import java.io.File import kotlin.coroutines.resume @@ -55,7 +55,7 @@ internal object OcrImage: IActionHandler() { } private suspend fun getOcrResult(file: File): Result { - val ocrService = BaseSvc.app.getRuntimeService(IPicOcrService::class.java, "all") + val ocrService = QQInterfaces.app.getRuntimeService(IPicOcrService::class.java, "all") ?: return Result.failure(Error("获取OCR服务失败")) return withTimeoutOrNull(5000) { suspendCancellableCoroutine { continuation -> diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt index 1723a16..2244a22 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt @@ -2,11 +2,10 @@ package moe.fuqiuluo.shamrock.remote.action.handlers import kotlinx.atomicfu.atomic import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.shamrock.remote.action.ActionSession import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.tools.EmptyJsonString +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import moe.fuqiuluo.symbols.OneBotHandler import protobuf.auto.toByteArray import protobuf.message.* @@ -51,7 +50,7 @@ internal object SendMsgByResid : IActionHandler() { msgRand = Random.nextUInt(), msgVia = 0u ) - BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray()) + QQInterfaces.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray()) return ok("ok", echo) } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt index ff7af36..6766a1d 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt @@ -39,7 +39,6 @@ import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.respond import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.xposed.ipc.ShamrockIpc import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.IByteData import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSigner import mqq.app.MobileQQ @@ -113,10 +112,7 @@ fun Routing.qsign() { return@get } if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@get - } + call.respond(OldApiResult(-2, "不支持的操作", null)) } val list = signer!!.cmdWhiteList call.respond(OldApiResult(0, "success", list)) @@ -128,10 +124,7 @@ fun Routing.qsign() { return@getOrPost } if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@getOrPost - } + call.respond(OldApiResult(-2, "不支持的操作", null)) } val uin = fetchOrThrow("uin") @@ -213,43 +206,7 @@ fun Routing.qsign() { } get("/get_byte") { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return@get - } - if (byteData == null || byteData?.asBinder()?.isBinderAlive == false) { - val binder = ShamrockIpc.get(ShamrockIpc.IPC_BYTEDATA) - if (binder == null) { - call.respond(OldApiResult(-2, "获取失败", null)) - return@get - } else { - byteData = IByteData.Stub.asInterface(binder) - binder.linkToDeath({ - byteData = null - }, 0) - } - } - - val data = fetchGetOrThrow("data") - if(!(data.startsWith("810_") || data.startsWith("812_"))) { - call.respond(OldApiResult(-2, "data参数不合法", null)) - return@get - } - - val uin = fetchOrThrow("uin") - val salt = fetchSalt(data, uin) - if (salt.isEmpty()) { - call.respond(OldApiResult(-2, "无法自动决断mode,请主动提供", null)) - return@get - } - - val sign = byteData!!.sign(uin, data, salt).sign - - if (sign == null) { - call.respond(OldApiResult(-2, "获取失败", null)) - } else { - call.respond(OldApiResult(0, "success", sign.toHexString())) - } + call.respond(OldApiResult(-2, "不支持的操作", null)) } get("/friend_sign") { @@ -258,10 +215,7 @@ fun Routing.qsign() { return@get } if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@get - } + call.respond(OldApiResult(-2, "不支持的操作", null)) } val addUin = fetchOrThrow("add_uin") @@ -286,10 +240,7 @@ fun Routing.qsign() { return@get } if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@get - } + call.respond(OldApiResult(-2, "不支持的操作", null)) } val addUin = fetchOrThrow("group_uin") @@ -387,23 +338,6 @@ private data class Sign( val requestCallback: List ) -private suspend fun initSigner(): Boolean { - if (!isMsfServiceAlive()) { - return false - } - val binder = ShamrockIpc.get(ShamrockIpc.IPC_QSIGN) - if (binder == null) { - //respond(false, Status.InternalHandlerError) - return false - } else { - signer = IQSigner.Stub.asInterface(binder) - binder.linkToDeath({ - signer = null - }, 0) - return true - } -} - private suspend fun PipelineContext.requestSign( cmd: String, uin: String, @@ -415,10 +349,7 @@ private suspend fun PipelineContext.requestSign( return } if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return - } + call.respond(OldApiResult(-2, "不支持的操作", null)) } val sign = withTimeoutOrNull(5000) { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt index 60e29a9..91f12e8 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt @@ -2,13 +2,13 @@ package moe.fuqiuluo.shamrock.remote.api import com.tencent.mobileqq.dt.model.FEBound import io.ktor.server.routing.Routing -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.shamrock.remote.structures.Protocol import moe.fuqiuluo.shamrock.remote.structures.QSignDtConfig import moe.fuqiuluo.shamrock.remote.structures.Status import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.utils.MMKVFetcher import moe.fuqiuluo.shamrock.utils.PlatformUtils +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import mqq.app.MobileQQ import oicq.wlogin_sdk.tlv_type.tlv_t100 import oicq.wlogin_sdk.tlv_type.tlv_t106 @@ -20,8 +20,8 @@ fun Routing.obtainProtocolData() { val cmd = fetchOrThrow("cmd") val isPb = fetchOrThrow("proto").toBooleanStrict() val buffer = fetchOrThrow("buffer").hex2ByteArray() - val resp = BaseSvc.sendBufferAW(cmd, isPb, buffer) - respond(true, Status.Ok, data = resp?.toHexString() ?: "null", msg = "成功") + val resp = QQInterfaces.sendBufferAW(cmd, isPb, buffer) + respond(true, Status.Ok, data = resp?.wupBuffer?.toHexString() ?: "null", msg = "成功") } getOrPost("/set_guid") { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt index 68334d1..0a5aa5f 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt @@ -10,9 +10,7 @@ import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.handlers.SendMsgByResid import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.fetchOrNull import moe.fuqiuluo.shamrock.tools.fetchOrThrow import moe.fuqiuluo.shamrock.tools.getOrPost import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt index 8f1149a..4ed6813 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt @@ -6,7 +6,6 @@ import io.ktor.server.application.createApplicationPlugin import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.tools.fetchOrNull import java.net.URLDecoder -import java.nio.charset.Charset private suspend fun ApplicationCall.checkToken() { val token = ShamrockConfig.getToken() diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt deleted file mode 100644 index 6d2eedd..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt +++ /dev/null @@ -1,69 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service - -import com.tencent.qphone.base.remote.FromServiceMsg -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import mqq.app.MobileQQ - -internal object PacketReceiver { - private val allowCommandList: MutableSet by lazy { mutableSetOf( - "trpc.msg.olpush.OlPushService.MsgPush", - - ) } // 非动态注册,永久常驻的包 - private val HandlerByIpcSet = hashSetOf() - - fun init() { - DynamicReceiver.register("register_handler_cmd", IPCRequest { - val cmd = it.getStringExtra("handler_cmd")!! - LogCenter.log({ "RegisterHandler(cmd = $cmd)" }, Level.DEBUG) - HandlerByIpcSet.add(cmd) - }) - DynamicReceiver.register("unregister_handler_cmd", IPCRequest { - val cmd = it.getStringExtra("handler_cmd")!! - LogCenter.log({ "UnRegisterHandler(cmd = $cmd)" }, Level.DEBUG) - HandlerByIpcSet.remove(cmd) - }) - MobileQQ.getContext().broadcast("xqbot") { - putExtra("__cmd", "msf_waiter") - LogCenter.log("MSF Packet Receiver running!") - } - } - - private fun onReceive(from: FromServiceMsg) { - if (HandlerByIpcSet.contains(from.serviceCmd) - || allowCommandList.contains(from.serviceCmd) - ) { - LogCenter.log({ "ReceivePacket(cmd = ${from.serviceCmd})" }, Level.DEBUG) - MobileQQ.getContext().broadcast("xqbot") { - putExtra("__cmd", from.serviceCmd) - putExtra("buffer", from.wupBuffer) - putExtra("seq", from.requestSsoSeq) - } - } else { - val seq = if (from.appSeq == -1) from.requestSsoSeq else from.appSeq - val hash = (from.serviceCmd + seq).hashCode() - LogCenter.log({ "ReceivePacket[$hash](cmd = ${from.serviceCmd}, seq = $seq)" }, Level.DEBUG) - MobileQQ.getContext().broadcast("xqbot") { - putExtra("__hash", hash) - putExtra("buffer", from.wupBuffer) - putExtra("seq", seq) - } - } - } - - fun internalOnReceive(from: FromServiceMsg?) { - if (from == null) return - GlobalScope.launch(Dispatchers.Default) { - onReceive(from) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt index 2f6d1e7..14606d8 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch -import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.CardSvc import moe.fuqiuluo.qqinterface.servlet.GroupSvc import moe.fuqiuluo.qqinterface.servlet.msg.toSegments @@ -35,9 +34,10 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.Sender import moe.fuqiuluo.shamrock.remote.service.data.push.SignDetail import moe.fuqiuluo.shamrock.tools.ShamrockDsl import moe.fuqiuluo.shamrock.tools.json +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces import java.util.ArrayList -internal object GlobalEventTransmitter: BaseSvc() { +internal object GlobalEventTransmitter: QQInterfaces() { private val messageEventFlow by lazy { MutableSharedFlow>() } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt index 1225e7c..7371f4f 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt @@ -1,284 +1,13 @@ package moe.fuqiuluo.shamrock.remote.service.config import android.content.Intent -import com.tencent.mmkv.MMKV import de.robv.android.xposed.XposedBridge import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.tools.GlobalJson5 -import moe.fuqiuluo.shamrock.utils.MMKVFetcher +import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader.moduleLoader import mqq.app.MobileQQ -import java.io.File +import java.util.Properties -internal object ShamrockConfig { - private val ConfigDir = MobileQQ.getContext().getExternalFilesDir(null)!! - .parentFile!!.resolve("Tencent/Shamrock").also { - if (it.exists()) it.delete() - it.mkdirs() - } - private val config = kotlin.runCatching { - GlobalJson5.decodeFromString(ConfigDir.resolve("config.json").also { - if (!it.exists()) it.writeText("{}") - }.readText()) - }.onFailure { - LogCenter.log("您的配置文件出现错误: ${it.stackTraceToString()}", Level.ERROR) - }.getOrElse { - ServiceConfig() - } +internal object ShamrockConfig: ShamrockConfigV0() { - fun isInit(): Boolean { - val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") - return mmkv.getBoolean("isInit", false) - } - - private fun updateConfig(config: ServiceConfig = this.config) { - ConfigDir.resolve("config.json").writeText(GlobalJson5.encodeToString(config)) - } - - fun updateConfig(intent: Intent) { - mmkv.apply { - if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) { - putBoolean("tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式 - putInt("port", intent.getIntExtra("port", 5700)) // 主动HTTP端口 - putBoolean("ws", intent.getBooleanExtra("ws", false)) // 主动WS开关 - putBoolean("http", intent.getBooleanExtra("http", false)) // HTTP回调开关 - putString("http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址 - putBoolean("ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关 - putBoolean("use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码 - putBoolean("inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包 - putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式 - putString( "key_store", intent.getStringExtra("key_store")) // 证书路径 - putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码 - putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码 - putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名 - putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口 - putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试 - putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息 - putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口 - putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息 - putBoolean("forbid_useless_process", intent.getBooleanExtra("forbid_useless_process", false)) // 禁用QQ生成无用进程 - putBoolean("enable_old_bdh", intent.getBooleanExtra("enable_old_bdh", false)) // 启用旧版BDH - intent.getStringExtra("up_res_group")?.let { putString("up_res_group", it) } - } else { - XposedBridge.log("[Shamrock] 已禁用自动同步配置") - } - config.defaultToken = intent.getStringExtra("token") - config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true) - val wsPort = intent.getIntExtra("ws_port", 5800) - config.activeWebSocket = if (config.activeWebSocket == null) ConnectionConfig( - address = "0.0.0.0", - port = wsPort, - ) else config.activeWebSocket?.also { - it.port = wsPort - } - config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", ",")?.filter { address -> - address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://")) - }?.map { - ConnectionConfig(address = it) - }?.toMutableList() - putBoolean("isInit", true) - } - if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) { - updateConfig() - } - } - - fun putDefaultSettings() { - val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") - if ((!isInit()) && (!mmkv.getBoolean("isEmergencyInitBefore", false))){ - mmkv.apply { - putBoolean("tablet", false) // 强制平板模式 - putInt("port", 5700) // 主动HTTP端口 - putBoolean("ws", false) // 主动WS开关 - putBoolean("http", false) // HTTP回调开关 - putString("http_addr", null) // WebHook回调地址 - putBoolean("ws_client", false) // 被动WS开关 - putBoolean("use_cqcode", false) // 使用CQ码 - putBoolean("inject_packet", false) // 拦截无用包 - putBoolean("debug", false) // 调试模式 - putString("key_store", null) // 证书路径 - putString("ssl_pwd", null) // 证书密码 - putString("ssl_private_pwd", null) // 证书私钥密码 - putString("ssl_alias", null) // 证书别名 - putInt("ssl_port", 5701) // 主动HTTP端口 - putBoolean("alive_reply", true) // 自回复测试 - putBoolean("enable_self_msg", false) // 推送自己发的消息 - putBoolean("shell", false) // 开启Shell接口 - putBoolean("enable_sync_msg_as_sent_msg", true) // 推送同步消息 - putBoolean("forbid_useless_process", false) // 禁用QQ生成无用进程 - putBoolean("enable_old_bdh", false) // 启用旧版BDH - putBoolean("antiTrace", false) - putBoolean("super_anti", true) - putString("up_res_group", "") - - config.defaultToken = null - // config.antiTrace = true - val wsPort = 5800 - config.activeWebSocket = - if (config.activeWebSocket == null) ConnectionConfig( - address = "0.0.0.0", - port = wsPort, - ) else config.activeWebSocket?.also { - it.port = wsPort - } - config.passiveWebSocket = null - putBoolean("isInit", true) - putBoolean("isEmergencyInitBefore", true) - } - XposedBridge.log("[Shamrock] 强制初始化配置完成,请重新打开QQ") - } else { - XposedBridge.log("[Shamrock] 程序逻辑错误或错误地多次强制初始化") - mmkv.putBoolean("isEmergencyInitBefore", false) - XposedBridge.log("[Shamrock] 如果执意要再次强制初始化,请重新执行程序") - } - } - - private val mmkv: MMKV - get() = MMKVFetcher.mmkvWithId("shamrock_config") - - fun getUpResGroup(): String { - return mmkv.getString("up_res_group", "") ?: "" - } - - fun aliveReply(): Boolean { - return mmkv.getBoolean("alive_reply", false) - } - - fun allowTempSession(): Boolean { - return config.allowTempSession - } - - fun getGroupMsgRule(): GroupRule? { - return config.rules?.groupRule - } - - fun getPrivateRule(): PrivateRule? { - return config.rules?.privateRule - } - - fun enableSyncMsgAsSentMsg(): Boolean { - return mmkv.getBoolean("enable_sync_msg_as_sent_msg", false) - } - - fun enableSelfMsg(): Boolean { - return mmkv.getBoolean("enable_self_msg", false) - } - - fun forbidUselessProcess(): Boolean { - return mmkv.getBoolean("forbid_useless_process", false) - } - - fun openWebSocketClient(): Boolean { - return mmkv.getBoolean("ws_client", false) - } - - fun getWebSocketClientAddress(): List { - return config.passiveWebSocket ?: emptyList() - } - - fun openWebSocket(): Boolean { - return mmkv.getBoolean("ws", false) - } - - fun getActiveWebSocketConfig(): ConnectionConfig? { - return config.activeWebSocket - } - - fun getToken(): String { - return config.defaultToken ?: "" - } - - fun useCQ(): Boolean { - return mmkv.getBoolean("use_cqcode", false) - } - - fun allowWebHook(): Boolean { - return mmkv.getBoolean("http", false) - } - - fun getWebHookAddress(): String { - return mmkv.getString("http_addr", "") ?: "" - } - - fun forceTablet(): Boolean { - return mmkv.getBoolean("tablet", true) - } - - fun getPort(): Int { - return mmkv.getInt("port", 5700) - } - - fun isInjectPacket(): Boolean { - return mmkv.getBoolean("inject_packet", false) - } - - fun enableOldBDH(): Boolean { - return mmkv.getBoolean("enable_old_bdh", false) - } - - fun isDebug(): Boolean { - return mmkv.getBoolean("debug", false) - } - - fun ssl(): Boolean { - return getKeyStorePath()?.exists() == true - } - - fun getKeyStorePath(): File? { - mmkv.getString("key_store", null)?.let { - return File(it) - } - return null - } - - fun sslPwd(): CharArray? { - return mmkv.getString("ssl_pwd", null)?.toCharArray() - } - - fun sslPrivatePwd(): String? { - return mmkv.getString("ssl_private_pwd", null) - } - - fun sslAlias(): String? { - return mmkv.getString("ssl_alias", null) - } - - fun getSslPort(): Int { - return mmkv.getInt("ssl_port", getPort()) - } - - fun isDev(): Boolean { - return mmkv.getBoolean("dev", false) - } - - operator fun set(key: String, value: String) { - mmkv.putString(key, value) - } - - operator fun set(key: String, value: Boolean) { - mmkv.putBoolean(key, value) - } - - operator fun set(key: String, value: Int) { - mmkv.putInt(key, value) - } - - operator fun set(key: String, value: Long) { - mmkv.putLong(key, value) - } - - operator fun set(key: String, value: Float) { - mmkv.putFloat(key, value) - } - - fun isAntiTrace(): Boolean { - return config.antiTrace - } - - fun allowShell(): Boolean { - return mmkv.getBoolean("shell", false) - } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfigV0.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfigV0.kt new file mode 100644 index 0000000..5d528c2 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfigV0.kt @@ -0,0 +1,285 @@ +package moe.fuqiuluo.shamrock.remote.service.config + +import android.content.Intent +import com.tencent.mmkv.MMKV +import de.robv.android.xposed.XposedBridge +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.tools.GlobalJson5 +import moe.fuqiuluo.shamrock.tools.toast +import moe.fuqiuluo.shamrock.utils.MMKVFetcher +import mqq.app.MobileQQ +import java.io.File + +internal abstract class ShamrockConfigV0 { + private val configDir = MobileQQ.getContext().getExternalFilesDir(null)!! + .parentFile!!.resolve("Tencent/Shamrock").also { + if (it.exists()) it.delete() + it.mkdirs() + } + private val config = kotlin.runCatching { + GlobalJson5.decodeFromString(configDir.resolve("config.json").also { + if (!it.exists()) it.writeText("{}") + }.readText()) + }.onFailure { + LogCenter.log("您的配置文件出现错误: ${it.stackTraceToString()}", Level.ERROR) + }.getOrElse { + ServiceConfig() + } + + fun isInit(): Boolean { + val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") + return mmkv.getBoolean("isInit", false) + } + + private fun updateConfig(config: ServiceConfig = this.config) { + configDir.resolve("config.json").writeText(GlobalJson5.encodeToString(config)) + } + + fun updateConfig(intent: Intent) { + MobileQQ.getContext().toast("同步配置数据解析中...") + mmkv.apply { + if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) { + putBoolean("tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式 + putInt("port", intent.getIntExtra("port", 5700)) // 主动HTTP端口 + putBoolean("ws", intent.getBooleanExtra("ws", false)) // 主动WS开关 + putBoolean("http", intent.getBooleanExtra("http", false)) // HTTP回调开关 + putString("http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址 + putBoolean("ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关 + putBoolean("use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码 + putBoolean("inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包 + putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式 + putString( "key_store", intent.getStringExtra("key_store")) // 证书路径 + putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码 + putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码 + putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名 + putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口 + putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试 + putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息 + putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口 + putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息 + putBoolean("forbid_useless_process", intent.getBooleanExtra("forbid_useless_process", false)) // 禁用QQ生成无用进程 + putBoolean("enable_old_bdh", intent.getBooleanExtra("enable_old_bdh", false)) // 启用旧版BDH + intent.getStringExtra("up_res_group")?.let { putString("up_res_group", it) } + } else { + XposedBridge.log("[Shamrock] 已禁用自动同步配置") + } + config.defaultToken = intent.getStringExtra("token") + config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true) + val wsPort = intent.getIntExtra("ws_port", 5800) + config.activeWebSocket = if (config.activeWebSocket == null) ConnectionConfig( + address = "0.0.0.0", + port = wsPort, + ) else config.activeWebSocket?.also { + it.port = wsPort + } + config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", ",")?.filter { address -> + address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://")) + }?.map { + ConnectionConfig(address = it) + }?.toMutableList() + putBoolean("isInit", true) + } + if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) { + updateConfig() + } + } + + fun putDefaultSettings() { + val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") + if ((!isInit()) && (!mmkv.getBoolean("isEmergencyInitBefore", false))){ + mmkv.apply { + putBoolean("tablet", false) // 强制平板模式 + putInt("port", 5700) // 主动HTTP端口 + putBoolean("ws", false) // 主动WS开关 + putBoolean("http", false) // HTTP回调开关 + putString("http_addr", null) // WebHook回调地址 + putBoolean("ws_client", false) // 被动WS开关 + putBoolean("use_cqcode", false) // 使用CQ码 + putBoolean("inject_packet", false) // 拦截无用包 + putBoolean("debug", false) // 调试模式 + putString("key_store", null) // 证书路径 + putString("ssl_pwd", null) // 证书密码 + putString("ssl_private_pwd", null) // 证书私钥密码 + putString("ssl_alias", null) // 证书别名 + putInt("ssl_port", 5701) // 主动HTTP端口 + putBoolean("alive_reply", true) // 自回复测试 + putBoolean("enable_self_msg", false) // 推送自己发的消息 + putBoolean("shell", false) // 开启Shell接口 + putBoolean("enable_sync_msg_as_sent_msg", true) // 推送同步消息 + putBoolean("forbid_useless_process", false) // 禁用QQ生成无用进程 + putBoolean("enable_old_bdh", false) // 启用旧版BDH + putBoolean("antiTrace", false) + putBoolean("super_anti", true) + putString("up_res_group", "") + + config.defaultToken = null + // config.antiTrace = true + val wsPort = 5800 + config.activeWebSocket = + if (config.activeWebSocket == null) ConnectionConfig( + address = "0.0.0.0", + port = wsPort, + ) else config.activeWebSocket?.also { + it.port = wsPort + } + config.passiveWebSocket = null + putBoolean("isInit", true) + putBoolean("isEmergencyInitBefore", true) + } + XposedBridge.log("[Shamrock] 强制初始化配置完成,请重新打开QQ") + } else { + XposedBridge.log("[Shamrock] 程序逻辑错误或错误地多次强制初始化") + mmkv.putBoolean("isEmergencyInitBefore", false) + XposedBridge.log("[Shamrock] 如果执意要再次强制初始化,请重新执行程序") + } + } + + protected val mmkv: MMKV + get() = MMKVFetcher.mmkvWithId("shamrock_config") + + fun getUpResGroup(): String { + return mmkv.getString("up_res_group", "") ?: "" + } + + fun aliveReply(): Boolean { + return mmkv.getBoolean("alive_reply", false) + } + + fun allowTempSession(): Boolean { + return config.allowTempSession + } + + fun getGroupMsgRule(): GroupRule? { + return config.rules?.groupRule + } + + fun getPrivateRule(): PrivateRule? { + return config.rules?.privateRule + } + + fun enableSyncMsgAsSentMsg(): Boolean { + return mmkv.getBoolean("enable_sync_msg_as_sent_msg", false) + } + + fun enableSelfMsg(): Boolean { + return mmkv.getBoolean("enable_self_msg", false) + } + + fun forbidUselessProcess(): Boolean { + return mmkv.getBoolean("forbid_useless_process", false) + } + + fun openWebSocketClient(): Boolean { + return mmkv.getBoolean("ws_client", false) + } + + fun getWebSocketClientAddress(): List { + return config.passiveWebSocket ?: emptyList() + } + + fun openWebSocket(): Boolean { + return mmkv.getBoolean("ws", false) + } + + fun getActiveWebSocketConfig(): ConnectionConfig? { + return config.activeWebSocket + } + + fun getToken(): String { + return config.defaultToken ?: "" + } + + fun useCQ(): Boolean { + return mmkv.getBoolean("use_cqcode", false) + } + + fun allowWebHook(): Boolean { + return mmkv.getBoolean("http", false) + } + + fun getWebHookAddress(): String { + return mmkv.getString("http_addr", "") ?: "" + } + + fun forceTablet(): Boolean { + return mmkv.getBoolean("tablet", true) + } + + fun getPort(): Int { + return mmkv.getInt("port", 5700) + } + + fun isInjectPacket(): Boolean { + return mmkv.getBoolean("inject_packet", false) + } + + fun enableOldBDH(): Boolean { + return mmkv.getBoolean("enable_old_bdh", false) + } + + fun isDebug(): Boolean { + return mmkv.getBoolean("debug", false) + } + + fun ssl(): Boolean { + return getKeyStorePath()?.exists() == true + } + + fun getKeyStorePath(): File? { + mmkv.getString("key_store", null)?.let { + return File(it) + } + return null + } + + fun sslPwd(): CharArray? { + return mmkv.getString("ssl_pwd", null)?.toCharArray() + } + + fun sslPrivatePwd(): String? { + return mmkv.getString("ssl_private_pwd", null) + } + + fun sslAlias(): String? { + return mmkv.getString("ssl_alias", null) + } + + fun getSslPort(): Int { + return mmkv.getInt("ssl_port", getPort()) + } + + fun isDev(): Boolean { + return mmkv.getBoolean("dev", false) + } + + operator fun set(key: String, value: String) { + mmkv.putString(key, value) + } + + operator fun set(key: String, value: Boolean) { + mmkv.putBoolean(key, value) + } + + operator fun set(key: String, value: Int) { + mmkv.putInt(key, value) + } + + operator fun set(key: String, value: Long) { + mmkv.putLong(key, value) + } + + operator fun set(key: String, value: Float) { + mmkv.putFloat(key, value) + } + + fun isAntiTrace(): Boolean { + return config.antiTrace + } + + fun allowShell(): Boolean { + return mmkv.getBoolean("shell", false) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt index f30c327..bbd7b18 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt @@ -8,6 +8,7 @@ import com.tencent.qqnt.kernelpublic.nativeinterface.Contact import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import moe.fuqiuluo.qqinterface.servlet.GroupSvc import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler @@ -21,8 +22,9 @@ import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource import moe.fuqiuluo.shamrock.remote.service.data.push.PostType +import moe.fuqiuluo.shamrock.utils.PlatformUtils +import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER import java.util.ArrayList -import java.util.Collections import kotlin.collections.HashMap internal object AioListener : IKernelMsgListener { @@ -63,7 +65,9 @@ internal object AioListener : IKernelMsgListener { if (ShamrockConfig.aliveReply() && rawMsg == "ping") { MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "pong", { _, _ -> }) - } + } /*else if (ShamrockConfig.aliveReply() && rawMsg == ".shamrock.at_me") { + MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "[CQ:at,qq=${record.senderUin}]", { _, _ -> }) + }*/ val postType = if (record.senderUin == TicketSvc.getLongUin() && ShamrockConfig.enableSyncMsgAsSentMsg()) { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt index bba790b..da6a151 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt @@ -1,5 +1,6 @@ package moe.fuqiuluo.shamrock.remote.service.listener +import com.tencent.qphone.base.remote.FromServiceMsg import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -24,7 +25,6 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.readBuf32Long import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.xposed.helper.PacketHandler import moe.fuqiuluo.symbols.decodeProtobuf import protobuf.message.ContentHead import protobuf.message.MsgBody @@ -32,16 +32,16 @@ import protobuf.message.ResponseHead import protobuf.push.* internal object PrimitiveListener { - fun registerListener() { - PacketHandler.register("trpc.msg.olpush.OlPushService.MsgPush") { _, buffer -> + fun onPush(fromServiceMsg: FromServiceMsg) { + if (fromServiceMsg.wupBuffer == null) return + try { + val push = fromServiceMsg.wupBuffer.slice(4) + .decodeProtobuf() GlobalScope.launch { - try { - val push = buffer.slice(4).decodeProtobuf() - onMsgPush(push) - } catch (e: Exception) { - LogCenter.log(e.stackTraceToString(), Level.WARN) - } + onMsgPush(push) } + } catch (e: Exception) { + LogCenter.log(e.stackTraceToString(), Level.WARN) } } @@ -188,6 +188,8 @@ internal object PrimitiveListener { } private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) { + if (body.msgContent == null) return + val event = runCatching { body.msgContent!!.decodeProtobuf() }.getOrElse { @@ -200,6 +202,7 @@ internal object PrimitiveListener { }.decodeProtobuf() } val groupId = event.groupCode.toLong() + if (event.uniqueTitleChangeDetail == null) return val detail = event.uniqueTitleChangeDetail!!.first() //detail = if (detail[5] is ProtoList) { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/AndroidX.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/AndroidX.kt new file mode 100644 index 0000000..52d99ad --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/AndroidX.kt @@ -0,0 +1,16 @@ +package moe.fuqiuluo.shamrock.tools + +import android.content.Context +import android.os.Handler +import android.widget.Toast +import de.robv.android.xposed.XposedBridge + +lateinit var GlobalUi: Handler + +internal fun Context.toast(msg: String, flag: Int = Toast.LENGTH_SHORT) { + XposedBridge.log(msg) + if (!::GlobalUi.isInitialized) { + return + } + GlobalUi.post { Toast.makeText(this, msg, flag).show() } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt new file mode 100644 index 0000000..9e81abc --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt @@ -0,0 +1,59 @@ +package moe.fuqiuluo.shamrock.tools + +import com.tencent.mobileqq.pb.MessageMicro +import com.tencent.qphone.base.remote.FromServiceMsg +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.utils.DeflateTools +import moe.fuqiuluo.symbols.Protobuf +import moe.fuqiuluo.symbols.decodeProtobuf +import protobuf.oidb.TrpcOidb +import tencent.im.oidb.oidb_sso + +fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg { + return kotlin.runCatching { + oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.slice(4).let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }) + }.getOrElse { + oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }) + } +} + +fun FromServiceMsg.decodeToTrpcOidb(): TrpcOidb { + return kotlin.runCatching { + wupBuffer.slice(4).let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }.decodeProtobuf() + }.getOrElse { + wupBuffer.let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }.decodeProtobuf() + } +} + +inline fun > FromServiceMsg.decodeToObject(): T { + return kotlin.runCatching { + wupBuffer.slice(4).let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }.decodeProtobuf() + }.getOrElse { + wupBuffer.let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }.decodeProtobuf() + } +} + +fun > FromServiceMsg.decodeToObject(self: T): T { + return kotlin.runCatching { + self.mergeFrom(wupBuffer.slice(4).let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }) + }.getOrElse { + self.mergeFrom(wupBuffer.let { + if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it + }) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/PlatformUtils.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/PlatformUtils.kt index cb8af55..03e4034 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/PlatformUtils.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/PlatformUtils.kt @@ -16,7 +16,8 @@ import mqq.app.MobileQQ import kotlin.random.Random internal object PlatformUtils { - const val QQ_9_0_8_VER = 5540 + const val QQ_9_0_8_VER = 5540 + const val QQ_9_0_65_VER = 6566 fun getQUA(): String { return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D" diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/XposedEntry.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/XposedEntry.kt index 6bd1b3c..393bee9 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/XposedEntry.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/XposedEntry.kt @@ -1,18 +1,19 @@ package moe.fuqiuluo.shamrock.xposed import android.content.Context +import android.os.Build +import android.os.Handler import android.os.Process import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.XposedBridge.log -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.utils.MMKVFetcher import moe.fuqiuluo.shamrock.xposed.loader.KeepAlive import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader import moe.fuqiuluo.shamrock.tools.FuzzySearchClass +import moe.fuqiuluo.shamrock.tools.GlobalUi import moe.fuqiuluo.shamrock.tools.afterHook import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.xposed.hooks.runFirstActions @@ -156,6 +157,13 @@ internal class XposedEntry: IXposedHookLoadPackage { log("Process Name = $processName") + + GlobalUi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + Handler.createAsync(ctx.mainLooper) + } else { + Handler(ctx.mainLooper) + } + runFirstActions(ctx) } } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt index 09e2bfc..abb71d3 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt @@ -6,8 +6,8 @@ import mqq.app.MobileQQ import kotlin.random.Random internal object AppTalker { - val uriName = "content://moe.fuqiuluo.108.provider" // 你是真的闲,这都上个检测 - val URI = Uri.parse(uriName) + private const val uriName = "content://moe.fuqiuluo.108.provider" // 你是真的闲,这都上个检测 + private val URI = Uri.parse(uriName) fun talk(values: ContentValues, onFailure: ((Throwable) -> Unit)? = null) { val ctx = MobileQQ.getContext() @@ -17,4 +17,20 @@ internal object AppTalker { onFailure?.invoke(e) } } + + fun talk(action: String, bodyBuilder: ContentValues.() -> Unit) { + val values = ContentValues() + values.put("__cmd", action) + values.put("__hash", 0) + bodyBuilder.invoke(values) + talk(values) + } + + fun talk(action: String, onFailure: ((Throwable) -> Unit)? = null, bodyBuilder: ContentValues.() -> Unit = {}) { + val values = ContentValues() + values.put("__cmd", action) + values.put("__hash", 0) + bodyBuilder.invoke(values) + talk(values, onFailure) + } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/MSFHandler.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/MSFHandler.kt new file mode 100644 index 0000000..b2baa6e --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/MSFHandler.kt @@ -0,0 +1,74 @@ +package moe.fuqiuluo.shamrock.xposed.helper + +import com.tencent.qphone.base.remote.FromServiceMsg +import com.tencent.qphone.base.remote.ToServiceMsg +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.remote.service.listener.PrimitiveListener +import kotlin.coroutines.resume + +typealias MsfPush = (FromServiceMsg) -> Unit +typealias MsfResp = CancellableContinuation> + +internal object MSFHandler { + private val mPushHandlers = hashMapOf() + private val mRespHandler = hashMapOf() + private val mPushLock = Mutex() + private val mRespLock = Mutex() + + private val seq = atomic(0) + + fun nextSeq(): Int { + seq.compareAndSet(0xFFFFFFF, 0) + return seq.incrementAndGet() + } + + suspend fun registerPush(cmd: String, push: MsfPush) { + mPushLock.withLock { + mPushHandlers[cmd] = push + } + } + + suspend fun unregisterPush(cmd: String) { + mPushLock.withLock { + mPushHandlers.remove(cmd) + } + } + + suspend fun registerResp(cmd: Int, resp: MsfResp) { + mRespLock.withLock { + mRespHandler[cmd] = resp + } + } + + suspend fun unregisterResp(cmd: Int) { + mRespLock.withLock { + mRespHandler.remove(cmd) + } + } + + fun onPush(fromServiceMsg: FromServiceMsg) { + val cmd = fromServiceMsg.serviceCmd + if (cmd == "trpc.msg.olpush.OlPushService.MsgPush") { + PrimitiveListener.onPush(fromServiceMsg) + } else { + val push = mPushHandlers[cmd] + push?.invoke(fromServiceMsg) + } + } + + fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) { + runCatching { + val cmd = toServiceMsg.getAttribute("shamrock_uid") as? Int? + ?: return@runCatching + val resp = mRespHandler[cmd] + resp?.resume(toServiceMsg to fromServiceMsg) + }.onFailure { + LogCenter.log("MSF.onResp failed: ${it.message}", Level.ERROR) + } + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt index 471a184..d453c4a 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.service.PacketReceiver import moe.fuqiuluo.shamrock.remote.service.listener.AioListener import moe.fuqiuluo.shamrock.remote.service.listener.PrimitiveListener import moe.fuqiuluo.shamrock.tools.hookMethod @@ -28,9 +27,6 @@ internal object NTServiceFetcher { val curHash = service.hashCode() + msgService.hashCode() if (isInitForNt(curHash)) return - PacketHandler.initPacketHandler() - PacketReceiver.init() - LogCenter.log("Fetch kernel service successfully: $curKernelHash,$curHash,${PlatformUtils.isMainProcess()}") curKernelHash = curHash this.iKernelService = service @@ -38,7 +34,6 @@ internal object NTServiceFetcher { initNTKernelListener(msgService) antiBackgroundMode(sessionService) - //hookGuildListener(sessionService) } } @@ -70,7 +65,7 @@ internal object NTServiceFetcher { //groupService.addKernelGroupListener(GroupEventListener) //LogCenter.log("Register Group listener successfully.") - PrimitiveListener.registerListener() + //PrimitiveListener.registerListener() } catch (e: Throwable) { LogCenter.log(e.stackTraceToString(), Level.WARN) } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt deleted file mode 100644 index 5326364..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.helper - -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import mqq.app.MobileQQ - -internal object PacketHandler { - /* - MSF 进程包处理是否就绪 - */ - var isInit = false - - internal fun initPacketHandler() { - DynamicReceiver.register("msf_waiter", IPCRequest { - isInit = true - }) - } - - /** - * 注册常驻包处理器 - */ - fun register(cmd: String, callback: (Int, ByteArray) -> Unit) { - // 在本地广播接收器注册对应处理器 - DynamicReceiver.register(cmd, IPCRequest { - val buffer = it.getByteArrayExtra("buffer")!! - val seq = it.getIntExtra("seq", 0) - callback(seq, buffer) - }) - if (!isInit) return - // 向MSF进程广播要求添加处理器 - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "register_handler_cmd") - putExtra("handler_cmd", cmd) - } - } - - suspend fun registerLessHandler(cmd: String, seq: Int, callback: (Int, ByteArray) -> Unit): Int { - DynamicReceiver.register(IPCRequest(cmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - val currSeq = it.getIntExtra("seq", 0) - callback(currSeq, buffer) - }) - return seq - } - - suspend fun unregisterLessHandler(seq: Int) { - DynamicReceiver.unregister(seq) - } - - fun unregister(cmd: String) { - DynamicReceiver.unregister(cmd) - if (!isInit) return - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "unregister_handler_cmd") - putExtra("handler_cmd", cmd) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/QQInterfaces.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/QQInterfaces.kt new file mode 100644 index 0000000..c5f0ae3 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/QQInterfaces.kt @@ -0,0 +1,145 @@ +package moe.fuqiuluo.shamrock.xposed.helper + +import android.os.Bundle +import com.tencent.common.app.AppInterface +import com.tencent.mobileqq.pb.ByteStringMicro +import com.tencent.qphone.base.remote.FromServiceMsg +import com.tencent.qphone.base.remote.ToServiceMsg +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import moe.fuqiuluo.shamrock.utils.PlatformUtils +import mqq.app.MobileQQ +import protobuf.auto.toByteArray +import protobuf.oidb.TrpcOidb +import tencent.im.oidb.oidb_sso +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +abstract class QQInterfaces { + companion object { + val app = (if (PlatformUtils.isMqqPackage()) + MobileQQ.getMobileQQ().waitAppRuntime() + else + MobileQQ.getMobileQQ().waitAppRuntime(null)) as AppInterface + + val currentUin: String + get() = app.currentAccountUin + + fun sendToServiceMsg(to: ToServiceMsg) { + app.sendToService(to) + } + + suspend fun sendToServiceMsgAW( + to: ToServiceMsg, + timeout: Duration = 30.seconds + ): FromServiceMsg? { + val seq = MSFHandler.nextSeq() + to.addAttribute("shamrock_uid", seq) + val resp: Pair? = withTimeoutOrNull(timeout) { + suspendCancellableCoroutine { continuation -> + GlobalScope.launch { + MSFHandler.registerResp(seq, continuation) + sendToServiceMsg(to) + } + } + } + if (resp == null) { + MSFHandler.unregisterResp(seq) + } + return resp?.second + } + + fun sendExtra(cmd: String, builder: (Bundle) -> Unit) { + val toServiceMsg = createToServiceMsg(cmd) + builder(toServiceMsg.extraData) + app.sendToService(toServiceMsg) + } + + fun createToServiceMsg(cmd: String): ToServiceMsg { + return ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd) + } + + fun sendOidb(cmd: String, command: Int, service: Int, data: ByteArray, trpc: Boolean = false) { + val to = createToServiceMsg(cmd) + if (trpc) { + val oidb = TrpcOidb( + cmd = command, + service = service, + buffer = data, + flag = 1 + ) + to.putWupBuffer(oidb.toByteArray()) + } else { + val oidb = oidb_sso.OIDBSSOPkg() + oidb.uint32_command.set(command) + oidb.uint32_service_type.set(service) + oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(data)) + oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext())) + to.putWupBuffer(oidb.toByteArray()) + } + to.addAttribute("req_pb_protocol_flag", true) + to.addAttribute("im_shamrock", true) + app.sendToService(to) + } + + fun sendBuffer( + cmd: String, + isProto: Boolean, + data: ByteArray, + ) { + val toServiceMsg = createToServiceMsg(cmd) + toServiceMsg.putWupBuffer(data) + toServiceMsg.addAttribute("req_pb_protocol_flag", isProto) + toServiceMsg.addAttribute("im_shamrock", true) + sendToServiceMsg(toServiceMsg) + } + + @DelicateCoroutinesApi + suspend fun sendBufferAW( + cmd: String, + isProto: Boolean, + data: ByteArray, + timeout: Duration = 30.seconds + ): FromServiceMsg? { + val toServiceMsg = createToServiceMsg(cmd) + toServiceMsg.putWupBuffer(data) + toServiceMsg.addAttribute("req_pb_protocol_flag", isProto) + toServiceMsg.addAttribute("im_shamrock", true) + return sendToServiceMsgAW(toServiceMsg, timeout) + } + + @DelicateCoroutinesApi + suspend fun sendOidbAW( + cmd: String, + command: Int, + service: Int, + data: ByteArray, + trpc: Boolean = false, + timeout: Duration = 30.seconds + ): FromServiceMsg? { + val to = createToServiceMsg(cmd) + if (trpc) { + val oidb = TrpcOidb( + cmd = command, + service = service, + buffer = data, + flag = 1 + ) + to.putWupBuffer(oidb.toByteArray()) + } else { + val oidb = oidb_sso.OIDBSSOPkg() + oidb.uint32_command.set(command) + oidb.uint32_service_type.set(service) + oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(data)) + oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext())) + to.putWupBuffer(oidb.toByteArray()) + } + to.addAttribute("req_pb_protocol_flag", true) + to.addAttribute("im_shamrock", true) + return sendToServiceMsgAW(to, timeout) + } + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt deleted file mode 100644 index f2a66c9..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt +++ /dev/null @@ -1,113 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.xposed.helper.internal - -import android.content.ContentValues -import android.content.Intent -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.xposed.helper.AppTalker -import java.util.Timer -import kotlin.concurrent.timer - -/** - * 数据请求中心 - * 支持应用内数据传递,以及外部向内部传入 - */ -object DataRequester { - private val seqFactory = atomic(0) - private val seq: Int - get() { - if (seqFactory.value > 1000000) { - seqFactory.lazySet(0) - } - return seqFactory.incrementAndGet() - } - - suspend fun request( - cmd: String, - currSeq: Int = seq, - values: Map? = null, - onFailure: ((Throwable) -> Unit)? = null, - callback: ICallback? = null - ): Int { - return request(cmd, currSeq, bodyBuilder = { - values?.forEach { (key, value) -> - when (value) { - is Int -> this.put(key, value) - is Long -> this.put(key, value) - is Short -> this.put(key, value) - is Byte -> this.put(key, value) - is String -> this.put(key, value) - is ByteArray -> this.put(key, value) - is Boolean -> this.put(key, value) - is Float -> this.put(key, value) - is Double -> this.put(key, value) - } - } - }, onFailure, callback) - } - - suspend fun request( - cmd: String, - currentSeq: Int = seq, - bodyBuilder: (ContentValues.() -> Unit)? = null, - onFailure: ((Throwable) -> Unit)? = null, - callback: ICallback? = null - ): Int { - val values = ContentValues() - bodyBuilder?.invoke(values) - values.put("__hash", (cmd + currentSeq).hashCode()) - values.put("__cmd", cmd) - AppTalker.talk(values, onFailure) - if (callback != null) { - val timer: Timer = timer(initialDelay = 3000L, period = 5000L) { - GlobalScope.launch(Dispatchers.Default) { - DynamicReceiver.unregister(currentSeq) - cancel() - } - } - val request = IPCRequest(cmd, currentSeq, values) { - try { - timer.cancel() - } finally { - callback.handle(it) - } - } - DynamicReceiver.register(request) - } - return currentSeq - } -} - -fun interface ICallback { - suspend fun handle(intent: Intent) -} - -data class IPCRequest( - val cmd: String = "", - val seq: Int = -1, - val values: ContentValues? = null, - var callback: ICallback? = null, -) { - override fun hashCode(): Int { - return (cmd + seq).hashCode() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as IPCRequest - - if (cmd != other.cmd) return false - if (seq != other.seq) return false - if (values != other.values) return false - if (callback != other.callback) return false - - return true - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt deleted file mode 100644 index aa04160..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt +++ /dev/null @@ -1,78 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.helper.internal - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter - -/** - * 动态广播 - */ -internal object DynamicReceiver: BroadcastReceiver() { - private val hashHandler = mutableSetOf() - private val cmdHandler = mutableMapOf() - private val mutex = Mutex() // 滥用的锁,尽量减少使用 - - override fun onReceive(ctx: Context, intent: Intent) { - GlobalScope.launch(Dispatchers.Default) { - val hash = intent.getIntExtra("__hash", -1) - val cmd = intent.getStringExtra("__cmd") ?: "" - try { - if (cmd.isNotBlank()) { - cmdHandler[cmd].also { - if (it == null) - LogCenter.log("无常驻包处理器: $cmd, main = ${PlatformUtils.isMainProcess()}", Level.ERROR) - }?.callback?.handle(intent) - } else if (hash != -1) { - mutex.withLock { - hashHandler.removeIf { - if (hash == it.hashCode()) { - GlobalScope.launch { - it.callback?.handle(intent) - } - return@removeIf it.seq != -1 - } - return@removeIf false - } - } - } - } catch (e: Throwable) { - LogCenter.log("包处理器[$cmd]错误: $e", Level.ERROR) - } - } - } - - fun register(cmd: String, request: IPCRequest) { - cmdHandler[cmd] = request - } - - fun unregister(cmd: String) { - cmdHandler.remove(cmd) - } - - /*** - * 注册临时包处理器 - */ - suspend fun register(request: IPCRequest) { - LogCenter.log({ "registerHandler[${request.hashCode()}](cmd = ${request.cmd}, seq = ${request.seq})" }, Level.DEBUG) - - mutex.withLock { - hashHandler.add(request) - } - } - - suspend fun unregister(seq: Int) { - mutex.withLock { - hashHandler.removeIf { it.seq == seq } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt deleted file mode 100644 index fedd7b7..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt +++ /dev/null @@ -1,67 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.annotation.SuppressLint -import android.content.Context -import android.content.IntentFilter -import android.os.Build -import android.os.Handler -import android.widget.Toast -import de.robv.android.xposed.XposedBridge -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.symbols.XposedHook -import mqq.app.MobileQQ - -internal lateinit var GlobalUi: Handler - -internal fun Context.toast(msg: String, flag: Int = Toast.LENGTH_SHORT) { - XposedBridge.log(msg) - if (!::GlobalUi.isInitialized) { - return - } - GlobalUi.post { Toast.makeText(this, msg, flag).show() } -} - -@XposedHook(priority = 0) -internal class DataReceiver: IAction { - @SuppressLint("UnspecifiedRegisterReceiverFlag") - override fun invoke(ctx: Context) { - kotlin.runCatching { - MobileQQ.getMobileQQ().unregisterReceiver(DynamicReceiver) - } - - if (PlatformUtils.isMainProcess()) { - GlobalUi = Handler(ctx.mainLooper) - GlobalScope.launch { - val intentFilter = IntentFilter() - intentFilter.addAction("moe.fuqiuluo.xqbot.dynamic") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - MobileQQ.getMobileQQ().registerReceiver( - DynamicReceiver, intentFilter, - Context.RECEIVER_EXPORTED - ) - } else { - MobileQQ.getMobileQQ().registerReceiver(DynamicReceiver, intentFilter) - } - XposedBridge.log("Register Main::Broadcast successfully.") - } - } else if (PlatformUtils.isMsfProcess()) { - val intentFilter = IntentFilter() - intentFilter.addAction("moe.fuqiuluo.msf.dynamic") - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - MobileQQ.getMobileQQ().registerReceiver( - DynamicReceiver, intentFilter, - Context.RECEIVER_EXPORTED - ) - } else { - MobileQQ.getMobileQQ().registerReceiver(DynamicReceiver, intentFilter) - } - } - } -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DynamicBroadcast.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DynamicBroadcast.kt new file mode 100644 index 0000000..9c2cc49 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DynamicBroadcast.kt @@ -0,0 +1,58 @@ +package moe.fuqiuluo.shamrock.xposed.hooks + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import de.robv.android.xposed.XposedBridge +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.xposed.hooks.interacts.Init +import moe.fuqiuluo.shamrock.xposed.hooks.interacts.SwitchStatus +import moe.fuqiuluo.shamrock.xposed.hooks.interacts.UpdateConfig +import moe.fuqiuluo.symbols.Process +import moe.fuqiuluo.symbols.XposedHook +import mqq.app.MobileQQ + +@XposedHook(priority = 1, process = Process.MAIN) +class DynamicBroadcast: IAction { + @SuppressLint("UnspecifiedRegisterReceiverFlag") + override fun invoke(ctx: Context) { + val intentFilter = IntentFilter() + intentFilter.addAction("moe.fuqiuluo.onebot.dynamic") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + MobileQQ.getMobileQQ().registerReceiver( + DynamicReceiver, intentFilter, + Context.RECEIVER_EXPORTED + ) + } else { + MobileQQ.getMobileQQ().registerReceiver(DynamicReceiver, intentFilter) + } + LogCenter.log("Register Main::Broadcast successfully.") + } + + private object DynamicReceiver: BroadcastReceiver() { + private val handlers = mapOf( + "init" to Init, + "switch_status" to SwitchStatus, + "push_config" to UpdateConfig + ) + + override fun onReceive(context: Context, intent: Intent) { + val cmd = intent.getStringExtra("__cmd") ?: "" + if (cmd.isBlank()) { + LogCenter.log("DynamicReceiver.onReceive: cmd is blank", Level.ERROR) + return + } + //LogCenter.log("DynamicReceiver.onReceive: cmd=$cmd") + val handler = handlers[cmd] + if (handler == null) { + LogCenter.log("DynamicReceiver.onReceive: unknown cmd=$cmd", Level.ERROR) + } else { + handler(intent) + } + } + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt index 1eaa4ce..8b536d4 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt @@ -7,7 +7,7 @@ import moe.fuqiuluo.symbols.XposedHook @XposedHook(priority = 0) internal class FixLibraryLoad: IAction { - val redirectedLibrary =arrayOf( + private val redirectedLibrary =arrayOf( "ffmpegkit_abidetect", "avutil", "swscale", diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt deleted file mode 100644 index b3805d5..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt +++ /dev/null @@ -1,139 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import com.tencent.msf.service.protocol.pb.SSOLoginMerge -import com.tencent.qphone.base.remote.FromServiceMsg -import com.tencent.qphone.base.remote.ToServiceMsg -import com.tencent.qphone.base.util.CodecWarpper -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.DelicateCoroutinesApi -import moe.fuqiuluo.shamrock.remote.service.PacketReceiver -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import moe.fuqiuluo.symbols.XposedHook - -private const val MAGIC_APP_ID = 114514 - -@XposedHook(priority = 2) -internal class HookWrapperCodec: IAction { - private val IgnoredCmd = arrayOf( - "trpc.sq_adv.official_account_adv_push.OfficialAccountAdvPush.AdvPush", - "LightAppSvc.mini_app_report_transfer.DataReport", - "JsApiSvr.webview.whitelist", - "trpc.commercial.access.access_sso.SsoAdGet", - "trpc.qpay.homepage2.Homepage2.SsoGetHomepage", - "trpc.qpay.value_added_info.Query.SsoGetPrivilege", - "trpc.qqshop.qgghomepage.Config.SsoGetBottomTab", - "ClubInfoSvc.queryPrivExt", - "OidbSvc.0xcf8", - "LbsSvc.lbs_report", - "OidbSvcTrpcTcp.0x88d_0", - "trpc.down.game_switch.GameSwitch.SsoGetDownloadConfig", - "trpc.zplan.aio_avatar.Mobile.SsoBatchGetSceneConfig", - "OidbSvcTrpcTcp.0x1127_5", - "OidbSvcTrpcTcp.0x10aa", - "WalletConfigSvr.getConfig", - "LightAppSvc.mini_app_userapp.GetDropdownAppList", - "LightAppSvc.mini_app_ad.GetAd", - "SQQzoneSvc.advReport", - "OidbSvc.0xbcb_0", // 内部浏览器URL检测 - //"ConfigurationService.ReqGetConfig" - ) - - override fun invoke(ctx: Context) { - try { - ToServiceMsg::class.java.hookMethod("setRequestSsoSeq").before { - val to = it.thisObject as ToServiceMsg - to.getAttribute("shamrock_seq")?.let { seq -> - it.args[0] = seq - } - } - - val isInit = atomic(false) - CodecWarpper::class.java.hookMethod("init").after { - if (isInit.value) return@after - hookReceive(it.thisObject, it.thisObject.javaClass) - isInit.lazySet(true) - } - CodecWarpper::class.java.hookMethod("nativeOnReceData").before { - if (isInit.value) return@before - hookReceive(it.thisObject, it.thisObject.javaClass) - isInit.lazySet(true) - } - } catch (e: Exception) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } - - private fun hookReceive(thiz: Any, thizClass: Class<*>) { - val onResponse = thizClass.getDeclaredMethod("onResponse", Integer.TYPE, Any::class.java, Integer.TYPE, ByteArray::class.java) - //LogCenter.log("HookWrapperCodec: onResponse = $onResponse", Level.INFO) - DynamicReceiver.register("fake_packet", IPCRequest { - val uin = it.getStringExtra("package_uin")!! - val cmd = it.getStringExtra("package_cmd")!! - val seq = it.getIntExtra("package_seq", 0) - val buffer = it.getByteArrayExtra("package_buffer")!! - //LogCenter.log("伪造收包(cmd = $cmd)") - - val from = FromServiceMsg() - from.requestSsoSeq = seq - from.putWupBuffer(buffer) - from.serviceCmd = cmd - from.appId = MAGIC_APP_ID - from.setMsgSuccess() - from.uin = uin - from.appSeq = seq - onResponse.invoke(thiz, 0, from, 0, ByteArray(0)) - }) - thizClass.hookMethod("onResponse").before { - val from = it.args[1] as FromServiceMsg - try { - if ("SSO.LoginMerge" == from.serviceCmd) { - val merge = SSOLoginMerge.BusiBuffData() - .mergeFrom(from.wupBuffer.slice(4)) - val busiBufVec = merge.BusiBuffVec.get() - busiBufVec.forEach { item -> - if (item.ServiceCmd.get() in IgnoredCmd && ShamrockConfig.isInjectPacket()) { - busiBufVec.remove(item) - } else { - pushOnReceive(FromServiceMsg().apply { - this.requestSsoSeq = item.SeqNo.get() - this.serviceCmd = item.ServiceCmd.get() - putWupBuffer(item.BusiBuff.get().toByteArray()) - }) - } - } - merge.BusiBuffVec.set(busiBufVec) - from.putWupBuffer(merge.toByteArray()) - } else if (from.appId != MAGIC_APP_ID) { - if (from.serviceCmd in IgnoredCmd && ShamrockConfig.isInjectPacket()) { - //from.serviceCmd = "ShamrockInjectedCmd_${from.serviceCmd}" - from.setBusinessFail(1) - from.putWupBuffer(EMPTY_BYTE_ARRAY) - } else if (from.serviceCmd.startsWith("trpc.o3.") && ShamrockConfig.isInjectPacket()) { - from.setBusinessFail(1) - from.putWupBuffer(EMPTY_BYTE_ARRAY) - } else { - pushOnReceive(from) - } - } - } finally { - it.args[1] = from - } - } - } - - private fun pushOnReceive(fromServiceMsg: FromServiceMsg) { - PacketReceiver.internalOnReceive(fromServiceMsg) - } -} - - - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt index 3de2ca2..a380316 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt @@ -20,8 +20,6 @@ import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.XposedHook import mqq.app.MobileQQ -import kotlin.concurrent.timer -import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @XposedHook(Process.MAIN, priority = 10) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt deleted file mode 100644 index 8777ee1..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt +++ /dev/null @@ -1,44 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import android.os.Bundle -import kotlinx.coroutines.DelicateCoroutinesApi -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.* -import moe.fuqiuluo.shamrock.xposed.ipc.ShamrockIpc -import moe.fuqiuluo.symbols.Process -import moe.fuqiuluo.symbols.XposedHook - -@XposedHook(Process.MSF, priority = 0) -internal class IpcService: IAction { - override fun invoke(ctx: Context) { - if (!PlatformUtils.isMsfProcess()) return - initIPCFetcher(ctx) - } - - private fun initIPCFetcher(ctx: Context) { - LogCenter.log("IPC服务已启动:$ctx", Level.INFO) - DynamicReceiver.register("fetch_ipc", IPCRequest { - val name = it.getStringExtra("ipc_name") - LogCenter.log("IPC FETCH => $name,请注意是否泄露了API?") - GlobalScope.launch { - ShamrockIpc.get(name)?.also { binder -> - ctx.broadcast("xqbot") { - putExtra("__cmd", "ipc_callback") - putExtra("ipc", Bundle().also { - it.putString("name", name) - it.putBinder("binder", binder) - }) - } - } ?: LogCenter.log("无法获取IPC: $name", Level.WARN) - } - }) - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PatchMsfCore.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PatchMsfCore.kt new file mode 100644 index 0000000..9cdc2bf --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PatchMsfCore.kt @@ -0,0 +1,44 @@ +package moe.fuqiuluo.shamrock.xposed.hooks + +import android.content.Context +import com.tencent.common.app.AppInterface +import com.tencent.mobileqq.msf.sdk.MsfMessagePair +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.tools.hookMethod +import moe.fuqiuluo.shamrock.xposed.helper.MSFHandler.onPush +import moe.fuqiuluo.shamrock.xposed.helper.MSFHandler.onResp +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces +import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader +import moe.fuqiuluo.symbols.XposedHook + + +@XposedHook(priority = 10) +class PatchMsfCore: IAction { + override fun invoke(ctx: Context) { + val app = QQInterfaces.app + require(app is AppInterface) { "QQInterface.app must be AppInterface" } + + runCatching { + val MSFRespHandleTask = LuoClassloader.load("mqq.app.msghandle.MSFRespHandleTask") + if (MSFRespHandleTask == null) { + LogCenter.log("无法注入MSFRespHandleTask!", Level.ERROR) + } else { + val msfPair = MSFRespHandleTask.declaredFields.first { + it.type == MsfMessagePair::class.java + } + msfPair.isAccessible = true + MSFRespHandleTask.hookMethod("run").before { + val pair = msfPair.get(it.thisObject) as MsfMessagePair + if (pair.toServiceMsg == null) { + onPush(pair.fromServiceMsg) + } else { + onResp(pair.toServiceMsg, pair.fromServiceMsg) + } + } + } + }.onFailure { + LogCenter.log(it.stackTraceToString(), Level.ERROR) + } + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt index f2f522c..f769670 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt @@ -4,92 +4,39 @@ package moe.fuqiuluo.shamrock.xposed.hooks import android.content.Context import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.remote.HTTPServer import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig +import moe.fuqiuluo.shamrock.tools.toast import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader +import moe.fuqiuluo.shamrock.xposed.helper.AppTalker import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.XposedHook -import mqq.app.MobileQQ -import kotlin.concurrent.thread import kotlin.system.exitProcess +import kotlin.time.Duration.Companion.seconds @XposedHook(Process.MAIN, priority = 1) class PullConfig: IAction { companion object { - @JvmStatic - var isConfigOk = false + external fun testNativeLibrary(): String } - private external fun testNativeLibrary(): String - override fun invoke(ctx: Context) { if (!PlatformUtils.isMainProcess()) return - GlobalScope.launch(Dispatchers.Default) { - DynamicReceiver.register("fetchPort", IPCRequest { - DataRequester.request("success", values = mapOf( - "port" to HTTPServer.currServerPort, - "voice" to NativeLoader.isVoiceLoaded - )) - }) - DynamicReceiver.register("checkAndStartService", IPCRequest { - if (HTTPServer.isServiceStarted) { - HTTPServer.isServiceStarted = false + val isInit = ShamrockConfig.isInit() + AppTalker.talk("init", onFailure = { + if (isInit) { + ctx.toast("Shamrock主进程未启动,将不会同步配置!") + } else { + ctx.toast("Shamrock主进程未启动,初始化失败!") + GlobalScope.launch { + delay(3.seconds) + exitProcess(1) } - initAppService(MobileQQ.getContext()) - }) - DynamicReceiver.register("push_config", IPCRequest { - ctx.toast("动态推送配置文件成功。") - ShamrockConfig.updateConfig(it) - }) - DynamicReceiver.register("change_port", IPCRequest { - when (it.getStringExtra("type")) { - "port" -> { - ctx.toast("动态修改HTTP端口成功。") - HTTPServer.changePort(it.getIntExtra("port", 5700)) - } - "ws_port" -> { - ctx.toast("动态修改WS端口不支持。") - } - "restart" -> { - if(HTTPServer.isServiceStarted) { - ctx.toast("重启HTTPServer完成。") - HTTPServer.restart() - } - } - } - }) - - DataRequester.request("init", onFailure = { - if (!ShamrockConfig.isInit()) { - ctx.toast("请启动Shamrock主进程以初始化服务,进程将退出。") - ShamrockConfig.putDefaultSettings() - thread { - Thread.sleep(3000) - exitProcess(1) - } - } else { - ctx.toast("Shamrock进程未启动,不会推送配置文件。") - initAppService(ctx) - } - }, bodyBuilder = null) { - isConfigOk = true - ShamrockConfig.updateConfig(it) - initAppService(ctx) } - } - } - - private fun initAppService(ctx: Context) { - NativeLoader.load("shamrock") - ctx.toast(testNativeLibrary()) - runServiceActions(ctx) + }) + ctx.toast("同步配置中...") } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/IInteract.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/IInteract.kt new file mode 100644 index 0000000..6ee4954 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/IInteract.kt @@ -0,0 +1,7 @@ +package moe.fuqiuluo.shamrock.xposed.hooks.interacts + +import android.content.Intent + +interface IInteract { + operator fun invoke(intent: Intent) +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/Init.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/Init.kt new file mode 100644 index 0000000..e56e240 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/Init.kt @@ -0,0 +1,25 @@ +package moe.fuqiuluo.shamrock.xposed.hooks.interacts + +import android.content.Context +import android.content.Intent +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig +import moe.fuqiuluo.shamrock.tools.toast +import moe.fuqiuluo.shamrock.xposed.hooks.PullConfig +import moe.fuqiuluo.shamrock.xposed.hooks.runServiceActions +import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader +import mqq.app.MobileQQ + +internal object Init: IInteract { + override fun invoke(intent: Intent) { + ShamrockConfig.updateConfig(intent) + initAppService(MobileQQ.getMobileQQ()) + } + + private fun initAppService(ctx: Context) { + LogCenter.log("载入依赖库...少女努力中...", toast = true) + //NativeLoader.load("shamrock") + //ctx.toast(PullConfig.testNativeLibrary()) + runServiceActions(ctx) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/SwitchStatus.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/SwitchStatus.kt new file mode 100644 index 0000000..062d1c5 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/SwitchStatus.kt @@ -0,0 +1,21 @@ +package moe.fuqiuluo.shamrock.xposed.hooks.interacts + +import android.content.Intent +import com.tencent.mobileqq.app.QQAppInterface +import moe.fuqiuluo.shamrock.tools.ShamrockVersion +import moe.fuqiuluo.shamrock.xposed.helper.AppTalker +import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces +import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader + +object SwitchStatus: IInteract, QQInterfaces() { + override fun invoke(intent: Intent) { + if (app.isLogin) { + AppTalker.talk("switch_status") { + put("account", app.currentAccountUin) + put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown") + put("voice", NativeLoader.isVoiceLoaded) + put("core_version", ShamrockVersion) + } + } + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/UpdateConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/UpdateConfig.kt new file mode 100644 index 0000000..49dab3d --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/interacts/UpdateConfig.kt @@ -0,0 +1,18 @@ +package moe.fuqiuluo.shamrock.xposed.hooks.interacts + +import android.content.Context +import android.content.Intent +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig +import moe.fuqiuluo.shamrock.tools.toast +import moe.fuqiuluo.shamrock.xposed.hooks.PullConfig +import moe.fuqiuluo.shamrock.xposed.hooks.runServiceActions +import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader +import mqq.app.MobileQQ + +internal object UpdateConfig: IInteract { + override fun invoke(intent: Intent) { + MobileQQ.getContext().toast("动态更新配置成功") + ShamrockConfig.updateConfig(intent) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt deleted file mode 100644 index 19f801c..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.ipc - -import android.os.IBinder -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withTimeoutOrNull -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.ByteDataCreator -import moe.fuqiuluo.shamrock.xposed.ipc.qsign.QSignGenerator -import mqq.app.MobileQQ -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -internal object ShamrockIpc { - const val IPC_QSIGN = "qsign" - const val IPC_BYTEDATA = "bytedata" - - private val IpcChannel = hashMapOf( - IPC_QSIGN to QSignGenerator, - IPC_BYTEDATA to ByteDataCreator - ) - - suspend fun get(name: String?): IBinder? { - return if (PlatformUtils.isMsfProcess()) { - IpcChannel[name] - } else { - withTimeoutOrNull(3000) { - suspendCoroutine { continuation -> - DynamicReceiver.register("ipc_callback", IPCRequest { - val bundle = it.getBundleExtra("ipc")!! - val binder = bundle.getBinder("binder") - continuation.resume(binder) - }) - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "fetch_ipc") - putExtra("ipc_name", name) - } - } - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/NativeLoader.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/NativeLoader.kt index 35eaf94..eb863b3 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/NativeLoader.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/NativeLoader.kt @@ -47,14 +47,16 @@ internal object NativeLoader { */ fun load(name: String) { try { - if (name == "shamrock" - || (name == "clover" && isEmu) - ) { + if (name == "shamrock" || (name == "clover" && isEmu)) { onLoadByResource(name) } else if (!onLoadByAbsolutePath(name)) { onLoadByExternalFile(name) } } catch (e: Throwable) { + if (name == "shamrock" && onLoadByAbsolutePath(name)) { + return + } + LogCenter.log("LoadLibrary(name = $name) failed: ${e.stackTraceToString()}", Level.ERROR) XposedBridge.log(e) } } @@ -95,7 +97,7 @@ internal object NativeLoader { if (soDir.isFile) soDir.delete() if (!soDir.exists()) soDir.mkdirs() val soPath = getLibFilePath(name) - val soFile = File(soDir, name) + val soFile = File(soDir, "lib$name.so") fun reloadSo(tmp: File? = null) { LogCenter.log("SO文件大小不一致或不存在,正在重新加载", Level.INFO) soFile.delete() @@ -107,7 +109,7 @@ internal object NativeLoader { if (!soFile.exists()) { reloadSo() } else { - val tmpSoFile = File(soDir, "$name.tmp").also { file -> + val tmpSoFile = File(soDir, "lib$name.so.tmp").also { file -> if (file.exists()) file.delete() file.outputStream().use { moduleLoader.getResourceAsStream(soPath).use { origin ->