mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
fix #339
This commit is contained in:
parent
65ddca2ea4
commit
e2f27cb36a
@ -17,7 +17,7 @@ android {
|
|||||||
minSdk = 27
|
minSdk = 27
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = getVersionCode()
|
versionCode = getVersionCode()
|
||||||
versionName = "1.0.9" + ".r${getGitCommitCount()}." + getVersionName()
|
versionName = "1.1.1.onebot" + ".r${getGitCommitCount()}." + getVersionName()
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
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库成功~");
|
return env->NewStringUTF("加载Shamrock库成功~");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -64,6 +63,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||||
import moe.fuqiuluo.shamrock.ui.app.Logger
|
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.RANDOM_TITLE
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.ShamrockTheme
|
import moe.fuqiuluo.shamrock.ui.theme.ShamrockTheme
|
||||||
import moe.fuqiuluo.shamrock.ui.tools.NoIndication
|
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
|
import moe.fuqiuluo.shamrock.ui.tools.getShamrockVersion
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@ -87,6 +87,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
delay(5_000) // Delay in milliseconds
|
||||||
|
broadcastToModule {
|
||||||
|
putExtra("__cmd", "switch_status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalIndication provides NoIndication
|
LocalIndication provides NoIndication
|
||||||
) {
|
) {
|
||||||
@ -336,7 +345,7 @@ private fun AnimatedTab(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShamrockTab(
|
ShamrockTabV2(
|
||||||
selected = curSelected,
|
selected = curSelected,
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
@ -30,6 +30,7 @@ abstract class ModuleHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
putExtra("__hash", callbackId)
|
putExtra("__hash", callbackId)
|
||||||
|
putExtra("__cmd", cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -58,7 +58,7 @@ class MultifunctionalProvider: ContentProvider() {
|
|||||||
|
|
||||||
inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) {
|
inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.action = "moe.fuqiuluo.xqbot.dynamic"
|
intent.action = "moe.fuqiuluo.onebot.dynamic"
|
||||||
intent.intentBuilder()
|
intent.intentBuilder()
|
||||||
sendBroadcast(intent)
|
sendBroadcast(intent)
|
||||||
}
|
}
|
@ -242,13 +242,13 @@ private fun Placeable.PlacementScope.placeTextAndIcon(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShamrockTab(
|
fun ShamrockTabV2(
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
text: @Composable (() -> Unit)? = null,
|
text: (@Composable (() -> Unit))? = null,
|
||||||
icon: @Composable (() -> Unit)? = null,
|
icon: (@Composable (() -> Unit))? = null,
|
||||||
selectedContentColor: Color = GlobalColor.TabSelected,
|
selectedContentColor: Color = GlobalColor.TabSelected,
|
||||||
unselectedContentColor: Color = selectedContentColor,
|
unselectedContentColor: Color = selectedContentColor,
|
||||||
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
|
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
|
||||||
@ -262,7 +262,7 @@ fun ShamrockTab(
|
|||||||
ProvideTextStyle(style, content = text)
|
ProvideTextStyle(style, content = text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ShamrockTab(
|
ShamrockTabV2(
|
||||||
selected,
|
selected,
|
||||||
onClick,
|
onClick,
|
||||||
modifier,
|
modifier,
|
||||||
@ -277,7 +277,7 @@ fun ShamrockTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShamrockTab(
|
fun ShamrockTabV2(
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -94,7 +94,8 @@ data class DeleteReq(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DownloadRkeyReq(
|
data class DownloadRkeyReq(
|
||||||
@ProtoNumber(1) val types: List<Int>
|
@ProtoNumber(1) val types: List<Int>,
|
||||||
|
@ProtoNumber(2) val downloadType: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -52,11 +52,11 @@ data class DownloadRkeyRsp(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class RKeyInfo(
|
data class RKeyInfo(
|
||||||
@ProtoNumber(1) val rkey: String?,
|
@ProtoNumber(1) val rkey: String,
|
||||||
@ProtoNumber(2) val rkeyTtlSec: ULong?,
|
@ProtoNumber(2) val rkeyTtlSec: ULong?,
|
||||||
@ProtoNumber(3) val storeId: UInt = 0u,
|
@ProtoNumber(3) val storeId: UInt = 0u,
|
||||||
@ProtoNumber(4) val rkeyCreateTime: UInt?,
|
@ProtoNumber(4) val rkeyCreateTime: UInt?,
|
||||||
@ProtoNumber(4) val type: UInt?,
|
@ProtoNumber(4) val type: UInt,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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<? super TroopMemberNickInfo, Unit> cb);
|
||||||
|
|
||||||
|
void fetchTroopMemberName(@Nullable String str, @Nullable String str2, @NotNull String str3, @Nullable Function1<? super TroopMemberNickInfo, Unit> cb);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void fetchTroopMemberUid(@Nullable String str, @NotNull Function2<? super Boolean, ? super String, Unit> function2);
|
||||||
|
|
||||||
|
void fetchTroopMemberUid(@NotNull List<String> list, @NotNull Function2<? super Boolean, ? super Map<String, String>, Unit> function2);
|
||||||
|
|
||||||
|
void fetchTroopMemberUin(@Nullable String str, @NotNull Function2<? super Boolean, ? super String, Unit> function2);
|
||||||
|
|
||||||
|
void fetchTroopMemberUin(@NotNull List<String> list, @NotNull Function2<? super Boolean, ? super Map<String, String>, Unit> function2);
|
||||||
|
|
||||||
|
//void fetchTroopMemberUinListInfo(@Nullable String str, @Nullable List<String> list, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str2, @Nullable f fVar);
|
||||||
|
|
||||||
|
//void fetchTroopMemberUinListInfoWithExtInfo(@Nullable String str, @Nullable List<String> 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<String> list, boolean z, @Nullable LifecycleOwner lifecycleOwner, @NotNull String str2, @Nullable f fVar);
|
||||||
|
}
|
@ -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<ByteArray?>(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<ByteArray?>(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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,12 +21,14 @@ import moe.fuqiuluo.shamrock.tools.json
|
|||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.Packet
|
import mqq.app.Packet
|
||||||
import tencent.im.oidb.cmd0x11b2.oidb_0x11b2
|
import tencent.im.oidb.cmd0x11b2.oidb_0x11b2
|
||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal object CardSvc: BaseSvc() {
|
internal object CardSvc: QQInterfaces() {
|
||||||
private val GetModelShowLock by lazy {
|
private val GetModelShowLock by lazy {
|
||||||
Mutex()
|
Mutex()
|
||||||
}
|
}
|
||||||
@ -46,7 +48,8 @@ internal object CardSvc: BaseSvc() {
|
|||||||
|
|
||||||
val resp = sendBufferAW("VipCustom.GetCustomOnlineStatus", false, uniPacket.encode())
|
val resp = sendBufferAW("VipCustom.GetCustomOnlineStatus", false, uniPacket.encode())
|
||||||
?: error("unable to fetch contact model_show")
|
?: 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.uin.set(peerId)
|
||||||
reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$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")
|
?: error("unable to fetch contact ark_json_text")
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = fromServiceMsg.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
|
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
|
||||||
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||||
return rsp.signed_ark_msg.get()
|
return rsp.signed_ark_msg.get()
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet
|
package moe.fuqiuluo.qqinterface.servlet
|
||||||
|
|
||||||
import kotlinx.serialization.encodeToByteArray
|
import kotlinx.serialization.encodeToByteArray
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
|
|
||||||
import protobuf.oidb.cmd0x9082.Oidb0x9082
|
import protobuf.oidb.cmd0x9082.Oidb0x9082
|
||||||
|
|
||||||
internal object ChatSvc: BaseSvc() {
|
internal object ChatSvc: QQInterfaces() {
|
||||||
fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) {
|
fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) {
|
||||||
val serviceId = if (isSet) 1 else 2
|
val serviceId = if (isSet) 1 else 2
|
||||||
sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, Oidb0x9082(
|
sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, Oidb0x9082(
|
||||||
|
@ -6,9 +6,11 @@ import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
|||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
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.slice
|
||||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import protobuf.oidb.cmd0x6d7.CreateFolderReq
|
import protobuf.oidb.cmd0x6d7.CreateFolderReq
|
||||||
import protobuf.oidb.cmd0x6d7.DeleteFolderReq
|
import protobuf.oidb.cmd0x6d7.DeleteFolderReq
|
||||||
@ -21,8 +23,10 @@ import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
|
|||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo
|
import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo
|
||||||
import protobuf.auto.toByteArray
|
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<GroupFileCommonFolderInfo> {
|
suspend fun createFileFolder(groupId: Long, folderName: String, parentFolderId: String = "/"): Result<GroupFileCommonFolderInfo> {
|
||||||
val data = Oidb0x6d7ReqBody(
|
val data = Oidb0x6d7ReqBody(
|
||||||
createFolder = CreateFolderReq(
|
createFolder = CreateFolderReq(
|
||||||
@ -32,10 +36,9 @@ internal object FileSvc: BaseSvc() {
|
|||||||
folderName = folderName
|
folderName = folderName
|
||||||
)
|
)
|
||||||
).toByteArray()
|
).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"))
|
?: return Result.failure(Exception("unable to fetch result"))
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||||
oidbPkg.mergeFrom(resultBuffer.slice(4))
|
|
||||||
val rsp = oidbPkg.bytes_bodybuffer.get()
|
val rsp = oidbPkg.bytes_bodybuffer.get()
|
||||||
.toByteArray()
|
.toByteArray()
|
||||||
.decodeProtobuf<Oidb0x6d7RespBody>()
|
.decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
@ -46,21 +49,20 @@ internal object FileSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteGroupFolder(groupId: Long, folderUid: String): Boolean {
|
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(
|
deleteFolder = DeleteFolderReq(
|
||||||
groupCode = groupId.toULong(),
|
groupCode = groupId.toULong(),
|
||||||
appId = 3u,
|
appId = 3u,
|
||||||
folderId = folderUid
|
folderId = folderUid
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: return false
|
).toByteArray()) ?: return false
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||||
oidbPkg.mergeFrom(buffer.slice(4))
|
|
||||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
return rsp.deleteFolder?.retCode == 0
|
return rsp.deleteFolder?.retCode == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun moveGroupFolder(groupId: Long, folderUid: String, newParentFolderUid: String): Boolean {
|
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(
|
moveFolder = MoveFolderReq(
|
||||||
groupCode = groupId.toULong(),
|
groupCode = groupId.toULong(),
|
||||||
appId = 3u,
|
appId = 3u,
|
||||||
@ -68,14 +70,13 @@ internal object FileSvc: BaseSvc() {
|
|||||||
parentFolderId = "/"
|
parentFolderId = "/"
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: return false
|
).toByteArray()) ?: return false
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||||
oidbPkg.mergeFrom(buffer.slice(4))
|
|
||||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
return rsp.moveFolder?.retCode == 0
|
return rsp.moveFolder?.retCode == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun renameFolder(groupId: Long, folderUid: String, name: String): Boolean {
|
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(
|
renameFolder = RenameFolderReq(
|
||||||
groupCode = groupId.toULong(),
|
groupCode = groupId.toULong(),
|
||||||
appId = 3u,
|
appId = 3u,
|
||||||
@ -83,8 +84,7 @@ internal object FileSvc: BaseSvc() {
|
|||||||
folderName = name
|
folderName = name
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: return false
|
).toByteArray()) ?: return false
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||||
oidbPkg.mergeFrom(buffer.slice(4))
|
|
||||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
return rsp.renameFolder?.retCode == 0
|
return rsp.renameFolder?.retCode == 0
|
||||||
}
|
}
|
||||||
@ -101,8 +101,7 @@ internal object FileSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
val result = sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
val result = sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
||||||
?: return false
|
?: return false
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = result.decodeToOidb()
|
||||||
oidbPkg.mergeFrom(result.slice(4))
|
|
||||||
val rsp = oidb_0x6d6.RspBody().apply {
|
val rsp = oidb_0x6d6.RspBody().apply {
|
||||||
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
|
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
|
||||||
}
|
}
|
||||||
@ -120,8 +119,8 @@ internal object FileSvc: BaseSvc() {
|
|||||||
val fileCnt: Int
|
val fileCnt: Int
|
||||||
val limitCnt: Int
|
val limitCnt: Int
|
||||||
if (rspGetFileCntBuffer != null) {
|
if (rspGetFileCntBuffer != null) {
|
||||||
oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
oidb_0x6d8.RspBody().mergeFrom(
|
||||||
.mergeFrom(rspGetFileCntBuffer.slice(4))
|
rspGetFileCntBuffer.decodeToOidb()
|
||||||
.bytes_bodybuffer.get()
|
.bytes_bodybuffer.get()
|
||||||
.toByteArray()
|
.toByteArray()
|
||||||
).group_file_cnt_rsp.apply {
|
).group_file_cnt_rsp.apply {
|
||||||
@ -141,8 +140,8 @@ internal object FileSvc: BaseSvc() {
|
|||||||
val totalSpace: Long
|
val totalSpace: Long
|
||||||
val usedSpace: Long
|
val usedSpace: Long
|
||||||
if (rspGetFileSpaceBuffer != null) {
|
if (rspGetFileSpaceBuffer != null) {
|
||||||
oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
oidb_0x6d8.RspBody().mergeFrom(
|
||||||
.mergeFrom(rspGetFileSpaceBuffer.slice(4))
|
rspGetFileSpaceBuffer.decodeToOidb()
|
||||||
.bytes_bodybuffer.get()
|
.bytes_bodybuffer.get()
|
||||||
.toByteArray()).group_space_rsp.apply {
|
.toByteArray()).group_space_rsp.apply {
|
||||||
totalSpace = uint64_total_space.get()
|
totalSpace = uint64_total_space.get()
|
||||||
@ -187,15 +186,13 @@ internal object FileSvc: BaseSvc() {
|
|||||||
|
|
||||||
uint32_show_onlinedoc_folder.set(0)
|
uint32_show_onlinedoc_folder.set(0)
|
||||||
})
|
})
|
||||||
}.toByteArray(), timeout = 15_000L)
|
}.toByteArray(), timeout = 15.seconds)
|
||||||
|
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
val files = arrayListOf<FileInfo>()
|
val files = arrayListOf<FileInfo>()
|
||||||
val dirs = arrayListOf<FolderInfo>()
|
val dirs = arrayListOf<FolderInfo>()
|
||||||
if (rspGetFileListBuffer != null) {
|
if (rspGetFileListBuffer != null) {
|
||||||
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(rspGetFileListBuffer.slice(4).let {
|
val oidb = rspGetFileListBuffer.decodeToOidb()
|
||||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
|
||||||
})
|
|
||||||
|
|
||||||
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
|
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
|
||||||
.file_list_info_rsp.apply {
|
.file_list_info_rsp.apply {
|
||||||
@ -242,7 +239,7 @@ internal object FileSvc: BaseSvc() {
|
|||||||
|
|
||||||
GroupFileList(files, dirs)
|
GroupFileList(files, dirs)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer.toHexString()}", Level.ERROR)
|
LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer?.wupBuffer?.toHexString()}", Level.ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,13 +13,15 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.AppRuntime
|
import mqq.app.AppRuntime
|
||||||
import tencent.mobileim.structmsg.structmsg
|
import tencent.mobileim.structmsg.structmsg
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal object FriendSvc: BaseSvc() {
|
internal object FriendSvc: QQInterfaces() {
|
||||||
|
|
||||||
suspend fun getFriendList(refresh: Boolean): Result<List<Friends>> {
|
suspend fun getFriendList(refresh: Boolean): Result<List<Friends>> {
|
||||||
val runtime = AppRuntimeFetcher.appRuntime
|
val runtime = AppRuntimeFetcher.appRuntime
|
||||||
@ -91,8 +93,7 @@ internal object FriendSvc: BaseSvc() {
|
|||||||
ArrayList()
|
ArrayList()
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val msg = structmsg.RspSystemMsgNew()
|
val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew())
|
||||||
msg.mergeFrom(respBuffer.slice(4))
|
|
||||||
return msg.friendmsgs.get()
|
return msg.friendmsgs.get()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||||
|
@ -19,9 +19,12 @@ import moe.fuqiuluo.qqinterface.servlet.structures.SlowModeInfo
|
|||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
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.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.guild.GetGuildFeedsReq
|
import protobuf.guild.GetGuildFeedsReq
|
||||||
@ -44,7 +47,7 @@ import protobuf.qweb.QWebRsp
|
|||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal object GProSvc: BaseSvc() {
|
internal object GProSvc: QQInterfaces() {
|
||||||
fun getSelfTinyId(): ULong {
|
fun getSelfTinyId(): ULong {
|
||||||
val service = app.getRuntimeService(IGPSService::class.java, "all")
|
val service = app.getRuntimeService(IGPSService::class.java, "all")
|
||||||
return service.selfTinyId.toULong()
|
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)
|
u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u)
|
||||||
),
|
),
|
||||||
guildInfo = Oidb0xf57GuildInfo(guildId = guildId)
|
guildInfo = Oidb0xf57GuildInfo(guildId = guildId)
|
||||||
).toByteArray())
|
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = respBuffer.decodeToOidb()
|
||||||
if (respBuffer == null) {
|
|
||||||
return Result.failure(Exception("unable to send packet"))
|
|
||||||
}
|
|
||||||
body.mergeFrom(respBuffer.slice(4))
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
body.bytes_bodybuffer.get()
|
body.bytes_bodybuffer.get()
|
||||||
.toByteArray()
|
.toByteArray()
|
||||||
@ -71,7 +70,7 @@ internal object GProSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result<GetGuildFeedsRsp> {
|
suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result<GetGuildFeedsRsp> {
|
||||||
val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq(
|
val fromServiceMsg = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq(
|
||||||
seq = 10,
|
seq = 10,
|
||||||
qua = PlatformUtils.getQUA(),
|
qua = PlatformUtils.getQUA(),
|
||||||
deviceInfo = DEFAULT_DEVICE_INFO,
|
deviceInfo = DEFAULT_DEVICE_INFO,
|
||||||
@ -92,7 +91,7 @@ internal object GProSvc: BaseSvc() {
|
|||||||
QWebExtInfo("tiny_id", getSelfTinyId().toString()),
|
QWebExtInfo("tiny_id", getSelfTinyId().toString()),
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
||||||
val webRsp = buffer.slice(4).decodeProtobuf<QWebRsp>()
|
val webRsp = fromServiceMsg.decodeToObject<QWebRsp>()
|
||||||
if(webRsp.buffer == null) return Result.failure(Exception("server error"))
|
if(webRsp.buffer == null) return Result.failure(Exception("server error"))
|
||||||
val wupBuffer = webRsp.buffer!!
|
val wupBuffer = webRsp.buffer!!
|
||||||
val feeds = wupBuffer.decodeProtobuf<GetGuildFeedsRsp>()
|
val feeds = wupBuffer.decodeProtobuf<GetGuildFeedsRsp>()
|
||||||
@ -188,12 +187,8 @@ internal object GProSvc: BaseSvc() {
|
|||||||
memberId = 0uL,
|
memberId = 0uL,
|
||||||
tinyId = memberTinyId,
|
tinyId = memberTinyId,
|
||||||
guildId = guildId
|
guildId = guildId
|
||||||
).toByteArray())
|
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = respBuffer.decodeToOidb()
|
||||||
if (respBuffer == null) {
|
|
||||||
return Result.failure(Exception("unable to send packet"))
|
|
||||||
}
|
|
||||||
body.mergeFrom(respBuffer.slice(4))
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
body.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0xf88Rsp>().userInfo!!
|
body.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0xf88Rsp>().userInfo!!
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,16 @@ import com.tencent.mobileqq.app.BusinessHandlerFactory
|
|||||||
import com.tencent.mobileqq.app.QQAppInterface
|
import com.tencent.mobileqq.app.QQAppInterface
|
||||||
import com.tencent.mobileqq.data.troop.TroopInfo
|
import com.tencent.mobileqq.data.troop.TroopInfo
|
||||||
import com.tencent.mobileqq.data.troop.TroopMemberInfo
|
import com.tencent.mobileqq.data.troop.TroopMemberInfo
|
||||||
|
import com.tencent.mobileqq.data.troop.TroopMemberNickInfo
|
||||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
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.ITroopInfoService
|
||||||
import com.tencent.mobileqq.troop.api.ITroopMemberInfoService
|
import com.tencent.mobileqq.troop.api.ITroopMemberInfoService
|
||||||
import com.tencent.protofile.join_group_link.join_group_link
|
import com.tencent.protofile.join_group_link.join_group_link
|
||||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
|
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.troopmemberlist.ITroopMemberListRepoApi
|
||||||
import friendlist.stUinInfo
|
import friendlist.stUinInfo
|
||||||
import io.ktor.client.call.body
|
import io.ktor.client.call.body
|
||||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
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.asLong
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
import moe.fuqiuluo.shamrock.tools.asStringOrNull
|
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.ifNullOrEmpty
|
||||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
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.AppRuntimeFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.oidb.cmd0xf16.Oidb0xf16
|
import protobuf.oidb.cmd0xf16.Oidb0xf16
|
||||||
@ -94,8 +102,9 @@ import java.lang.reflect.Method
|
|||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.coroutines.resume
|
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 const val GET_MEMBER_ROLE_BY_NT = false
|
||||||
|
|
||||||
private val RefreshTroopMemberInfoLock by lazy {
|
private val RefreshTroopMemberInfoLock by lazy {
|
||||||
@ -112,15 +121,14 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method
|
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method
|
||||||
|
|
||||||
suspend fun getGroupRemainAtAllRemain (groupId: Long): Result<GroupAtAllRemainInfo> {
|
suspend fun getGroupRemainAtAllRemain (groupId: Long): Result<GroupAtAllRemainInfo> {
|
||||||
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_sub_cmd.set(1)
|
||||||
uint32_limit_interval_type_for_uin.set(2)
|
uint32_limit_interval_type_for_uin.set(2)
|
||||||
uint32_limit_interval_type_for_group.set(1)
|
uint32_limit_interval_type_for_group.set(1)
|
||||||
uint64_uin.set(getLongUin())
|
uint64_uin.set(getLongUin())
|
||||||
uint64_group_code.set(groupId)
|
uint64_group_code.set(groupId)
|
||||||
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = fromServiceMsg.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
if(body.uint32_result.get() != 0) {
|
if(body.uint32_result.get() != 0) {
|
||||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||||
}
|
}
|
||||||
@ -133,7 +141,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
|
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
|
||||||
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_group_code.set(groupId)
|
||||||
uint64_start_uin.set(0)
|
uint64_start_uin.set(0)
|
||||||
uint32_identify_flag.set(6)
|
uint32_identify_flag.set(6)
|
||||||
@ -142,8 +150,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
uint32_shutup_timestap.set(0)
|
uint32_shutup_timestap.set(0)
|
||||||
})
|
})
|
||||||
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = fromServiceMsg.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
if(body.uint32_result.get() != 0) {
|
if(body.uint32_result.get() != 0) {
|
||||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
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.putLong("troop_code", groupId)
|
||||||
toServiceMsg.extraData.putBoolean("is_admin", false)
|
toServiceMsg.extraData.putBoolean("is_admin", false)
|
||||||
toServiceMsg.extraData.putInt("from", 0)
|
toServiceMsg.extraData.putInt("from", 0)
|
||||||
val buffer = sendAW(toServiceMsg)
|
val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时"))
|
||||||
val uniPacket = UniPacket(true)
|
val uniPacket = UniPacket(true)
|
||||||
uniPacket.encodeName = "utf-8"
|
uniPacket.encodeName = "utf-8"
|
||||||
uniPacket.decode(buffer)
|
uniPacket.decode(fromServiceMsg.wupBuffer)
|
||||||
val respBatchProcess = uniPacket.getByClass("RespBatchProcess", RespBatchProcess())
|
val respBatchProcess = uniPacket.getByClass("RespBatchProcess", RespBatchProcess())
|
||||||
val batchRespInfo = oidb_0x88d.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
val batchRespInfo = oidb_0x88d.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
||||||
.mergeFrom(respBatchProcess.batch_response_list.first().buffer)
|
.mergeFrom(respBatchProcess.batch_response_list.first().buffer)
|
||||||
@ -308,7 +315,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
info.dwFlag = 1
|
info.dwFlag = 1
|
||||||
createToServiceMsg.extraData.putSerializable("vecUinInfo", arrayListOf(info))
|
createToServiceMsg.extraData.putSerializable("vecUinInfo", arrayListOf(info))
|
||||||
createToServiceMsg.extraData.putLong("dwNewSeq", 0L)
|
createToServiceMsg.extraData.putLong("dwNewSeq", 0L)
|
||||||
send(createToServiceMsg)
|
sendToServiceMsg(createToServiceMsg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,13 +331,12 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair<Boolean, String> {
|
suspend fun setEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair<Boolean, String> {
|
||||||
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)
|
group_code.set(groupId)
|
||||||
msg_seq.set(seq.toInt())
|
msg_seq.set(seq.toInt())
|
||||||
msg_random.set(rand.toInt())
|
msg_random.set(rand.toInt())
|
||||||
}.toByteArray()) ?: return Pair(false, "unknown error")
|
}.toByteArray()) ?: return Pair(false, "unknown error")
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = fromServiceMsg.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||||
return if (result.wording.has()) {
|
return if (result.wording.has()) {
|
||||||
LogCenter.log("设置群精华失败: ${result.wording.get()}")
|
LogCenter.log("设置群精华失败: ${result.wording.get()}")
|
||||||
@ -342,16 +348,12 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair<Boolean, String> {
|
suspend fun deleteEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair<Boolean, String> {
|
||||||
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)
|
group_code.set(groupId)
|
||||||
msg_seq.set(seq.toInt())
|
msg_seq.set(seq.toInt())
|
||||||
msg_random.set(rand.toInt())
|
msg_random.set(rand.toInt())
|
||||||
}.toByteArray())
|
}.toByteArray()) ?: return Pair(false, "unknown error")
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = fromServiceMsg.decodeToOidb()
|
||||||
if (buffer == null) {
|
|
||||||
return Pair(false, "unknown error")
|
|
||||||
}
|
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||||
return if (result.wording.has()) {
|
return if (result.wording.has()) {
|
||||||
LogCenter.log("移除群精华失败: ${result.wording.get()}")
|
LogCenter.log("移除群精华失败: ${result.wording.get()}")
|
||||||
@ -565,10 +567,9 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
reqBody.get_ark.set(true)
|
reqBody.get_ark.set(true)
|
||||||
reqBody.type.set(1)
|
reqBody.type.set(1)
|
||||||
reqBody.group_code.set(groupId)
|
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")
|
?: error("unable to fetch contact ark_json_text")
|
||||||
val body = join_group_link.RspBody()
|
val body = fromServiceMsg.decodeToObject(join_group_link.RspBody())
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
return body.signed_ark.get().toStringUtf8()
|
return body.signed_ark.get().toStringUtf8()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,8 +601,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
req.uint32_rich_card_name_ver.set(1)
|
req.uint32_rich_card_name_ver.set(1)
|
||||||
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
||||||
if (respBuffer != null) {
|
if (respBuffer != null) {
|
||||||
val rsp = group_member_info.RspBody()
|
val rsp = respBuffer.decodeToObject(group_member_info.RspBody())
|
||||||
rsp.mergeFrom(respBuffer.slice(4))
|
|
||||||
if (rsp.msg_meminfo.str_location.has()) {
|
if (rsp.msg_meminfo.str_location.has()) {
|
||||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||||
}
|
}
|
||||||
@ -627,15 +627,49 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTroopMemberInfoByUinFromNt(
|
||||||
|
groupId: Long,
|
||||||
|
uin: Long
|
||||||
|
): Result<TroopMemberInfo> {
|
||||||
|
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<TroopMemberNickInfo> { continuation ->
|
||||||
|
api.fetchTroopMemberName(groupId.toString(), uin.toString(), null, groupId.toString()) {
|
||||||
|
continuation.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getTroopMemberInfoByUinV2(
|
suspend fun getTroopMemberInfoByUinV2(
|
||||||
groupId: Long,
|
groupId: Long,
|
||||||
uin: Long,
|
uin: Long,
|
||||||
refresh: Boolean = false
|
refresh: Boolean = false
|
||||||
): Result<TroopMemberInfo> {
|
): Result<TroopMemberInfo> {
|
||||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
var info: TroopMemberInfo? = null
|
||||||
var info = service.getTroopMember(groupId.toString(), uin.toString())
|
if (PlatformUtils.getQQVersionCode() <= QQ_9_0_65_VER) {
|
||||||
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) {
|
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||||
info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull()
|
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) {
|
if (info == null) {
|
||||||
info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let {
|
info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let {
|
||||||
@ -645,35 +679,36 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
if (PlatformUtils.getQQVersionCode() <= QQ_9_0_8_VER) {
|
||||||
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
try {
|
||||||
val req = group_member_info.ReqBody()
|
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
||||||
req.uint64_group_code.set(groupId)
|
val req = group_member_info.ReqBody()
|
||||||
req.uint64_uin.set(uin)
|
req.uint64_group_code.set(groupId)
|
||||||
req.bool_new_client.set(true)
|
req.uint64_uin.set(uin)
|
||||||
req.uint32_client_type.set(1)
|
req.bool_new_client.set(true)
|
||||||
req.uint32_rich_card_name_ver.set(1)
|
req.uint32_client_type.set(1)
|
||||||
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000)
|
req.uint32_rich_card_name_ver.set(1)
|
||||||
if (respBuffer != null) {
|
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2.seconds)
|
||||||
val rsp = group_member_info.RspBody()
|
if (respBuffer != null) {
|
||||||
rsp.mergeFrom(respBuffer.slice(4))
|
val rsp = respBuffer.decodeToObject(group_member_info.RspBody())
|
||||||
if (rsp.msg_meminfo.str_location.has()) {
|
if (rsp.msg_meminfo.str_location.has()) {
|
||||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||||
}
|
}
|
||||||
if (rsp.msg_meminfo.uint32_age.has()) {
|
if (rsp.msg_meminfo.uint32_age.has()) {
|
||||||
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
|
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
|
||||||
}
|
}
|
||||||
if (rsp.msg_meminfo.bytes_group_honor.has()) {
|
if (rsp.msg_meminfo.bytes_group_honor.has()) {
|
||||||
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
|
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
|
||||||
val honor = troop_honor.GroupUserCardHonor()
|
val honor = troop_honor.GroupUserCardHonor()
|
||||||
honor.mergeFrom(honorBytes)
|
honor.mergeFrom(honorBytes)
|
||||||
info.level = honor.level.get()
|
info.level = honor.level.get()
|
||||||
// 10315: medal_id not real group level
|
// 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) {
|
return if (info != null) {
|
||||||
Result.success(info)
|
Result.success(info)
|
||||||
@ -682,7 +717,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTroopMemberInfoByUinViaNt(
|
private suspend fun getTroopMemberInfoByUinViaNt(
|
||||||
groupId: Long,
|
groupId: Long,
|
||||||
qq: Long,
|
qq: Long,
|
||||||
timeout: Long = 5000L
|
timeout: Long = 5000L
|
||||||
@ -711,7 +746,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
return if (info != null) {
|
return if (info != null) {
|
||||||
Result.success(info)
|
Result.success(info)
|
||||||
} else {
|
} 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())
|
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray())
|
||||||
?: return Result.failure(Exception("操作失败"))
|
?: return Result.failure(Exception("操作失败"))
|
||||||
val rsp = structmsg.RspSystemMsgAction().mergeFrom(respBuffer.slice(4))
|
val rsp = respBuffer.decodeToObject(structmsg.RspSystemMsgAction())
|
||||||
return if (rsp.head.result.has()) {
|
return if (rsp.head.result.has()) {
|
||||||
if (rsp.head.result.get() == 0) {
|
if (rsp.head.result.get() == 0) {
|
||||||
Result.success(rsp.msg_detail.get())
|
Result.success(rsp.msg_detail.get())
|
||||||
@ -984,8 +1019,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
ArrayList()
|
ArrayList()
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val msg = structmsg.RspSystemMsgNew()
|
val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew())
|
||||||
msg.mergeFrom(respBuffer.slice(4))
|
|
||||||
return msg.groupmsgs.get().orEmpty()
|
return msg.groupmsgs.get().orEmpty()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||||
@ -1178,8 +1212,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
return if (buffer == null) {
|
return if (buffer == null) {
|
||||||
Result.failure(Exception("操作失败"))
|
Result.failure(Exception("操作失败"))
|
||||||
} else {
|
} else {
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = buffer.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val rsp = oidb_0xeb7.RspBody()
|
val rsp = oidb_0xeb7.RspBody()
|
||||||
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||||
val doneInfo = rsp.signInWriteRsp.doneInfo
|
val doneInfo = rsp.signInWriteRsp.doneInfo
|
||||||
|
@ -6,10 +6,12 @@ import com.tencent.mobileqq.msf.service.MsfService
|
|||||||
import com.tencent.proto.lbsshare.LBSShare
|
import com.tencent.proto.lbsshare.LBSShare
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import moe.fuqiuluo.shamrock.helper.IllegalParamsException
|
import moe.fuqiuluo.shamrock.helper.IllegalParamsException
|
||||||
|
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
internal object LbsSvc: BaseSvc() {
|
internal object LbsSvc: QQInterfaces() {
|
||||||
suspend fun tryShareLocation(chatType: Int, peerId: Long, lat: Double, lon: Double): Result<Unit> {
|
suspend fun tryShareLocation(chatType: Int, peerId: Long, lat: Double, lon: Double): Result<Unit> {
|
||||||
val req = LbsSendInfo.SendMessageReq()
|
val req = LbsSendInfo.SendMessageReq()
|
||||||
req.uint64_peer_account.set(peerId)
|
req.uint64_peer_account.set(peerId)
|
||||||
@ -24,8 +26,8 @@ internal object LbsSvc: BaseSvc() {
|
|||||||
}.getOrNull())
|
}.getOrNull())
|
||||||
req.str_lat.set(lat.toString())
|
req.str_lat.set(lat.toString())
|
||||||
req.str_lng.set(lon.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)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +52,7 @@ internal object LbsSvc: BaseSvc() {
|
|||||||
req.imei.set("")
|
req.imei.set("")
|
||||||
val buffer = sendBufferAW("LbsShareSvr.location", true, req.toByteArray())
|
val buffer = sendBufferAW("LbsShareSvr.location", true, req.toByteArray())
|
||||||
?: return Result.failure(Exception("获取位置失败"))
|
?: return Result.failure(Exception("获取位置失败"))
|
||||||
val resp = LBSShare.LocationResp()
|
val resp = buffer.decodeToObject(LBSShare.LocationResp())
|
||||||
resp.mergeFrom(buffer.slice(4))
|
|
||||||
val location = resp.mylbs
|
val location = resp.mylbs
|
||||||
return Result.success(location.addr.get())
|
return Result.success(location.addr.get())
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
|||||||
import moe.fuqiuluo.shamrock.tools.*
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
@ -33,9 +34,10 @@ import java.util.*
|
|||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
internal object MsgSvc : BaseSvc() {
|
internal object MsgSvc : QQInterfaces() {
|
||||||
private suspend fun prepareTempChatFromGroup(
|
suspend fun prepareTempChatFromGroup(
|
||||||
groupId: String,
|
groupId: String,
|
||||||
peerId: String
|
peerId: String
|
||||||
): Result<Unit> {
|
): Result<Unit> {
|
||||||
@ -418,13 +420,9 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
)
|
)
|
||||||
).toByteArray()
|
).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"))
|
?: return Result.failure(Exception("unable to upload multi message, response timeout"))
|
||||||
val rsp = runCatching {
|
val rsp = buffer.decodeToObject<LongMsgRsp>()
|
||||||
buffer.slice(4).decodeProtobuf<LongMsgRsp>()
|
|
||||||
}.getOrElse {
|
|
||||||
buffer.decodeProtobuf<LongMsgRsp>()
|
|
||||||
}
|
|
||||||
val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message"))
|
val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message"))
|
||||||
return Result.success(MessageSegment(
|
return Result.success(MessageSegment(
|
||||||
type = "forward",
|
type = "forward",
|
||||||
@ -456,7 +454,7 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
true,
|
true,
|
||||||
req.toByteArray()
|
req.toByteArray()
|
||||||
) ?: return Result.failure(Exception("unable to get multi message"))
|
) ?: return Result.failure(Exception("unable to get multi message"))
|
||||||
val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
|
val rsp = buffer.decodeToObject<LongMsgRsp>()
|
||||||
val zippedPayload = DeflateTools.ungzip(
|
val zippedPayload = DeflateTools.ungzip(
|
||||||
rsp.recvResult?.payload ?: return Result.failure(Exception("payload is empty"))
|
rsp.recvResult?.payload ?: return Result.failure(Exception("payload is empty"))
|
||||||
)
|
)
|
||||||
|
@ -13,6 +13,7 @@ import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler
|
|||||||
import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
|
||||||
import moe.fuqiuluo.shamrock.tools.broadcast
|
import moe.fuqiuluo.shamrock.tools.broadcast
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.message.*
|
import protobuf.message.*
|
||||||
@ -21,7 +22,7 @@ import protobuf.push.MessagePush
|
|||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.text.toByteArray
|
import kotlin.text.toByteArray
|
||||||
|
|
||||||
internal object PacketSvc : BaseSvc() {
|
internal object PacketSvc : QQInterfaces() {
|
||||||
/**
|
/**
|
||||||
* 伪造收到Json卡片消息
|
* 伪造收到Json卡片消息
|
||||||
*/
|
*/
|
||||||
|
@ -23,6 +23,7 @@ import moe.fuqiuluo.shamrock.tools.toHexString
|
|||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
import moe.fuqiuluo.shamrock.utils.MD5
|
import moe.fuqiuluo.shamrock.utils.MD5
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import protobuf.fav.WeiyunAddRichMediaReq
|
import protobuf.fav.WeiyunAddRichMediaReq
|
||||||
import protobuf.fav.WeiyunAuthor
|
import protobuf.fav.WeiyunAuthor
|
||||||
import protobuf.fav.WeiyunCollectCommInfo
|
import protobuf.fav.WeiyunCollectCommInfo
|
||||||
@ -49,7 +50,7 @@ import kotlin.coroutines.resume
|
|||||||
/**
|
/**
|
||||||
* QQ收藏相关接口
|
* QQ收藏相关接口
|
||||||
*/
|
*/
|
||||||
internal object QFavSvc: BaseSvc() {
|
internal object QFavSvc: QQInterfaces() {
|
||||||
private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also {
|
private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also {
|
||||||
it.isIpv6 = false
|
it.isIpv6 = false
|
||||||
it.mIp = "collector.weiyun.com"
|
it.mIp = "collector.weiyun.com"
|
||||||
@ -275,7 +276,7 @@ internal object QFavSvc: BaseSvc() {
|
|||||||
|
|
||||||
override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
|
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)
|
//LogCenter.log(pSKey)
|
||||||
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
|
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
|
||||||
httpNetReq.mSendData = BytePacketBuilder().apply {
|
httpNetReq.mSendData = BytePacketBuilder().apply {
|
||||||
@ -381,7 +382,7 @@ internal object QFavSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getWeiYunPSKey(): String {
|
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)
|
.getPskey(app.currentAccountUin, 16L, arrayOf("weiyun.com"), WeiYunPSKeyPromise)
|
||||||
return if (pskey != null) pskey.getPSkey("weiyun.com") else ""
|
return if (pskey != null) pskey.getPSkey("weiyun.com") else ""
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@ import QQService.SvcDevLoginInfo
|
|||||||
import QQService.SvcReqGetDevLoginInfo
|
import QQService.SvcReqGetDevLoginInfo
|
||||||
import QQService.SvcRspGetDevLoginInfo
|
import QQService.SvcRspGetDevLoginInfo
|
||||||
import com.qq.jce.wup.UniPacket
|
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.MobileQQ
|
||||||
import mqq.app.Packet
|
import mqq.app.Packet
|
||||||
import oicq.wlogin_sdk.tools.util
|
import oicq.wlogin_sdk.tools.util
|
||||||
|
|
||||||
internal object QSafeSvc: BaseSvc() {
|
internal object QSafeSvc: QQInterfaces() {
|
||||||
|
|
||||||
suspend fun getOnlineClients(): ArrayList<SvcDevLoginInfo>? {
|
suspend fun getOnlineClients(): ArrayList<SvcDevLoginInfo>? {
|
||||||
val req = SvcReqGetDevLoginInfo()
|
val req = SvcReqGetDevLoginInfo()
|
||||||
@ -26,7 +26,7 @@ internal object QSafeSvc: BaseSvc() {
|
|||||||
val resp = sendBufferAW("StatSvc.GetDevLoginInfo", false, uniPacket.encode())
|
val resp = sendBufferAW("StatSvc.GetDevLoginInfo", false, uniPacket.encode())
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
return Packet.decodePacket(resp, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo
|
return Packet.decodePacket(resp.wupBuffer, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,13 +8,15 @@ import io.ktor.client.request.get
|
|||||||
import io.ktor.client.request.header
|
import io.ktor.client.request.header
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.BigDataTicket
|
import moe.fuqiuluo.shamrock.remote.service.data.BigDataTicket
|
||||||
import moe.fuqiuluo.shamrock.tools.GlobalClientNoRedirect
|
import moe.fuqiuluo.shamrock.tools.GlobalClientNoRedirect
|
||||||
|
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import mqq.manager.TicketManager
|
import mqq.manager.TicketManager
|
||||||
import oicq.wlogin_sdk.request.Ticket
|
import oicq.wlogin_sdk.request.Ticket
|
||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
|
|
||||||
internal object TicketSvc: BaseSvc() {
|
internal object TicketSvc: QQInterfaces() {
|
||||||
object SigType {
|
object SigType {
|
||||||
const val WLOGIN_A5 = 2
|
const val WLOGIN_A5 = 2
|
||||||
const val WLOGIN_RESERVED = 16
|
const val WLOGIN_RESERVED = 16
|
||||||
@ -109,23 +111,23 @@ internal object TicketSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getTicket(uin: String, id: Int): Ticket? {
|
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 {
|
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 {
|
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 {
|
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 {
|
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())
|
manager.reloadCache(MobileQQ.getContext())
|
||||||
return manager.getSuperkey(uin) ?: ""
|
return manager.getSuperkey(uin) ?: ""
|
||||||
}
|
}
|
||||||
@ -135,14 +137,13 @@ internal object TicketSvc: BaseSvc() {
|
|||||||
req.domains.set(domain.toList())
|
req.domains.set(domain.toList())
|
||||||
val buffer = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray())
|
val buffer = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray())
|
||||||
?: return Result.failure(Exception("getLessPSKey failed"))
|
?: return Result.failure(Exception("getLessPSKey failed"))
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = buffer.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||||
return Result.success(rsp.private_keys.get())
|
return Result.success(rsp.private_keys.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getPSKey(uin: String, domain: String): String? {
|
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())
|
if (it.isNullOrBlank())
|
||||||
getLessPSKey(domain).getOrNull()?.firstOrNull()?.key?.get()
|
getLessPSKey(domain).getOrNull()?.firstOrNull()?.key?.get()
|
||||||
else it
|
else it
|
||||||
@ -150,7 +151,7 @@ internal object TicketSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getPt4Token(uin: String, domain: String): String? {
|
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? {
|
suspend fun GetHttpCookies(appid: String, daid: String, jumpurl: String): String? {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet
|
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_C2C_AIO = 2
|
||||||
const val FROM_CONDITION_SEARCH = 9
|
const val FROM_CONDITION_SEARCH = 9
|
||||||
const val FROM_CONTACTS_TAB = 5
|
const val FROM_CONTACTS_TAB = 5
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.ark
|
package moe.fuqiuluo.qqinterface.servlet.ark
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
|
import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
|
||||||
|
|
||||||
internal object ArkMsgSvc: BaseSvc() {
|
internal object ArkMsgSvc: QQInterfaces() {
|
||||||
fun tryShareMusic(
|
fun tryShareMusic(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
peerId: Long,
|
peerId: Long,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.ark
|
package moe.fuqiuluo.qqinterface.servlet.ark
|
||||||
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.lightapp.AdaptShareInfoReq
|
import protobuf.lightapp.AdaptShareInfoReq
|
||||||
@ -11,7 +11,7 @@ import protobuf.qweb.DEFAULT_DEVICE_INFO
|
|||||||
import protobuf.qweb.QWebReq
|
import protobuf.qweb.QWebReq
|
||||||
import protobuf.qweb.QWebRsp
|
import protobuf.qweb.QWebRsp
|
||||||
|
|
||||||
internal object LightAppSvc: BaseSvc() {
|
internal object LightAppSvc: QQInterfaces() {
|
||||||
suspend fun adaptShareJumpUrl(
|
suspend fun adaptShareJumpUrl(
|
||||||
arkAppInfo: ArkAppInfo,
|
arkAppInfo: ArkAppInfo,
|
||||||
coverUrl: String,
|
coverUrl: String,
|
||||||
@ -37,7 +37,7 @@ internal object LightAppSvc: BaseSvc() {
|
|||||||
webURL = url,
|
webURL = url,
|
||||||
).toByteArray(),
|
).toByteArray(),
|
||||||
traceId = app.account + "_0_0",
|
traceId = app.account + "_0_0",
|
||||||
).toByteArray())?.decodeProtobuf<QWebRsp>()?.buffer?.decodeProtobuf<AdaptShareInfoResp>()
|
).toByteArray())?.wupBuffer?.decodeProtobuf<QWebRsp>()?.buffer?.decodeProtobuf<AdaptShareInfoResp>()
|
||||||
if (rsp == null || rsp.json.isNullOrEmpty())
|
if (rsp == null || rsp.json.isNullOrEmpty())
|
||||||
return Result.failure(Exception("unable to adapt ShareInfo"))
|
return Result.failure(Exception("unable to adapt ShareInfo"))
|
||||||
return Result.success(rsp.json!!)
|
return Result.success(rsp.json!!)
|
||||||
|
@ -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,}
|
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(
|
return MessageSegment(
|
||||||
type = "image",
|
type = "image",
|
||||||
data = hashMapOf(
|
data = hashMapOf(
|
||||||
"file" to md5,
|
"file" to md5,
|
||||||
"url" to when (chatType) {
|
"url" to url,
|
||||||
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")
|
|
||||||
},
|
|
||||||
"subType" to image.picSubType,
|
"subType" to image.picSubType,
|
||||||
"type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
|
"type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
|
||||||
)
|
)
|
||||||
|
@ -124,18 +124,23 @@ internal class ElemMaker {
|
|||||||
else -> {
|
else -> {
|
||||||
qq = qqStr.toLong()
|
qq = qqStr.toLong()
|
||||||
type = 0
|
type = 0
|
||||||
"@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(
|
val name = (data["name"].asStringOrNull
|
||||||
peerId.toLong(),
|
?: GroupSvc.getTroopMemberInfoByUinV3(peerId.toLong(), qq).let {
|
||||||
qq,
|
it?.troopNick
|
||||||
true
|
.ifNullOrEmpty(it?.friendNick)
|
||||||
).let {
|
.ifNullOrEmpty(it?.showName)
|
||||||
val info = it.getOrNull()
|
.ifNullOrEmpty(it?.autoRemark)
|
||||||
if (info == null)
|
.ifNullOrEmpty(it?.colorNick)
|
||||||
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
|
}
|
||||||
else info.troopnick
|
?: GroupSvc.getTroopMemberInfoByUinV2(peerId.toLong(), qq, true).let {
|
||||||
.ifNullOrEmpty(info.friendnick)
|
val info = it.getOrNull()
|
||||||
.ifNullOrEmpty(qqStr)
|
if (info == null)
|
||||||
})
|
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
|
||||||
|
else info.troopnick
|
||||||
|
.ifNullOrEmpty(info.friendnick)
|
||||||
|
.ifNullOrEmpty(qqStr)
|
||||||
|
})
|
||||||
|
"@$name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,7 +856,15 @@ internal object NtMsgElementMaker {
|
|||||||
.ifNullOrEmpty(qqStr)
|
.ifNullOrEmpty(qqStr)
|
||||||
}"
|
}"
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
at.content = "@$name"
|
at.content = "@$name"
|
||||||
|
@ -16,26 +16,29 @@ import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
|||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData
|
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
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.hex2ByteArray
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||||
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.MediaType
|
import moe.fuqiuluo.shamrock.utils.MediaType
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.oidb.TrpcOidb
|
|
||||||
import protobuf.oidb.cmd0x11c5.ClientMeta
|
import protobuf.oidb.cmd0x11c5.ClientMeta
|
||||||
import protobuf.oidb.cmd0x11c5.CodecConfigReq
|
import protobuf.oidb.cmd0x11c5.CodecConfigReq
|
||||||
import protobuf.oidb.cmd0x11c5.CommonHead
|
import protobuf.oidb.cmd0x11c5.CommonHead
|
||||||
import protobuf.oidb.cmd0x11c5.DownloadExt
|
import protobuf.oidb.cmd0x11c5.DownloadExt
|
||||||
import protobuf.oidb.cmd0x11c5.DownloadReq
|
import protobuf.oidb.cmd0x11c5.DownloadReq
|
||||||
|
import protobuf.oidb.cmd0x11c5.DownloadRkeyReq
|
||||||
|
import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp
|
||||||
import protobuf.oidb.cmd0x11c5.FileInfo
|
import protobuf.oidb.cmd0x11c5.FileInfo
|
||||||
import protobuf.oidb.cmd0x11c5.FileType
|
import protobuf.oidb.cmd0x11c5.FileType
|
||||||
import protobuf.oidb.cmd0x11c5.IndexNode
|
import protobuf.oidb.cmd0x11c5.IndexNode
|
||||||
@ -57,8 +60,9 @@ import kotlin.random.Random
|
|||||||
import kotlin.random.nextUInt
|
import kotlin.random.nextUInt
|
||||||
import kotlin.random.nextULong
|
import kotlin.random.nextULong
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
internal object NtV2RichMediaSvc: BaseSvc() {
|
internal object NtV2RichMediaSvc: QQInterfaces() {
|
||||||
private val requestIdSeq = atomic(2L)
|
private val requestIdSeq = atomic(2L)
|
||||||
|
|
||||||
fun fetchGroupResUploadTo(): String {
|
fun fetchGroupResUploadTo(): String {
|
||||||
@ -320,6 +324,40 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
|||||||
return Result.success(result)
|
return Result.success(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getTempNtRKey(): Result<DownloadRkeyRsp> {
|
||||||
|
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<NtV2RichMediaRsp>().downloadRkeyRsp?.let {
|
||||||
|
return Result.success(it)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
return Result.failure(it)
|
||||||
|
}
|
||||||
|
return Result.failure(Exception("failed to fetch NtTempRKey"))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取NT图片的RKEY
|
* 获取NT图片的RKEY
|
||||||
*/
|
*/
|
||||||
@ -386,8 +424,9 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
).toByteArray()
|
).toByteArray()
|
||||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)?.slice(4)
|
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)
|
||||||
buffer?.decodeProtobuf<TrpcOidb>()?.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.download?.rkeyParam?.let {
|
?: return Result.failure(Exception("no response"))
|
||||||
|
buffer.decodeToTrpcOidb().buffer.decodeProtobuf<NtV2RichMediaRsp>().download?.rkeyParam?.let {
|
||||||
return Result.success(it)
|
return Result.success(it)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
@ -470,17 +509,16 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
|||||||
).toByteArray()
|
).toByteArray()
|
||||||
val buffer = when (chatType) {
|
val buffer = when (chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> {
|
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"))
|
?: return Result.failure(Exception("no response: timeout"))
|
||||||
}
|
}
|
||||||
MsgConstant.KCHATTYPEC2C -> {
|
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"))
|
?: return Result.failure(Exception("no response: timeout"))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return Result.failure(Exception("unknown chat type: $chatType"))
|
else -> return Result.failure(Exception("unknown chat type: $chatType"))
|
||||||
}
|
}
|
||||||
val rspBuffer = buffer.decodeProtobuf<TrpcOidb>().buffer
|
val rspBuffer = buffer.decodeToTrpcOidb().buffer
|
||||||
val rsp = rspBuffer.decodeProtobuf<NtV2RichMediaRsp>()
|
val rsp = rspBuffer.decodeProtobuf<NtV2RichMediaRsp>()
|
||||||
if (rsp.upload == null) {
|
if (rsp.upload == null) {
|
||||||
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
|
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
|
||||||
@ -522,7 +560,7 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
).toByteArray())!!
|
).toByteArray())!!
|
||||||
val rsp = rspBuffer.decodeProtobuf<Cmd0x388RspBody>()
|
val rsp = rspBuffer.decodeToObject<Cmd0x388RspBody>()
|
||||||
.msgTryUpImgRsp!!.first()
|
.msgTryUpImgRsp!!.first()
|
||||||
TryUpPicData(
|
TryUpPicData(
|
||||||
uKey = rsp.ukey,
|
uKey = rsp.ukey,
|
||||||
|
@ -6,18 +6,20 @@ import com.tencent.mobileqq.transfile.FileMsg
|
|||||||
import com.tencent.mobileqq.transfile.api.IProtoReqManager
|
import com.tencent.mobileqq.transfile.api.IProtoReqManager
|
||||||
import com.tencent.mobileqq.transfile.protohandler.RichProto
|
import com.tencent.mobileqq.transfile.protohandler.RichProto
|
||||||
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
|
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.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.getNtPicRKey
|
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.getNtPicRKey
|
||||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
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.slice
|
||||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import protobuf.auto.toByteArray
|
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 MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn"
|
||||||
private const val C2C_PIC = "c2cpicdw.qpic.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 {
|
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
|
||||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
|
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
|
||||||
msgCmd = 1200,
|
msgCmd = 1200,
|
||||||
@ -53,8 +55,7 @@ internal object RichProtoSvc: BaseSvc() {
|
|||||||
supportEncrypt = 0
|
supportEncrypt = 0
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: return ""
|
).toByteArray()) ?: return ""
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = buffer.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
body.bytes_bodybuffer
|
body.bytes_bodybuffer
|
||||||
.get().toByteArray()
|
.get().toByteArray()
|
||||||
.decodeProtobuf<Oidb0xfc2RspBody>()
|
.decodeProtobuf<Oidb0xfc2RspBody>()
|
||||||
@ -79,8 +80,7 @@ internal object RichProtoSvc: BaseSvc() {
|
|||||||
str_file_id.set(fileId)
|
str_file_id.set(fileId)
|
||||||
})
|
})
|
||||||
}.toByteArray()) ?: return ""
|
}.toByteArray()) ?: return ""
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = buffer.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||||
if (body.uint32_result.get() != 0
|
if (body.uint32_result.get() != 0
|
||||||
|| result.download_file_rsp.int32_ret_code.get() != 0) {
|
|| result.download_file_rsp.int32_ret_code.get() != 0) {
|
||||||
@ -128,8 +128,7 @@ internal object RichProtoSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
} else {
|
} else {
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = buffer.decodeToOidb()
|
||||||
body.mergeFrom(buffer.slice(4))
|
|
||||||
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
|
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
|
||||||
body.bytes_bodybuffer.get().toByteArray()
|
body.bytes_bodybuffer.get().toByteArray()
|
||||||
).bytes_cmd_0x346_rsp_body.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<UInt, String>()
|
||||||
|
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(
|
suspend fun getGroupPicDownUrl(
|
||||||
originalUrl: String,
|
originalUrl: String,
|
||||||
md5: String,
|
md5: String,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package moe.fuqiuluo.shamrock.helper
|
package moe.fuqiuluo.shamrock.helper
|
||||||
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
internal object LocalCacheHelper: BaseSvc() {
|
internal object LocalCacheHelper: QQInterfaces() {
|
||||||
// 获取外部储存data目录
|
// 获取外部储存data目录
|
||||||
private val dataDir = MobileQQ.getContext().getExternalFilesDir(null)!!
|
private val dataDir = MobileQQ.getContext().getExternalFilesDir(null)!!
|
||||||
.parentFile!!.resolve("Tencent")
|
.parentFile!!.resolve("Tencent")
|
||||||
|
@ -9,8 +9,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.xposed.hooks.toast
|
import moe.fuqiuluo.shamrock.tools.toast
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -60,10 +60,10 @@ internal object LogCenter {
|
|||||||
}
|
}
|
||||||
// 把日志广播到主进程
|
// 把日志广播到主进程
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
DataRequester.request("send_message", bodyBuilder = {
|
AppTalker.talk("send_message") {
|
||||||
put("string", string)
|
put("string", string)
|
||||||
put("level", level.id)
|
put("level", level.id)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LogFile.exists()) {
|
if (!LogFile.exists()) {
|
||||||
@ -89,10 +89,10 @@ internal object LogCenter {
|
|||||||
}
|
}
|
||||||
// 把日志广播到主进程
|
// 把日志广播到主进程
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
DataRequester.request("send_message", bodyBuilder = {
|
AppTalker.talk("send_message") {
|
||||||
put("string", log)
|
put("string", log)
|
||||||
put("level", level.id)
|
put("level", level.id)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LogFile.exists()) {
|
if (!LogFile.exists()) {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package moe.fuqiuluo.shamrock.helper
|
package moe.fuqiuluo.shamrock.helper
|
||||||
|
|
||||||
import com.tencent.mobileqq.qroute.QRoute
|
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.IOperateCallback
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
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.kernelpublic.nativeinterface.Contact
|
||||||
import com.tencent.qqnt.msg.api.IMsgService
|
import com.tencent.qqnt.msg.api.IMsgService
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
@ -16,12 +19,15 @@ import kotlinx.serialization.json.JsonElement
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
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.ElemMaker
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker
|
import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker
|
||||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||||
import moe.fuqiuluo.shamrock.helper.db.MessageMapping
|
import moe.fuqiuluo.shamrock.helper.db.MessageMapping
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
||||||
import moe.fuqiuluo.shamrock.tools.*
|
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 protobuf.message.RichText
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@ -48,6 +54,28 @@ internal object MessageHelper {
|
|||||||
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
|
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun sendMessage(contact: Contact, msgs: ArrayList<MsgElement>, retry: Int, msgId: SendMsgResult): Result<SendMsgResult> {
|
||||||
|
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(
|
suspend fun resendMsg(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
peerId: String,
|
peerId: String,
|
||||||
@ -246,6 +274,40 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getTempChatInfo(chatType: Int, uid: String): Result<TempChatInfo> {
|
||||||
|
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 {
|
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
||||||
val peerId = when (chatType) {
|
val peerId = when (chatType) {
|
||||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote
|
package moe.fuqiuluo.shamrock.remote
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.app.QQAppInterface
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.server.application.Application
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
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.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
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 moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
@ -138,10 +141,13 @@ internal object HTTPServer {
|
|||||||
isServiceStarted = true
|
isServiceStarted = true
|
||||||
currServerPort = port
|
currServerPort = port
|
||||||
LogCenter.log("Start HTTP Server: http://0.0.0.0:$currServerPort/")
|
LogCenter.log("Start HTTP Server: http://0.0.0.0:$currServerPort/")
|
||||||
DataRequester.request("success", values = mapOf(
|
AppTalker.talk("success") {
|
||||||
"port" to currServerPort,
|
put("account", app.currentAccountUin)
|
||||||
"voice" to NativeLoader.isVoiceLoaded
|
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 {
|
fun isActive(): Boolean {
|
||||||
|
@ -9,7 +9,6 @@ import kotlinx.coroutines.withContext
|
|||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
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.tools.EmptyJsonString
|
||||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.MD5
|
import moe.fuqiuluo.shamrock.utils.MD5
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.symbols.OneBotHandler
|
import moe.fuqiuluo.symbols.OneBotHandler
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@ -55,7 +55,7 @@ internal object OcrImage: IActionHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getOcrResult(file: File): Result<OcrResult> {
|
private suspend fun getOcrResult(file: File): Result<OcrResult> {
|
||||||
val ocrService = BaseSvc.app.getRuntimeService(IPicOcrService::class.java, "all")
|
val ocrService = QQInterfaces.app.getRuntimeService(IPicOcrService::class.java, "all")
|
||||||
?: return Result.failure(Error("获取OCR服务失败"))
|
?: return Result.failure(Error("获取OCR服务失败"))
|
||||||
return withTimeoutOrNull(5000) {
|
return withTimeoutOrNull(5000) {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
@ -2,11 +2,10 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
|
|||||||
|
|
||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import moe.fuqiuluo.symbols.OneBotHandler
|
import moe.fuqiuluo.symbols.OneBotHandler
|
||||||
import protobuf.auto.toByteArray
|
import protobuf.auto.toByteArray
|
||||||
import protobuf.message.*
|
import protobuf.message.*
|
||||||
@ -51,7 +50,7 @@ internal object SendMsgByResid : IActionHandler() {
|
|||||||
msgRand = Random.nextUInt(),
|
msgRand = Random.nextUInt(),
|
||||||
msgVia = 0u
|
msgVia = 0u
|
||||||
)
|
)
|
||||||
BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
|
QQInterfaces.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
|
||||||
return ok("ok", echo)
|
return ok("ok", echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
|||||||
import moe.fuqiuluo.shamrock.tools.json
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
import moe.fuqiuluo.shamrock.tools.respond
|
import moe.fuqiuluo.shamrock.tools.respond
|
||||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
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.bytedata.IByteData
|
||||||
import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSigner
|
import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSigner
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
@ -113,10 +112,7 @@ fun Routing.qsign() {
|
|||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||||
if (!initSigner()) {
|
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||||
respond(false, Status.InternalHandlerError)
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val list = signer!!.cmdWhiteList
|
val list = signer!!.cmdWhiteList
|
||||||
call.respond(OldApiResult(0, "success", list))
|
call.respond(OldApiResult(0, "success", list))
|
||||||
@ -128,10 +124,7 @@ fun Routing.qsign() {
|
|||||||
return@getOrPost
|
return@getOrPost
|
||||||
}
|
}
|
||||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||||
if (!initSigner()) {
|
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||||
respond(false, Status.InternalHandlerError)
|
|
||||||
return@getOrPost
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val uin = fetchOrThrow("uin")
|
val uin = fetchOrThrow("uin")
|
||||||
@ -213,43 +206,7 @@ fun Routing.qsign() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get("/get_byte") {
|
get("/get_byte") {
|
||||||
if (!isMsfServiceAlive()) {
|
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/friend_sign") {
|
get("/friend_sign") {
|
||||||
@ -258,10 +215,7 @@ fun Routing.qsign() {
|
|||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||||
if (!initSigner()) {
|
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||||
respond(false, Status.InternalHandlerError)
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val addUin = fetchOrThrow("add_uin")
|
val addUin = fetchOrThrow("add_uin")
|
||||||
@ -286,10 +240,7 @@ fun Routing.qsign() {
|
|||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||||
if (!initSigner()) {
|
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||||
respond(false, Status.InternalHandlerError)
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val addUin = fetchOrThrow("group_uin")
|
val addUin = fetchOrThrow("group_uin")
|
||||||
@ -387,23 +338,6 @@ private data class Sign(
|
|||||||
val requestCallback: List<Int>
|
val requestCallback: List<Int>
|
||||||
)
|
)
|
||||||
|
|
||||||
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<Unit, ApplicationCall>.requestSign(
|
private suspend fun PipelineContext<Unit, ApplicationCall>.requestSign(
|
||||||
cmd: String,
|
cmd: String,
|
||||||
uin: String,
|
uin: String,
|
||||||
@ -415,10 +349,7 @@ private suspend fun PipelineContext<Unit, ApplicationCall>.requestSign(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||||
if (!initSigner()) {
|
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||||
respond(false, Status.InternalHandlerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val sign = withTimeoutOrNull(5000) {
|
val sign = withTimeoutOrNull(5000) {
|
||||||
|
@ -2,13 +2,13 @@ package moe.fuqiuluo.shamrock.remote.api
|
|||||||
|
|
||||||
import com.tencent.mobileqq.dt.model.FEBound
|
import com.tencent.mobileqq.dt.model.FEBound
|
||||||
import io.ktor.server.routing.Routing
|
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.Protocol
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.QSignDtConfig
|
import moe.fuqiuluo.shamrock.remote.structures.QSignDtConfig
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.Status
|
import moe.fuqiuluo.shamrock.remote.structures.Status
|
||||||
import moe.fuqiuluo.shamrock.tools.*
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import oicq.wlogin_sdk.tlv_type.tlv_t100
|
import oicq.wlogin_sdk.tlv_type.tlv_t100
|
||||||
import oicq.wlogin_sdk.tlv_type.tlv_t106
|
import oicq.wlogin_sdk.tlv_type.tlv_t106
|
||||||
@ -20,8 +20,8 @@ fun Routing.obtainProtocolData() {
|
|||||||
val cmd = fetchOrThrow("cmd")
|
val cmd = fetchOrThrow("cmd")
|
||||||
val isPb = fetchOrThrow("proto").toBooleanStrict()
|
val isPb = fetchOrThrow("proto").toBooleanStrict()
|
||||||
val buffer = fetchOrThrow("buffer").hex2ByteArray()
|
val buffer = fetchOrThrow("buffer").hex2ByteArray()
|
||||||
val resp = BaseSvc.sendBufferAW(cmd, isPb, buffer)
|
val resp = QQInterfaces.sendBufferAW(cmd, isPb, buffer)
|
||||||
respond(true, Status.Ok, data = resp?.toHexString() ?: "null", msg = "成功")
|
respond(true, Status.Ok, data = resp?.wupBuffer?.toHexString() ?: "null", msg = "成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrPost("/set_guid") {
|
getOrPost("/set_guid") {
|
||||||
|
@ -10,9 +10,7 @@ import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
|||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
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.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
||||||
import moe.fuqiuluo.shamrock.tools.getOrPost
|
import moe.fuqiuluo.shamrock.tools.getOrPost
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
@ -6,7 +6,6 @@ import io.ktor.server.application.createApplicationPlugin
|
|||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
private suspend fun ApplicationCall.checkToken() {
|
private suspend fun ApplicationCall.checkToken() {
|
||||||
val token = ShamrockConfig.getToken()
|
val token = ShamrockConfig.getToken()
|
||||||
|
@ -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<String> by lazy { mutableSetOf(
|
|
||||||
"trpc.msg.olpush.OlPushService.MsgPush",
|
|
||||||
|
|
||||||
) } // 非动态注册,永久常驻的包
|
|
||||||
private val HandlerByIpcSet = hashSetOf<String>()
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,6 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.flow.FlowCollector
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments
|
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.remote.service.data.push.SignDetail
|
||||||
import moe.fuqiuluo.shamrock.tools.ShamrockDsl
|
import moe.fuqiuluo.shamrock.tools.ShamrockDsl
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
internal object GlobalEventTransmitter: BaseSvc() {
|
internal object GlobalEventTransmitter: QQInterfaces() {
|
||||||
private val messageEventFlow by lazy {
|
private val messageEventFlow by lazy {
|
||||||
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
||||||
}
|
}
|
||||||
|
@ -1,284 +1,13 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.service.config
|
package moe.fuqiuluo.shamrock.remote.service.config
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.tencent.mmkv.MMKV
|
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import kotlinx.serialization.decodeFromString
|
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.tools.GlobalJson5
|
||||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader.moduleLoader
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import java.io.File
|
import java.util.Properties
|
||||||
|
|
||||||
internal object ShamrockConfig {
|
internal object ShamrockConfig: 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<ServiceConfig>(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) {
|
|
||||||
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<ConnectionConfig> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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<ServiceConfig>(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<ConnectionConfig> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
|||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler
|
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.qqinterface.servlet.transfile.RichMediaUploadHandler
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
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.ArrayList
|
||||||
import java.util.Collections
|
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
internal object AioListener : IKernelMsgListener {
|
internal object AioListener : IKernelMsgListener {
|
||||||
@ -63,7 +65,9 @@ internal object AioListener : IKernelMsgListener {
|
|||||||
|
|
||||||
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
|
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
|
||||||
MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "pong", { _, _ -> })
|
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()) {
|
val postType = if (record.senderUin == TicketSvc.getLongUin() && ShamrockConfig.enableSyncMsgAsSentMsg()) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.service.listener
|
package moe.fuqiuluo.shamrock.remote.service.listener
|
||||||
|
|
||||||
|
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
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.asString
|
||||||
import moe.fuqiuluo.shamrock.tools.readBuf32Long
|
import moe.fuqiuluo.shamrock.tools.readBuf32Long
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.PacketHandler
|
|
||||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
import protobuf.message.ContentHead
|
import protobuf.message.ContentHead
|
||||||
import protobuf.message.MsgBody
|
import protobuf.message.MsgBody
|
||||||
@ -32,16 +32,16 @@ import protobuf.message.ResponseHead
|
|||||||
import protobuf.push.*
|
import protobuf.push.*
|
||||||
|
|
||||||
internal object PrimitiveListener {
|
internal object PrimitiveListener {
|
||||||
fun registerListener() {
|
fun onPush(fromServiceMsg: FromServiceMsg) {
|
||||||
PacketHandler.register("trpc.msg.olpush.OlPushService.MsgPush") { _, buffer ->
|
if (fromServiceMsg.wupBuffer == null) return
|
||||||
|
try {
|
||||||
|
val push = fromServiceMsg.wupBuffer.slice(4)
|
||||||
|
.decodeProtobuf<MessagePush>()
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
try {
|
onMsgPush(push)
|
||||||
val push = buffer.slice(4).decodeProtobuf<MessagePush>()
|
|
||||||
onMsgPush(push)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,6 +188,8 @@ internal object PrimitiveListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) {
|
private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) {
|
||||||
|
if (body.msgContent == null) return
|
||||||
|
|
||||||
val event = runCatching {
|
val event = runCatching {
|
||||||
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
@ -200,6 +202,7 @@ internal object PrimitiveListener {
|
|||||||
}.decodeProtobuf<GroupCommonTipsEvent>()
|
}.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
}
|
}
|
||||||
val groupId = event.groupCode.toLong()
|
val groupId = event.groupCode.toLong()
|
||||||
|
if (event.uniqueTitleChangeDetail == null) return
|
||||||
val detail = event.uniqueTitleChangeDetail!!.first()
|
val detail = event.uniqueTitleChangeDetail!!.first()
|
||||||
|
|
||||||
//detail = if (detail[5] is ProtoList) {
|
//detail = if (detail[5] is ProtoList) {
|
||||||
|
16
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/AndroidX.kt
Normal file
16
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/AndroidX.kt
Normal file
@ -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() }
|
||||||
|
}
|
59
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt
Normal file
59
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt
Normal file
@ -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<TrpcOidb>()
|
||||||
|
}.getOrElse {
|
||||||
|
wupBuffer.let {
|
||||||
|
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||||
|
}.decodeProtobuf<TrpcOidb>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T: Protobuf<T>> FromServiceMsg.decodeToObject(): T {
|
||||||
|
return kotlin.runCatching {
|
||||||
|
wupBuffer.slice(4).let {
|
||||||
|
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||||
|
}.decodeProtobuf<T>()
|
||||||
|
}.getOrElse {
|
||||||
|
wupBuffer.let {
|
||||||
|
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||||
|
}.decodeProtobuf<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T: MessageMicro<T>> 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,8 @@ import mqq.app.MobileQQ
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal object PlatformUtils {
|
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 {
|
fun getQUA(): String {
|
||||||
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
|
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
package moe.fuqiuluo.shamrock.xposed
|
package moe.fuqiuluo.shamrock.xposed
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import de.robv.android.xposed.IXposedHookLoadPackage
|
import de.robv.android.xposed.IXposedHookLoadPackage
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import de.robv.android.xposed.XposedBridge.log
|
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.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.loader.KeepAlive
|
import moe.fuqiuluo.shamrock.xposed.loader.KeepAlive
|
||||||
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
|
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
|
||||||
import moe.fuqiuluo.shamrock.tools.FuzzySearchClass
|
import moe.fuqiuluo.shamrock.tools.FuzzySearchClass
|
||||||
|
import moe.fuqiuluo.shamrock.tools.GlobalUi
|
||||||
import moe.fuqiuluo.shamrock.tools.afterHook
|
import moe.fuqiuluo.shamrock.tools.afterHook
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
import moe.fuqiuluo.shamrock.xposed.hooks.runFirstActions
|
import moe.fuqiuluo.shamrock.xposed.hooks.runFirstActions
|
||||||
@ -156,6 +157,13 @@ internal class XposedEntry: IXposedHookLoadPackage {
|
|||||||
|
|
||||||
log("Process Name = $processName")
|
log("Process Name = $processName")
|
||||||
|
|
||||||
|
|
||||||
|
GlobalUi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
Handler.createAsync(ctx.mainLooper)
|
||||||
|
} else {
|
||||||
|
Handler(ctx.mainLooper)
|
||||||
|
}
|
||||||
|
|
||||||
runFirstActions(ctx)
|
runFirstActions(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import mqq.app.MobileQQ
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal object AppTalker {
|
internal object AppTalker {
|
||||||
val uriName = "content://moe.fuqiuluo.108.provider" // 你是真的闲,这都上个检测
|
private const val uriName = "content://moe.fuqiuluo.108.provider" // 你是真的闲,这都上个检测
|
||||||
val URI = Uri.parse(uriName)
|
private val URI = Uri.parse(uriName)
|
||||||
|
|
||||||
fun talk(values: ContentValues, onFailure: ((Throwable) -> Unit)? = null) {
|
fun talk(values: ContentValues, onFailure: ((Throwable) -> Unit)? = null) {
|
||||||
val ctx = MobileQQ.getContext()
|
val ctx = MobileQQ.getContext()
|
||||||
@ -17,4 +17,20 @@ internal object AppTalker {
|
|||||||
onFailure?.invoke(e)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
@ -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<Pair<ToServiceMsg, FromServiceMsg>>
|
||||||
|
|
||||||
|
internal object MSFHandler {
|
||||||
|
private val mPushHandlers = hashMapOf<String, MsfPush>()
|
||||||
|
private val mRespHandler = hashMapOf<Int, MsfResp>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ import kotlinx.coroutines.sync.Mutex
|
|||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
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.AioListener
|
||||||
import moe.fuqiuluo.shamrock.remote.service.listener.PrimitiveListener
|
import moe.fuqiuluo.shamrock.remote.service.listener.PrimitiveListener
|
||||||
import moe.fuqiuluo.shamrock.tools.hookMethod
|
import moe.fuqiuluo.shamrock.tools.hookMethod
|
||||||
@ -28,9 +27,6 @@ internal object NTServiceFetcher {
|
|||||||
val curHash = service.hashCode() + msgService.hashCode()
|
val curHash = service.hashCode() + msgService.hashCode()
|
||||||
if (isInitForNt(curHash)) return
|
if (isInitForNt(curHash)) return
|
||||||
|
|
||||||
PacketHandler.initPacketHandler()
|
|
||||||
PacketReceiver.init()
|
|
||||||
|
|
||||||
LogCenter.log("Fetch kernel service successfully: $curKernelHash,$curHash,${PlatformUtils.isMainProcess()}")
|
LogCenter.log("Fetch kernel service successfully: $curKernelHash,$curHash,${PlatformUtils.isMainProcess()}")
|
||||||
curKernelHash = curHash
|
curKernelHash = curHash
|
||||||
this.iKernelService = service
|
this.iKernelService = service
|
||||||
@ -38,7 +34,6 @@ internal object NTServiceFetcher {
|
|||||||
|
|
||||||
initNTKernelListener(msgService)
|
initNTKernelListener(msgService)
|
||||||
antiBackgroundMode(sessionService)
|
antiBackgroundMode(sessionService)
|
||||||
//hookGuildListener(sessionService)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +65,7 @@ internal object NTServiceFetcher {
|
|||||||
//groupService.addKernelGroupListener(GroupEventListener)
|
//groupService.addKernelGroupListener(GroupEventListener)
|
||||||
//LogCenter.log("Register Group listener successfully.")
|
//LogCenter.log("Register Group listener successfully.")
|
||||||
|
|
||||||
PrimitiveListener.registerListener()
|
//PrimitiveListener.registerListener()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<ToServiceMsg, FromServiceMsg>? = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, Any>? = 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<IPCRequest>()
|
|
||||||
private val cmdHandler = mutableMapOf<String, IPCRequest>()
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import moe.fuqiuluo.symbols.XposedHook
|
|||||||
|
|
||||||
@XposedHook(priority = 0)
|
@XposedHook(priority = 0)
|
||||||
internal class FixLibraryLoad: IAction {
|
internal class FixLibraryLoad: IAction {
|
||||||
val redirectedLibrary =arrayOf(
|
private val redirectedLibrary =arrayOf(
|
||||||
"ffmpegkit_abidetect",
|
"ffmpegkit_abidetect",
|
||||||
"avutil",
|
"avutil",
|
||||||
"swscale",
|
"swscale",
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,8 +20,6 @@ import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
|||||||
import moe.fuqiuluo.symbols.Process
|
import moe.fuqiuluo.symbols.Process
|
||||||
import moe.fuqiuluo.symbols.XposedHook
|
import moe.fuqiuluo.symbols.XposedHook
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import kotlin.concurrent.timer
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@XposedHook(Process.MAIN, priority = 10)
|
@XposedHook(Process.MAIN, priority = 10)
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,92 +4,39 @@ package moe.fuqiuluo.shamrock.xposed.hooks
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.shamrock.remote.HTTPServer
|
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
|
import moe.fuqiuluo.shamrock.tools.toast
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
|
||||||
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.symbols.Process
|
import moe.fuqiuluo.symbols.Process
|
||||||
import moe.fuqiuluo.symbols.XposedHook
|
import moe.fuqiuluo.symbols.XposedHook
|
||||||
import mqq.app.MobileQQ
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@XposedHook(Process.MAIN, priority = 1)
|
@XposedHook(Process.MAIN, priority = 1)
|
||||||
class PullConfig: IAction {
|
class PullConfig: IAction {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
external fun testNativeLibrary(): String
|
||||||
var isConfigOk = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun testNativeLibrary(): String
|
|
||||||
|
|
||||||
override fun invoke(ctx: Context) {
|
override fun invoke(ctx: Context) {
|
||||||
if (!PlatformUtils.isMainProcess()) return
|
if (!PlatformUtils.isMainProcess()) return
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
val isInit = ShamrockConfig.isInit()
|
||||||
DynamicReceiver.register("fetchPort", IPCRequest {
|
AppTalker.talk("init", onFailure = {
|
||||||
DataRequester.request("success", values = mapOf(
|
if (isInit) {
|
||||||
"port" to HTTPServer.currServerPort,
|
ctx.toast("Shamrock主进程未启动,将不会同步配置!")
|
||||||
"voice" to NativeLoader.isVoiceLoaded
|
} else {
|
||||||
))
|
ctx.toast("Shamrock主进程未启动,初始化失败!")
|
||||||
})
|
GlobalScope.launch {
|
||||||
DynamicReceiver.register("checkAndStartService", IPCRequest {
|
delay(3.seconds)
|
||||||
if (HTTPServer.isServiceStarted) {
|
exitProcess(1)
|
||||||
HTTPServer.isServiceStarted = false
|
|
||||||
}
|
}
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
ctx.toast("同步配置中...")
|
||||||
|
|
||||||
private fun initAppService(ctx: Context) {
|
|
||||||
NativeLoader.load("shamrock")
|
|
||||||
ctx.toast(testNativeLibrary())
|
|
||||||
runServiceActions(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.xposed.hooks.interacts
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
interface IInteract {
|
||||||
|
operator fun invoke(intent: Intent)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, IBinder>(
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,14 +47,16 @@ internal object NativeLoader {
|
|||||||
*/
|
*/
|
||||||
fun load(name: String) {
|
fun load(name: String) {
|
||||||
try {
|
try {
|
||||||
if (name == "shamrock"
|
if (name == "shamrock" || (name == "clover" && isEmu)) {
|
||||||
|| (name == "clover" && isEmu)
|
|
||||||
) {
|
|
||||||
onLoadByResource(name)
|
onLoadByResource(name)
|
||||||
} else if (!onLoadByAbsolutePath(name)) {
|
} else if (!onLoadByAbsolutePath(name)) {
|
||||||
onLoadByExternalFile(name)
|
onLoadByExternalFile(name)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
if (name == "shamrock" && onLoadByAbsolutePath(name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
LogCenter.log("LoadLibrary(name = $name) failed: ${e.stackTraceToString()}", Level.ERROR)
|
||||||
XposedBridge.log(e)
|
XposedBridge.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +97,7 @@ internal object NativeLoader {
|
|||||||
if (soDir.isFile) soDir.delete()
|
if (soDir.isFile) soDir.delete()
|
||||||
if (!soDir.exists()) soDir.mkdirs()
|
if (!soDir.exists()) soDir.mkdirs()
|
||||||
val soPath = getLibFilePath(name)
|
val soPath = getLibFilePath(name)
|
||||||
val soFile = File(soDir, name)
|
val soFile = File(soDir, "lib$name.so")
|
||||||
fun reloadSo(tmp: File? = null) {
|
fun reloadSo(tmp: File? = null) {
|
||||||
LogCenter.log("SO文件大小不一致或不存在,正在重新加载", Level.INFO)
|
LogCenter.log("SO文件大小不一致或不存在,正在重新加载", Level.INFO)
|
||||||
soFile.delete()
|
soFile.delete()
|
||||||
@ -107,7 +109,7 @@ internal object NativeLoader {
|
|||||||
if (!soFile.exists()) {
|
if (!soFile.exists()) {
|
||||||
reloadSo()
|
reloadSo()
|
||||||
} else {
|
} else {
|
||||||
val tmpSoFile = File(soDir, "$name.tmp").also { file ->
|
val tmpSoFile = File(soDir, "lib$name.so.tmp").also { file ->
|
||||||
if (file.exists()) file.delete()
|
if (file.exists()) file.delete()
|
||||||
file.outputStream().use {
|
file.outputStream().use {
|
||||||
moduleLoader.getResourceAsStream(soPath).use { origin ->
|
moduleLoader.getResourceAsStream(soPath).use { origin ->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user