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
|
||||
targetSdk = 34
|
||||
versionCode = getVersionCode()
|
||||
versionName = "1.0.9" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
versionName = "1.1.1.onebot" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
@ -12,7 +12,8 @@
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_moe_fuqiuluo_shamrock_xposed_hooks_PullConfig_testNativeLibrary(JNIEnv *env, jobject thiz) {
|
||||
Java_moe_fuqiuluo_shamrock_xposed_hooks_PullConfig_00024Companion_testNativeLibrary(JNIEnv *env,
|
||||
jobject thiz) {
|
||||
return env->NewStringUTF("加载Shamrock库成功~");
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@ -64,6 +63,7 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||
import moe.fuqiuluo.shamrock.ui.app.Logger
|
||||
@ -79,7 +79,7 @@ import moe.fuqiuluo.shamrock.ui.theme.RANDOM_SUB_TITLE
|
||||
import moe.fuqiuluo.shamrock.ui.theme.RANDOM_TITLE
|
||||
import moe.fuqiuluo.shamrock.ui.theme.ShamrockTheme
|
||||
import moe.fuqiuluo.shamrock.ui.tools.NoIndication
|
||||
import moe.fuqiuluo.shamrock.ui.tools.ShamrockTab
|
||||
import moe.fuqiuluo.shamrock.ui.tools.ShamrockTabV2
|
||||
import moe.fuqiuluo.shamrock.ui.tools.getShamrockVersion
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@ -87,6 +87,15 @@ class MainActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
delay(5_000) // Delay in milliseconds
|
||||
broadcastToModule {
|
||||
putExtra("__cmd", "switch_status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalIndication provides NoIndication
|
||||
) {
|
||||
@ -336,7 +345,7 @@ private fun AnimatedTab(
|
||||
}
|
||||
}
|
||||
|
||||
ShamrockTab(
|
||||
ShamrockTabV2(
|
||||
selected = curSelected,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
|
@ -30,6 +30,7 @@ abstract class ModuleHandler {
|
||||
}
|
||||
}
|
||||
putExtra("__hash", callbackId)
|
||||
putExtra("__cmd", cmd)
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ class MultifunctionalProvider: ContentProvider() {
|
||||
|
||||
inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) {
|
||||
val intent = Intent()
|
||||
intent.action = "moe.fuqiuluo.xqbot.dynamic"
|
||||
intent.action = "moe.fuqiuluo.onebot.dynamic"
|
||||
intent.intentBuilder()
|
||||
sendBroadcast(intent)
|
||||
}
|
@ -242,13 +242,13 @@ private fun Placeable.PlacementScope.placeTextAndIcon(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShamrockTab(
|
||||
fun ShamrockTabV2(
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
icon: @Composable (() -> Unit)? = null,
|
||||
text: (@Composable (() -> Unit))? = null,
|
||||
icon: (@Composable (() -> Unit))? = null,
|
||||
selectedContentColor: Color = GlobalColor.TabSelected,
|
||||
unselectedContentColor: Color = selectedContentColor,
|
||||
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
|
||||
@ -262,7 +262,7 @@ fun ShamrockTab(
|
||||
ProvideTextStyle(style, content = text)
|
||||
}
|
||||
}
|
||||
ShamrockTab(
|
||||
ShamrockTabV2(
|
||||
selected,
|
||||
onClick,
|
||||
modifier,
|
||||
@ -277,7 +277,7 @@ fun ShamrockTab(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShamrockTab(
|
||||
fun ShamrockTabV2(
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -94,7 +94,8 @@ data class DeleteReq(
|
||||
|
||||
@Serializable
|
||||
data class DownloadRkeyReq(
|
||||
@ProtoNumber(1) val types: List<Int>
|
||||
@ProtoNumber(1) val types: List<Int>,
|
||||
@ProtoNumber(2) val downloadType: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -52,11 +52,11 @@ data class DownloadRkeyRsp(
|
||||
|
||||
@Serializable
|
||||
data class RKeyInfo(
|
||||
@ProtoNumber(1) val rkey: String?,
|
||||
@ProtoNumber(1) val rkey: String,
|
||||
@ProtoNumber(2) val rkeyTtlSec: ULong?,
|
||||
@ProtoNumber(3) val storeId: UInt = 0u,
|
||||
@ProtoNumber(4) val rkeyCreateTime: UInt?,
|
||||
@ProtoNumber(4) val type: UInt?,
|
||||
@ProtoNumber(4) val type: UInt,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -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.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.Packet
|
||||
import tencent.im.oidb.cmd0x11b2.oidb_0x11b2
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object CardSvc: BaseSvc() {
|
||||
internal object CardSvc: QQInterfaces() {
|
||||
private val GetModelShowLock by lazy {
|
||||
Mutex()
|
||||
}
|
||||
@ -46,7 +48,8 @@ internal object CardSvc: BaseSvc() {
|
||||
|
||||
val resp = sendBufferAW("VipCustom.GetCustomOnlineStatus", false, uniPacket.encode())
|
||||
?: error("unable to fetch contact model_show")
|
||||
Packet.decodePacket(resp, "rsp", GetCustomOnlineStatusRsp()).sBuffer
|
||||
val buffer = resp.wupBuffer
|
||||
Packet.decodePacket(buffer, "rsp", GetCustomOnlineStatusRsp()).sBuffer
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,10 +82,9 @@ internal object CardSvc: BaseSvc() {
|
||||
reqBody.uin.set(peerId)
|
||||
reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$peerId")
|
||||
|
||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
|
||||
?: error("unable to fetch contact ark_json_text")
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
|
||||
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return rsp.signed_ark_msg.get()
|
||||
|
@ -1,11 +1,12 @@
|
||||
package moe.fuqiuluo.qqinterface.servlet
|
||||
|
||||
import kotlinx.serialization.encodeToByteArray
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import protobuf.auto.toByteArray
|
||||
|
||||
import protobuf.oidb.cmd0x9082.Oidb0x9082
|
||||
|
||||
internal object ChatSvc: BaseSvc() {
|
||||
internal object ChatSvc: QQInterfaces() {
|
||||
fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) {
|
||||
val serviceId = if (isSet) 1 else 2
|
||||
sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, Oidb0x9082(
|
||||
|
@ -6,9 +6,11 @@ import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.oidb.cmd0x6d7.CreateFolderReq
|
||||
import protobuf.oidb.cmd0x6d7.DeleteFolderReq
|
||||
@ -21,8 +23,10 @@ import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo
|
||||
import protobuf.auto.toByteArray
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object FileSvc: BaseSvc() {
|
||||
internal object FileSvc: QQInterfaces() {
|
||||
suspend fun createFileFolder(groupId: Long, folderName: String, parentFolderId: String = "/"): Result<GroupFileCommonFolderInfo> {
|
||||
val data = Oidb0x6d7ReqBody(
|
||||
createFolder = CreateFolderReq(
|
||||
@ -32,10 +36,9 @@ internal object FileSvc: BaseSvc() {
|
||||
folderName = folderName
|
||||
)
|
||||
).toByteArray()
|
||||
val resultBuffer = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
||||
?: return Result.failure(Exception("unable to fetch result"))
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(resultBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
.decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
@ -46,21 +49,20 @@ internal object FileSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
suspend fun deleteGroupFolder(groupId: Long, folderUid: String): Boolean {
|
||||
val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
|
||||
deleteFolder = DeleteFolderReq(
|
||||
groupCode = groupId.toULong(),
|
||||
appId = 3u,
|
||||
folderId = folderUid
|
||||
)
|
||||
).toByteArray()) ?: return false
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(buffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
return rsp.deleteFolder?.retCode == 0
|
||||
}
|
||||
|
||||
suspend fun moveGroupFolder(groupId: Long, folderUid: String, newParentFolderUid: String): Boolean {
|
||||
val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, Oidb0x6d7ReqBody(
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, Oidb0x6d7ReqBody(
|
||||
moveFolder = MoveFolderReq(
|
||||
groupCode = groupId.toULong(),
|
||||
appId = 3u,
|
||||
@ -68,14 +70,13 @@ internal object FileSvc: BaseSvc() {
|
||||
parentFolderId = "/"
|
||||
)
|
||||
).toByteArray()) ?: return false
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(buffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
return rsp.moveFolder?.retCode == 0
|
||||
}
|
||||
|
||||
suspend fun renameFolder(groupId: Long, folderUid: String, name: String): Boolean {
|
||||
val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
|
||||
renameFolder = RenameFolderReq(
|
||||
groupCode = groupId.toULong(),
|
||||
appId = 3u,
|
||||
@ -83,8 +84,7 @@ internal object FileSvc: BaseSvc() {
|
||||
folderName = name
|
||||
)
|
||||
).toByteArray()) ?: return false
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(buffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
return rsp.renameFolder?.retCode == 0
|
||||
}
|
||||
@ -101,8 +101,7 @@ internal object FileSvc: BaseSvc() {
|
||||
}
|
||||
val result = sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
||||
?: return false
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(result.slice(4))
|
||||
val oidbPkg = result.decodeToOidb()
|
||||
val rsp = oidb_0x6d6.RspBody().apply {
|
||||
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
|
||||
}
|
||||
@ -120,8 +119,8 @@ internal object FileSvc: BaseSvc() {
|
||||
val fileCnt: Int
|
||||
val limitCnt: Int
|
||||
if (rspGetFileCntBuffer != null) {
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(rspGetFileCntBuffer.slice(4))
|
||||
oidb_0x6d8.RspBody().mergeFrom(
|
||||
rspGetFileCntBuffer.decodeToOidb()
|
||||
.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
).group_file_cnt_rsp.apply {
|
||||
@ -141,8 +140,8 @@ internal object FileSvc: BaseSvc() {
|
||||
val totalSpace: Long
|
||||
val usedSpace: Long
|
||||
if (rspGetFileSpaceBuffer != null) {
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(rspGetFileSpaceBuffer.slice(4))
|
||||
oidb_0x6d8.RspBody().mergeFrom(
|
||||
rspGetFileSpaceBuffer.decodeToOidb()
|
||||
.bytes_bodybuffer.get()
|
||||
.toByteArray()).group_space_rsp.apply {
|
||||
totalSpace = uint64_total_space.get()
|
||||
@ -187,15 +186,13 @@ internal object FileSvc: BaseSvc() {
|
||||
|
||||
uint32_show_onlinedoc_folder.set(0)
|
||||
})
|
||||
}.toByteArray(), timeout = 15_000L)
|
||||
}.toByteArray(), timeout = 15.seconds)
|
||||
|
||||
return kotlin.runCatching {
|
||||
val files = arrayListOf<FileInfo>()
|
||||
val dirs = arrayListOf<FolderInfo>()
|
||||
if (rspGetFileListBuffer != null) {
|
||||
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(rspGetFileListBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
val oidb = rspGetFileListBuffer.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
|
||||
.file_list_info_rsp.apply {
|
||||
@ -242,7 +239,7 @@ internal object FileSvc: BaseSvc() {
|
||||
|
||||
GroupFileList(files, dirs)
|
||||
}.onFailure {
|
||||
LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer.toHexString()}", Level.ERROR)
|
||||
LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer?.wupBuffer?.toHexString()}", Level.ERROR)
|
||||
}
|
||||
}
|
||||
}
|
@ -13,13 +13,15 @@ import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.AppRuntime
|
||||
import tencent.mobileim.structmsg.structmsg
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object FriendSvc: BaseSvc() {
|
||||
internal object FriendSvc: QQInterfaces() {
|
||||
|
||||
suspend fun getFriendList(refresh: Boolean): Result<List<Friends>> {
|
||||
val runtime = AppRuntimeFetcher.appRuntime
|
||||
@ -91,8 +93,7 @@ internal object FriendSvc: BaseSvc() {
|
||||
ArrayList()
|
||||
} else {
|
||||
try {
|
||||
val msg = structmsg.RspSystemMsgNew()
|
||||
msg.mergeFrom(respBuffer.slice(4))
|
||||
val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew())
|
||||
return msg.friendmsgs.get()
|
||||
} catch (err: Throwable) {
|
||||
requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||
|
@ -19,9 +19,12 @@ import moe.fuqiuluo.qqinterface.servlet.structures.SlowModeInfo
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.guild.GetGuildFeedsReq
|
||||
@ -44,7 +47,7 @@ import protobuf.qweb.QWebRsp
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object GProSvc: BaseSvc() {
|
||||
internal object GProSvc: QQInterfaces() {
|
||||
fun getSelfTinyId(): ULong {
|
||||
val service = app.getRuntimeService(IGPSService::class.java, "all")
|
||||
return service.selfTinyId.toULong()
|
||||
@ -57,12 +60,8 @@ internal object GProSvc: BaseSvc() {
|
||||
u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u)
|
||||
),
|
||||
guildInfo = Oidb0xf57GuildInfo(guildId = guildId)
|
||||
).toByteArray())
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
if (respBuffer == null) {
|
||||
return Result.failure(Exception("unable to send packet"))
|
||||
}
|
||||
body.mergeFrom(respBuffer.slice(4))
|
||||
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
||||
val body = respBuffer.decodeToOidb()
|
||||
return runCatching {
|
||||
body.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
@ -71,7 +70,7 @@ internal object GProSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result<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,
|
||||
qua = PlatformUtils.getQUA(),
|
||||
deviceInfo = DEFAULT_DEVICE_INFO,
|
||||
@ -92,7 +91,7 @@ internal object GProSvc: BaseSvc() {
|
||||
QWebExtInfo("tiny_id", getSelfTinyId().toString()),
|
||||
)
|
||||
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
||||
val webRsp = buffer.slice(4).decodeProtobuf<QWebRsp>()
|
||||
val webRsp = fromServiceMsg.decodeToObject<QWebRsp>()
|
||||
if(webRsp.buffer == null) return Result.failure(Exception("server error"))
|
||||
val wupBuffer = webRsp.buffer!!
|
||||
val feeds = wupBuffer.decodeProtobuf<GetGuildFeedsRsp>()
|
||||
@ -188,12 +187,8 @@ internal object GProSvc: BaseSvc() {
|
||||
memberId = 0uL,
|
||||
tinyId = memberTinyId,
|
||||
guildId = guildId
|
||||
).toByteArray())
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
if (respBuffer == null) {
|
||||
return Result.failure(Exception("unable to send packet"))
|
||||
}
|
||||
body.mergeFrom(respBuffer.slice(4))
|
||||
).toByteArray()) ?: return Result.failure(Exception("unable to send packet"))
|
||||
val body = respBuffer.decodeToOidb()
|
||||
return runCatching {
|
||||
body.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0xf88Rsp>().userInfo!!
|
||||
}
|
||||
|
@ -10,13 +10,16 @@ import com.tencent.mobileqq.app.BusinessHandlerFactory
|
||||
import com.tencent.mobileqq.app.QQAppInterface
|
||||
import com.tencent.mobileqq.data.troop.TroopInfo
|
||||
import com.tencent.mobileqq.data.troop.TroopMemberInfo
|
||||
import com.tencent.mobileqq.data.troop.TroopMemberNickInfo
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import com.tencent.mobileqq.troop.api.ITroopInfoService
|
||||
import com.tencent.mobileqq.troop.api.ITroopMemberInfoService
|
||||
import com.tencent.protofile.join_group_link.join_group_link
|
||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.troopmemberlist.ITroopMemberListRepoApi
|
||||
import friendlist.stUinInfo
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
@ -66,13 +69,18 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.asLong
|
||||
import moe.fuqiuluo.shamrock.tools.asString
|
||||
import moe.fuqiuluo.shamrock.tools.asStringOrNull
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_65_VER
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.cmd0xf16.Oidb0xf16
|
||||
@ -94,8 +102,9 @@ import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object GroupSvc: BaseSvc() {
|
||||
internal object GroupSvc: QQInterfaces() {
|
||||
private const val GET_MEMBER_ROLE_BY_NT = false
|
||||
|
||||
private val RefreshTroopMemberInfoLock by lazy {
|
||||
@ -112,15 +121,14 @@ internal object GroupSvc: BaseSvc() {
|
||||
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method
|
||||
|
||||
suspend fun getGroupRemainAtAllRemain (groupId: Long): Result<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_limit_interval_type_for_uin.set(2)
|
||||
uint32_limit_interval_type_for_group.set(1)
|
||||
uint64_uin.set(getLongUin())
|
||||
uint64_group_code.set(groupId)
|
||||
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
@ -133,7 +141,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
))
|
||||
}
|
||||
suspend fun getProhibitedMemberList(groupId: Long): Result<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_start_uin.set(0)
|
||||
uint32_identify_flag.set(6)
|
||||
@ -142,8 +150,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
uint32_shutup_timestap.set(0)
|
||||
})
|
||||
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
@ -241,10 +248,10 @@ internal object GroupSvc: BaseSvc() {
|
||||
toServiceMsg.extraData.putLong("troop_code", groupId)
|
||||
toServiceMsg.extraData.putBoolean("is_admin", false)
|
||||
toServiceMsg.extraData.putInt("from", 0)
|
||||
val buffer = sendAW(toServiceMsg)
|
||||
val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时"))
|
||||
val uniPacket = UniPacket(true)
|
||||
uniPacket.encodeName = "utf-8"
|
||||
uniPacket.decode(buffer)
|
||||
uniPacket.decode(fromServiceMsg.wupBuffer)
|
||||
val respBatchProcess = uniPacket.getByClass("RespBatchProcess", RespBatchProcess())
|
||||
val batchRespInfo = oidb_0x88d.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(respBatchProcess.batch_response_list.first().buffer)
|
||||
@ -308,7 +315,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
info.dwFlag = 1
|
||||
createToServiceMsg.extraData.putSerializable("vecUinInfo", arrayListOf(info))
|
||||
createToServiceMsg.extraData.putLong("dwNewSeq", 0L)
|
||||
send(createToServiceMsg)
|
||||
sendToServiceMsg(createToServiceMsg)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -324,13 +331,12 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
suspend fun setEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair<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)
|
||||
msg_seq.set(seq.toInt())
|
||||
msg_random.set(rand.toInt())
|
||||
}.toByteArray()) ?: return Pair(false, "unknown error")
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return if (result.wording.has()) {
|
||||
LogCenter.log("设置群精华失败: ${result.wording.get()}")
|
||||
@ -342,16 +348,12 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
suspend fun deleteEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair<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)
|
||||
msg_seq.set(seq.toInt())
|
||||
msg_random.set(rand.toInt())
|
||||
}.toByteArray())
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
if (buffer == null) {
|
||||
return Pair(false, "unknown error")
|
||||
}
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
}.toByteArray()) ?: return Pair(false, "unknown error")
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return if (result.wording.has()) {
|
||||
LogCenter.log("移除群精华失败: ${result.wording.get()}")
|
||||
@ -565,10 +567,9 @@ internal object GroupSvc: BaseSvc() {
|
||||
reqBody.get_ark.set(true)
|
||||
reqBody.type.set(1)
|
||||
reqBody.group_code.set(groupId)
|
||||
val buffer = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray())
|
||||
val fromServiceMsg = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray())
|
||||
?: error("unable to fetch contact ark_json_text")
|
||||
val body = join_group_link.RspBody()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToObject(join_group_link.RspBody())
|
||||
return body.signed_ark.get().toStringUtf8()
|
||||
}
|
||||
|
||||
@ -600,8 +601,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
req.uint32_rich_card_name_ver.set(1)
|
||||
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
||||
if (respBuffer != null) {
|
||||
val rsp = group_member_info.RspBody()
|
||||
rsp.mergeFrom(respBuffer.slice(4))
|
||||
val rsp = respBuffer.decodeToObject(group_member_info.RspBody())
|
||||
if (rsp.msg_meminfo.str_location.has()) {
|
||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||
}
|
||||
@ -627,16 +627,50 @@ 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(
|
||||
groupId: Long,
|
||||
uin: Long,
|
||||
refresh: Boolean = false
|
||||
): Result<TroopMemberInfo> {
|
||||
var info: TroopMemberInfo? = null
|
||||
if (PlatformUtils.getQQVersionCode() <= QQ_9_0_65_VER) {
|
||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||
var info = service.getTroopMember(groupId.toString(), uin.toString())
|
||||
info = service.getTroopMember(groupId.toString(), uin.toString())
|
||||
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) {
|
||||
info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull()
|
||||
}
|
||||
} else {
|
||||
info = getTroopMemberInfoByUinFromNt(groupId, uin).getOrNull()
|
||||
}
|
||||
if (info == null) {
|
||||
info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let {
|
||||
TroopMemberInfo().apply {
|
||||
@ -645,6 +679,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PlatformUtils.getQQVersionCode() <= QQ_9_0_8_VER) {
|
||||
try {
|
||||
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
||||
val req = group_member_info.ReqBody()
|
||||
@ -653,10 +688,9 @@ internal object GroupSvc: BaseSvc() {
|
||||
req.bool_new_client.set(true)
|
||||
req.uint32_client_type.set(1)
|
||||
req.uint32_rich_card_name_ver.set(1)
|
||||
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000)
|
||||
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2.seconds)
|
||||
if (respBuffer != null) {
|
||||
val rsp = group_member_info.RspBody()
|
||||
rsp.mergeFrom(respBuffer.slice(4))
|
||||
val rsp = respBuffer.decodeToObject(group_member_info.RspBody())
|
||||
if (rsp.msg_meminfo.str_location.has()) {
|
||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||
}
|
||||
@ -675,6 +709,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
} catch (err: Throwable) {
|
||||
LogCenter.log(err.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
@ -682,7 +717,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTroopMemberInfoByUinViaNt(
|
||||
private suspend fun getTroopMemberInfoByUinViaNt(
|
||||
groupId: Long,
|
||||
qq: Long,
|
||||
timeout: Long = 5000L
|
||||
@ -711,7 +746,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群成员信息失败"))
|
||||
Result.failure(Exception("[NT]获取群成员信息失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -933,7 +968,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray())
|
||||
?: return Result.failure(Exception("操作失败"))
|
||||
val rsp = structmsg.RspSystemMsgAction().mergeFrom(respBuffer.slice(4))
|
||||
val rsp = respBuffer.decodeToObject(structmsg.RspSystemMsgAction())
|
||||
return if (rsp.head.result.has()) {
|
||||
if (rsp.head.result.get() == 0) {
|
||||
Result.success(rsp.msg_detail.get())
|
||||
@ -984,8 +1019,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
ArrayList()
|
||||
} else {
|
||||
try {
|
||||
val msg = structmsg.RspSystemMsgNew()
|
||||
msg.mergeFrom(respBuffer.slice(4))
|
||||
val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew())
|
||||
return msg.groupmsgs.get().orEmpty()
|
||||
} catch (err: Throwable) {
|
||||
requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||
@ -1178,8 +1212,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
return if (buffer == null) {
|
||||
Result.failure(Exception("操作失败"))
|
||||
} else {
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = buffer.decodeToOidb()
|
||||
val rsp = oidb_0xeb7.RspBody()
|
||||
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
val doneInfo = rsp.signInWriteRsp.doneInfo
|
||||
|
@ -6,10 +6,12 @@ import com.tencent.mobileqq.msf.service.MsfService
|
||||
import com.tencent.proto.lbsshare.LBSShare
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import moe.fuqiuluo.shamrock.helper.IllegalParamsException
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
internal object LbsSvc: BaseSvc() {
|
||||
internal object LbsSvc: QQInterfaces() {
|
||||
suspend fun tryShareLocation(chatType: Int, peerId: Long, lat: Double, lon: Double): Result<Unit> {
|
||||
val req = LbsSendInfo.SendMessageReq()
|
||||
req.uint64_peer_account.set(peerId)
|
||||
@ -24,8 +26,8 @@ internal object LbsSvc: BaseSvc() {
|
||||
}.getOrNull())
|
||||
req.str_lat.set(lat.toString())
|
||||
req.str_lng.set(lon.toString())
|
||||
sendPb("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", req.toByteArray(), MsfService.getCore().nextSeq)
|
||||
|
||||
sendBuffer("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", true, req.toByteArray())
|
||||
//sendPb("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", req.toByteArray(), MsfService.getCore().nextSeq)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
@ -50,8 +52,7 @@ internal object LbsSvc: BaseSvc() {
|
||||
req.imei.set("")
|
||||
val buffer = sendBufferAW("LbsShareSvr.location", true, req.toByteArray())
|
||||
?: return Result.failure(Exception("获取位置失败"))
|
||||
val resp = LBSShare.LocationResp()
|
||||
resp.mergeFrom(buffer.slice(4))
|
||||
val resp = buffer.decodeToObject(LBSShare.LocationResp())
|
||||
val location = resp.mylbs
|
||||
return Result.success(location.addr.get())
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
@ -33,9 +34,10 @@ import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object MsgSvc : BaseSvc() {
|
||||
private suspend fun prepareTempChatFromGroup(
|
||||
internal object MsgSvc : QQInterfaces() {
|
||||
suspend fun prepareTempChatFromGroup(
|
||||
groupId: String,
|
||||
peerId: String
|
||||
): Result<Unit> {
|
||||
@ -418,13 +420,9 @@ internal object MsgSvc : BaseSvc() {
|
||||
)
|
||||
).toByteArray()
|
||||
|
||||
val buffer = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 30_000)
|
||||
val buffer = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 30.seconds)
|
||||
?: return Result.failure(Exception("unable to upload multi message, response timeout"))
|
||||
val rsp = runCatching {
|
||||
buffer.slice(4).decodeProtobuf<LongMsgRsp>()
|
||||
}.getOrElse {
|
||||
buffer.decodeProtobuf<LongMsgRsp>()
|
||||
}
|
||||
val rsp = buffer.decodeToObject<LongMsgRsp>()
|
||||
val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message"))
|
||||
return Result.success(MessageSegment(
|
||||
type = "forward",
|
||||
@ -456,7 +454,7 @@ internal object MsgSvc : BaseSvc() {
|
||||
true,
|
||||
req.toByteArray()
|
||||
) ?: return Result.failure(Exception("unable to get multi message"))
|
||||
val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
|
||||
val rsp = buffer.decodeToObject<LongMsgRsp>()
|
||||
val zippedPayload = DeflateTools.ungzip(
|
||||
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.tools.broadcast
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.message.*
|
||||
@ -21,7 +22,7 @@ import protobuf.push.MessagePush
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.text.toByteArray
|
||||
|
||||
internal object PacketSvc : BaseSvc() {
|
||||
internal object PacketSvc : QQInterfaces() {
|
||||
/**
|
||||
* 伪造收到Json卡片消息
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@ import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.shamrock.utils.MD5
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import protobuf.fav.WeiyunAddRichMediaReq
|
||||
import protobuf.fav.WeiyunAuthor
|
||||
import protobuf.fav.WeiyunCollectCommInfo
|
||||
@ -49,7 +50,7 @@ import kotlin.coroutines.resume
|
||||
/**
|
||||
* QQ收藏相关接口
|
||||
*/
|
||||
internal object QFavSvc: BaseSvc() {
|
||||
internal object QFavSvc: QQInterfaces() {
|
||||
private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also {
|
||||
it.isIpv6 = false
|
||||
it.mIp = "collector.weiyun.com"
|
||||
@ -275,7 +276,7 @@ internal object QFavSvc: BaseSvc() {
|
||||
|
||||
override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
|
||||
}
|
||||
val vi = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin)
|
||||
val vi = ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin)
|
||||
//LogCenter.log(pSKey)
|
||||
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
|
||||
httpNetReq.mSendData = BytePacketBuilder().apply {
|
||||
@ -381,7 +382,7 @@ internal object QFavSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
private fun getWeiYunPSKey(): String {
|
||||
val pskey = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager)
|
||||
val pskey = ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager)
|
||||
.getPskey(app.currentAccountUin, 16L, arrayOf("weiyun.com"), WeiYunPSKeyPromise)
|
||||
return if (pskey != null) pskey.getPSkey("weiyun.com") else ""
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import QQService.SvcDevLoginInfo
|
||||
import QQService.SvcReqGetDevLoginInfo
|
||||
import QQService.SvcRspGetDevLoginInfo
|
||||
import com.qq.jce.wup.UniPacket
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.MobileQQ
|
||||
import mqq.app.Packet
|
||||
import oicq.wlogin_sdk.tools.util
|
||||
|
||||
internal object QSafeSvc: BaseSvc() {
|
||||
internal object QSafeSvc: QQInterfaces() {
|
||||
|
||||
suspend fun getOnlineClients(): ArrayList<SvcDevLoginInfo>? {
|
||||
val req = SvcReqGetDevLoginInfo()
|
||||
@ -26,7 +26,7 @@ internal object QSafeSvc: BaseSvc() {
|
||||
val resp = sendBufferAW("StatSvc.GetDevLoginInfo", false, uniPacket.encode())
|
||||
?: return null
|
||||
|
||||
return Packet.decodePacket(resp, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo
|
||||
return Packet.decodePacket(resp.wupBuffer, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,13 +8,15 @@ import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.BigDataTicket
|
||||
import moe.fuqiuluo.shamrock.tools.GlobalClientNoRedirect
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.MobileQQ
|
||||
import mqq.manager.TicketManager
|
||||
import oicq.wlogin_sdk.request.Ticket
|
||||
import tencent.im.oidb.oidb_sso
|
||||
|
||||
internal object TicketSvc: BaseSvc() {
|
||||
internal object TicketSvc: QQInterfaces() {
|
||||
object SigType {
|
||||
const val WLOGIN_A5 = 2
|
||||
const val WLOGIN_RESERVED = 16
|
||||
@ -109,23 +111,23 @@ internal object TicketSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
fun getTicket(uin: String, id: Int): Ticket? {
|
||||
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getLocalTicket(uin, id)
|
||||
return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getLocalTicket(uin, id)
|
||||
}
|
||||
|
||||
fun getStWeb(uin: String): String {
|
||||
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getStweb(uin)
|
||||
return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getStweb(uin)
|
||||
}
|
||||
|
||||
fun getSKey(uin: String): String {
|
||||
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getSkey(uin)
|
||||
return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getSkey(uin)
|
||||
}
|
||||
|
||||
fun getRealSkey(uin: String): String {
|
||||
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getRealSkey(uin)
|
||||
return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getRealSkey(uin)
|
||||
}
|
||||
|
||||
fun getPSKey(uin: String): String {
|
||||
val manager = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager)
|
||||
val manager = ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager)
|
||||
manager.reloadCache(MobileQQ.getContext())
|
||||
return manager.getSuperkey(uin) ?: ""
|
||||
}
|
||||
@ -135,14 +137,13 @@ internal object TicketSvc: BaseSvc() {
|
||||
req.domains.set(domain.toList())
|
||||
val buffer = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray())
|
||||
?: return Result.failure(Exception("getLessPSKey failed"))
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = buffer.decodeToOidb()
|
||||
val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return Result.success(rsp.private_keys.get())
|
||||
}
|
||||
|
||||
suspend fun getPSKey(uin: String, domain: String): String? {
|
||||
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPskey(uin, domain).let {
|
||||
return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPskey(uin, domain).let {
|
||||
if (it.isNullOrBlank())
|
||||
getLessPSKey(domain).getOrNull()?.firstOrNull()?.key?.get()
|
||||
else it
|
||||
@ -150,7 +151,7 @@ internal object TicketSvc: BaseSvc() {
|
||||
}
|
||||
|
||||
fun getPt4Token(uin: String, domain: String): String? {
|
||||
return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPt4Token(uin, domain)
|
||||
return ((app as QQAppInterface).getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPt4Token(uin, domain)
|
||||
}
|
||||
|
||||
suspend fun GetHttpCookies(appid: String, daid: String, jumpurl: String): String? {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package moe.fuqiuluo.qqinterface.servlet
|
||||
|
||||
internal object VisitorSvc: BaseSvc() {
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
|
||||
internal object VisitorSvc: QQInterfaces() {
|
||||
const val FROM_C2C_AIO = 2
|
||||
const val FROM_CONDITION_SEARCH = 9
|
||||
const val FROM_CONTACTS_TAB = 5
|
||||
|
@ -1,11 +1,11 @@
|
||||
package moe.fuqiuluo.qqinterface.servlet.ark
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
|
||||
|
||||
internal object ArkMsgSvc: BaseSvc() {
|
||||
internal object ArkMsgSvc: QQInterfaces() {
|
||||
fun tryShareMusic(
|
||||
chatType: Int,
|
||||
peerId: Long,
|
||||
|
@ -1,8 +1,8 @@
|
||||
package moe.fuqiuluo.qqinterface.servlet.ark
|
||||
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.lightapp.AdaptShareInfoReq
|
||||
@ -11,7 +11,7 @@ import protobuf.qweb.DEFAULT_DEVICE_INFO
|
||||
import protobuf.qweb.QWebReq
|
||||
import protobuf.qweb.QWebRsp
|
||||
|
||||
internal object LightAppSvc: BaseSvc() {
|
||||
internal object LightAppSvc: QQInterfaces() {
|
||||
suspend fun adaptShareJumpUrl(
|
||||
arkAppInfo: ArkAppInfo,
|
||||
coverUrl: String,
|
||||
@ -37,7 +37,7 @@ internal object LightAppSvc: BaseSvc() {
|
||||
webURL = url,
|
||||
).toByteArray(),
|
||||
traceId = app.account + "_0_0",
|
||||
).toByteArray())?.decodeProtobuf<QWebRsp>()?.buffer?.decodeProtobuf<AdaptShareInfoResp>()
|
||||
).toByteArray())?.wupBuffer?.decodeProtobuf<QWebRsp>()?.buffer?.decodeProtobuf<AdaptShareInfoResp>()
|
||||
if (rsp == null || rsp.json.isNullOrEmpty())
|
||||
return Result.failure(Exception("unable to adapt ShareInfo"))
|
||||
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,}
|
||||
*/
|
||||
|
||||
val url = RichProtoSvc.getTempPicDownloadUrl(chatType, originalUrl, md5, image, storeId)
|
||||
|
||||
return MessageSegment(
|
||||
type = "image",
|
||||
data = hashMapOf(
|
||||
"file" to md5,
|
||||
"url" to when (chatType) {
|
||||
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
|
||||
originalUrl = originalUrl,
|
||||
md5 = md5,
|
||||
fileId = image.fileUuid,
|
||||
width = image.picWidth.toUInt(),
|
||||
height = image.picHeight.toUInt(),
|
||||
sha = "",
|
||||
fileSize = image.fileSize.toULong(),
|
||||
peer = peerId
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(
|
||||
originalUrl = originalUrl,
|
||||
md5 = md5,
|
||||
fileId = image.fileUuid,
|
||||
width = image.picWidth.toUInt(),
|
||||
height = image.picHeight.toUInt(),
|
||||
sha = "",
|
||||
fileSize = image.fileSize.toULong(),
|
||||
peer = peerId,
|
||||
storeId = storeId
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(
|
||||
originalUrl = originalUrl,
|
||||
md5 = md5,
|
||||
fileId = image.fileUuid,
|
||||
width = image.picWidth.toUInt(),
|
||||
height = image.picHeight.toUInt(),
|
||||
sha = "",
|
||||
fileSize = image.fileSize.toULong(),
|
||||
peer = peerId,
|
||||
subPeer = subPeer
|
||||
)
|
||||
|
||||
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||
},
|
||||
"url" to url,
|
||||
"subType" to image.picSubType,
|
||||
"type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
|
||||
)
|
||||
|
@ -124,11 +124,15 @@ internal class ElemMaker {
|
||||
else -> {
|
||||
qq = qqStr.toLong()
|
||||
type = 0
|
||||
"@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(
|
||||
peerId.toLong(),
|
||||
qq,
|
||||
true
|
||||
).let {
|
||||
val name = (data["name"].asStringOrNull
|
||||
?: GroupSvc.getTroopMemberInfoByUinV3(peerId.toLong(), qq).let {
|
||||
it?.troopNick
|
||||
.ifNullOrEmpty(it?.friendNick)
|
||||
.ifNullOrEmpty(it?.showName)
|
||||
.ifNullOrEmpty(it?.autoRemark)
|
||||
.ifNullOrEmpty(it?.colorNick)
|
||||
}
|
||||
?: GroupSvc.getTroopMemberInfoByUinV2(peerId.toLong(), qq, true).let {
|
||||
val info = it.getOrNull()
|
||||
if (info == null)
|
||||
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
|
||||
@ -136,6 +140,7 @@ internal class ElemMaker {
|
||||
.ifNullOrEmpty(info.friendnick)
|
||||
.ifNullOrEmpty(qqStr)
|
||||
})
|
||||
"@$name"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -856,7 +856,15 @@ internal object NtMsgElementMaker {
|
||||
.ifNullOrEmpty(qqStr)
|
||||
}"
|
||||
} else {
|
||||
at.content = "@$qqStr"
|
||||
at.content = "@${
|
||||
GroupSvc.getTroopMemberInfoByUinV3(peerId.toLong(), qq).let {
|
||||
it?.troopNick
|
||||
.ifNullOrEmpty(it?.friendNick)
|
||||
.ifNullOrEmpty(it?.showName)
|
||||
.ifNullOrEmpty(it?.autoRemark)
|
||||
.ifNullOrEmpty(it?.colorNick)
|
||||
} ?: qqStr
|
||||
}"
|
||||
}
|
||||
} else {
|
||||
at.content = "@$name"
|
||||
|
@ -16,26 +16,29 @@ import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToObject
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToTrpcOidb
|
||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import moe.fuqiuluo.shamrock.utils.MediaType
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.TrpcOidb
|
||||
import protobuf.oidb.cmd0x11c5.ClientMeta
|
||||
import protobuf.oidb.cmd0x11c5.CodecConfigReq
|
||||
import protobuf.oidb.cmd0x11c5.CommonHead
|
||||
import protobuf.oidb.cmd0x11c5.DownloadExt
|
||||
import protobuf.oidb.cmd0x11c5.DownloadReq
|
||||
import protobuf.oidb.cmd0x11c5.DownloadRkeyReq
|
||||
import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp
|
||||
import protobuf.oidb.cmd0x11c5.FileInfo
|
||||
import protobuf.oidb.cmd0x11c5.FileType
|
||||
import protobuf.oidb.cmd0x11c5.IndexNode
|
||||
@ -57,8 +60,9 @@ import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
import kotlin.random.nextULong
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object NtV2RichMediaSvc: BaseSvc() {
|
||||
internal object NtV2RichMediaSvc: QQInterfaces() {
|
||||
private val requestIdSeq = atomic(2L)
|
||||
|
||||
fun fetchGroupResUploadTo(): String {
|
||||
@ -320,6 +324,40 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
||||
return Result.success(result)
|
||||
}
|
||||
|
||||
suspend fun getTempNtRKey(): Result<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
|
||||
*/
|
||||
@ -386,8 +424,9 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
||||
)
|
||||
)
|
||||
).toByteArray()
|
||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)?.slice(4)
|
||||
buffer?.decodeProtobuf<TrpcOidb>()?.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.download?.rkeyParam?.let {
|
||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)
|
||||
?: return Result.failure(Exception("no response"))
|
||||
buffer.decodeToTrpcOidb().buffer.decodeProtobuf<NtV2RichMediaRsp>().download?.rkeyParam?.let {
|
||||
return Result.success(it)
|
||||
}
|
||||
}.onFailure {
|
||||
@ -470,17 +509,16 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
||||
).toByteArray()
|
||||
val buffer = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3_000)?.slice(4)
|
||||
sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3.seconds)
|
||||
?: return Result.failure(Exception("no response: timeout"))
|
||||
}
|
||||
MsgConstant.KCHATTYPEC2C -> {
|
||||
sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3_000)?.slice(4)
|
||||
sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3.seconds)
|
||||
?: return Result.failure(Exception("no response: timeout"))
|
||||
}
|
||||
|
||||
else -> return Result.failure(Exception("unknown chat type: $chatType"))
|
||||
}
|
||||
val rspBuffer = buffer.decodeProtobuf<TrpcOidb>().buffer
|
||||
val rspBuffer = buffer.decodeToTrpcOidb().buffer
|
||||
val rsp = rspBuffer.decodeProtobuf<NtV2RichMediaRsp>()
|
||||
if (rsp.upload == null) {
|
||||
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
|
||||
@ -522,7 +560,7 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
||||
)
|
||||
),
|
||||
).toByteArray())!!
|
||||
val rsp = rspBuffer.decodeProtobuf<Cmd0x388RspBody>()
|
||||
val rsp = rspBuffer.decodeToObject<Cmd0x388RspBody>()
|
||||
.msgTryUpImgRsp!!.first()
|
||||
TryUpPicData(
|
||||
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.protohandler.RichProto
|
||||
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.getNtPicRKey
|
||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
@ -38,7 +40,7 @@ private const val GPRO_PIC = "gchat.qpic.cn"
|
||||
private const val MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn"
|
||||
private const val C2C_PIC = "c2cpicdw.qpic.cn"
|
||||
|
||||
internal object RichProtoSvc: BaseSvc() {
|
||||
internal object RichProtoSvc: QQInterfaces() {
|
||||
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
|
||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
|
||||
msgCmd = 1200,
|
||||
@ -53,8 +55,7 @@ internal object RichProtoSvc: BaseSvc() {
|
||||
supportEncrypt = 0
|
||||
)
|
||||
).toByteArray()) ?: return ""
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = buffer.decodeToOidb()
|
||||
body.bytes_bodybuffer
|
||||
.get().toByteArray()
|
||||
.decodeProtobuf<Oidb0xfc2RspBody>()
|
||||
@ -79,8 +80,7 @@ internal object RichProtoSvc: BaseSvc() {
|
||||
str_file_id.set(fileId)
|
||||
})
|
||||
}.toByteArray()) ?: return ""
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = buffer.decodeToOidb()
|
||||
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
if (body.uint32_result.get() != 0
|
||||
|| result.download_file_rsp.int32_ret_code.get() != 0) {
|
||||
@ -128,8 +128,7 @@ internal object RichProtoSvc: BaseSvc() {
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
val body = buffer.decodeToOidb()
|
||||
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
|
||||
body.bytes_bodybuffer.get().toByteArray()
|
||||
).bytes_cmd_0x346_rsp_body.get().toByteArray())
|
||||
@ -150,6 +149,75 @@ internal object RichProtoSvc: BaseSvc() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTempPicDownloadUrl(
|
||||
chatType: Int,
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
image: PicElement,
|
||||
storeId: Int = 0,
|
||||
peer: String? = null,
|
||||
subPeer: String? = null,
|
||||
): String {
|
||||
val isNtServer = originalUrl.startsWith("/download")
|
||||
if (isNtServer) {
|
||||
val tmpRKey = NtV2RichMediaSvc.getTempNtRKey()
|
||||
if (tmpRKey.isSuccess) {
|
||||
val tmpRKeyRsp = tmpRKey.getOrThrow()
|
||||
val tmpRKeyMap = hashMapOf<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(
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
|
@ -1,11 +1,11 @@
|
||||
package moe.fuqiuluo.shamrock.helper
|
||||
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.MobileQQ
|
||||
import java.io.File
|
||||
|
||||
internal object LocalCacheHelper: BaseSvc() {
|
||||
internal object LocalCacheHelper: QQInterfaces() {
|
||||
// 获取外部储存data目录
|
||||
private val dataDir = MobileQQ.getContext().getExternalFilesDir(null)!!
|
||||
.parentFile!!.resolve("Tencent")
|
||||
|
@ -9,8 +9,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.xposed.hooks.toast
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
||||
import moe.fuqiuluo.shamrock.tools.toast
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
|
||||
import mqq.app.MobileQQ
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
@ -60,10 +60,10 @@ internal object LogCenter {
|
||||
}
|
||||
// 把日志广播到主进程
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
DataRequester.request("send_message", bodyBuilder = {
|
||||
AppTalker.talk("send_message") {
|
||||
put("string", string)
|
||||
put("level", level.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!LogFile.exists()) {
|
||||
@ -89,10 +89,10 @@ internal object LogCenter {
|
||||
}
|
||||
// 把日志广播到主进程
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
DataRequester.request("send_message", bodyBuilder = {
|
||||
AppTalker.talk("send_message") {
|
||||
put("string", log)
|
||||
put("level", level.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!LogFile.exists()) {
|
||||
|
@ -1,9 +1,12 @@
|
||||
package moe.fuqiuluo.shamrock.helper
|
||||
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import com.tencent.qqnt.kernel.api.IKernelService
|
||||
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
||||
import com.tencent.qqnt.msg.api.IMsgService
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@ -16,12 +19,15 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc.prepareTempChatFromGroup
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.maker.ElemMaker
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker
|
||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||
import moe.fuqiuluo.shamrock.helper.db.MessageMapping
|
||||
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces.Companion.app
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||
import protobuf.message.RichText
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.abs
|
||||
@ -48,6 +54,28 @@ internal object MessageHelper {
|
||||
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
|
||||
}
|
||||
|
||||
suspend fun sendMessage(contact: Contact, msgs: ArrayList<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(
|
||||
chatType: Int,
|
||||
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 {
|
||||
val peerId = when (chatType) {
|
||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package moe.fuqiuluo.shamrock.remote
|
||||
|
||||
import com.tencent.mobileqq.app.QQAppInterface
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
@ -21,7 +22,9 @@ import moe.fuqiuluo.shamrock.remote.plugin.Auth
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
||||
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces.Companion.app
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.security.KeyStore
|
||||
@ -138,10 +141,13 @@ internal object HTTPServer {
|
||||
isServiceStarted = true
|
||||
currServerPort = port
|
||||
LogCenter.log("Start HTTP Server: http://0.0.0.0:$currServerPort/")
|
||||
DataRequester.request("success", values = mapOf(
|
||||
"port" to currServerPort,
|
||||
"voice" to NativeLoader.isVoiceLoaded
|
||||
))
|
||||
AppTalker.talk("success") {
|
||||
put("account", app.currentAccountUin)
|
||||
put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown")
|
||||
put("voice", NativeLoader.isVoiceLoaded)
|
||||
put("core_version", ShamrockVersion)
|
||||
put("port", currServerPort)
|
||||
}
|
||||
}
|
||||
|
||||
fun isActive(): Boolean {
|
||||
|
@ -9,7 +9,6 @@ import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||
@ -17,6 +16,7 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import moe.fuqiuluo.shamrock.utils.MD5
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.symbols.OneBotHandler
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
@ -55,7 +55,7 @@ internal object OcrImage: IActionHandler() {
|
||||
}
|
||||
|
||||
private suspend fun getOcrResult(file: File): Result<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 withTimeoutOrNull(5000) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
|
@ -2,11 +2,10 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import moe.fuqiuluo.symbols.OneBotHandler
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.message.*
|
||||
@ -51,7 +50,7 @@ internal object SendMsgByResid : IActionHandler() {
|
||||
msgRand = Random.nextUInt(),
|
||||
msgVia = 0u
|
||||
)
|
||||
BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
|
||||
QQInterfaces.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray())
|
||||
return ok("ok", echo)
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,6 @@ import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||
import moe.fuqiuluo.shamrock.tools.json
|
||||
import moe.fuqiuluo.shamrock.tools.respond
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.xposed.ipc.ShamrockIpc
|
||||
import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.IByteData
|
||||
import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSigner
|
||||
import mqq.app.MobileQQ
|
||||
@ -113,10 +112,7 @@ fun Routing.qsign() {
|
||||
return@get
|
||||
}
|
||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||
if (!initSigner()) {
|
||||
respond(false, Status.InternalHandlerError)
|
||||
return@get
|
||||
}
|
||||
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||
}
|
||||
val list = signer!!.cmdWhiteList
|
||||
call.respond(OldApiResult(0, "success", list))
|
||||
@ -128,10 +124,7 @@ fun Routing.qsign() {
|
||||
return@getOrPost
|
||||
}
|
||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||
if (!initSigner()) {
|
||||
respond(false, Status.InternalHandlerError)
|
||||
return@getOrPost
|
||||
}
|
||||
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||
}
|
||||
|
||||
val uin = fetchOrThrow("uin")
|
||||
@ -213,43 +206,7 @@ fun Routing.qsign() {
|
||||
}
|
||||
|
||||
get("/get_byte") {
|
||||
if (!isMsfServiceAlive()) {
|
||||
call.respond(OldApiResult(-2, "MSF服务未启动", null))
|
||||
return@get
|
||||
}
|
||||
if (byteData == null || byteData?.asBinder()?.isBinderAlive == false) {
|
||||
val binder = ShamrockIpc.get(ShamrockIpc.IPC_BYTEDATA)
|
||||
if (binder == null) {
|
||||
call.respond(OldApiResult(-2, "获取失败", null))
|
||||
return@get
|
||||
} else {
|
||||
byteData = IByteData.Stub.asInterface(binder)
|
||||
binder.linkToDeath({
|
||||
byteData = null
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
val data = fetchGetOrThrow("data")
|
||||
if(!(data.startsWith("810_") || data.startsWith("812_"))) {
|
||||
call.respond(OldApiResult(-2, "data参数不合法", null))
|
||||
return@get
|
||||
}
|
||||
|
||||
val uin = fetchOrThrow("uin")
|
||||
val salt = fetchSalt(data, uin)
|
||||
if (salt.isEmpty()) {
|
||||
call.respond(OldApiResult(-2, "无法自动决断mode,请主动提供", null))
|
||||
return@get
|
||||
}
|
||||
|
||||
val sign = byteData!!.sign(uin, data, salt).sign
|
||||
|
||||
if (sign == null) {
|
||||
call.respond(OldApiResult(-2, "获取失败", null))
|
||||
} else {
|
||||
call.respond(OldApiResult(0, "success", sign.toHexString()))
|
||||
}
|
||||
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||
}
|
||||
|
||||
get("/friend_sign") {
|
||||
@ -258,10 +215,7 @@ fun Routing.qsign() {
|
||||
return@get
|
||||
}
|
||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||
if (!initSigner()) {
|
||||
respond(false, Status.InternalHandlerError)
|
||||
return@get
|
||||
}
|
||||
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||
}
|
||||
|
||||
val addUin = fetchOrThrow("add_uin")
|
||||
@ -286,10 +240,7 @@ fun Routing.qsign() {
|
||||
return@get
|
||||
}
|
||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||
if (!initSigner()) {
|
||||
respond(false, Status.InternalHandlerError)
|
||||
return@get
|
||||
}
|
||||
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||
}
|
||||
|
||||
val addUin = fetchOrThrow("group_uin")
|
||||
@ -387,23 +338,6 @@ private data class Sign(
|
||||
val requestCallback: List<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(
|
||||
cmd: String,
|
||||
uin: String,
|
||||
@ -415,10 +349,7 @@ private suspend fun PipelineContext<Unit, ApplicationCall>.requestSign(
|
||||
return
|
||||
}
|
||||
if (signer == null || signer?.asBinder()?.isBinderAlive == false) {
|
||||
if (!initSigner()) {
|
||||
respond(false, Status.InternalHandlerError)
|
||||
return
|
||||
}
|
||||
call.respond(OldApiResult(-2, "不支持的操作", null))
|
||||
}
|
||||
|
||||
val sign = withTimeoutOrNull(5000) {
|
||||
|
@ -2,13 +2,13 @@ package moe.fuqiuluo.shamrock.remote.api
|
||||
|
||||
import com.tencent.mobileqq.dt.model.FEBound
|
||||
import io.ktor.server.routing.Routing
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.shamrock.remote.structures.Protocol
|
||||
import moe.fuqiuluo.shamrock.remote.structures.QSignDtConfig
|
||||
import moe.fuqiuluo.shamrock.remote.structures.Status
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import mqq.app.MobileQQ
|
||||
import oicq.wlogin_sdk.tlv_type.tlv_t100
|
||||
import oicq.wlogin_sdk.tlv_type.tlv_t106
|
||||
@ -20,8 +20,8 @@ fun Routing.obtainProtocolData() {
|
||||
val cmd = fetchOrThrow("cmd")
|
||||
val isPb = fetchOrThrow("proto").toBooleanStrict()
|
||||
val buffer = fetchOrThrow("buffer").hex2ByteArray()
|
||||
val resp = BaseSvc.sendBufferAW(cmd, isPb, buffer)
|
||||
respond(true, Status.Ok, data = resp?.toHexString() ?: "null", msg = "成功")
|
||||
val resp = QQInterfaces.sendBufferAW(cmd, isPb, buffer)
|
||||
respond(true, Status.Ok, data = resp?.wupBuffer?.toHexString() ?: "null", msg = "成功")
|
||||
}
|
||||
|
||||
getOrPost("/set_guid") {
|
||||
|
@ -10,9 +10,7 @@ import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.remote.action.handlers.SendMsgByResid
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
||||
import moe.fuqiuluo.shamrock.tools.getOrPost
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
|
@ -6,7 +6,6 @@ import io.ktor.server.application.createApplicationPlugin
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.Charset
|
||||
|
||||
private suspend fun ApplicationCall.checkToken() {
|
||||
val token = ShamrockConfig.getToken()
|
||||
|
@ -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.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments
|
||||
@ -35,9 +34,10 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.Sender
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.push.SignDetail
|
||||
import moe.fuqiuluo.shamrock.tools.ShamrockDsl
|
||||
import moe.fuqiuluo.shamrock.tools.json
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.QQInterfaces
|
||||
import java.util.ArrayList
|
||||
|
||||
internal object GlobalEventTransmitter: BaseSvc() {
|
||||
internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
private val messageEventFlow by lazy {
|
||||
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
||||
}
|
||||
|
@ -1,284 +1,13 @@
|
||||
package moe.fuqiuluo.shamrock.remote.service.config
|
||||
|
||||
import android.content.Intent
|
||||
import com.tencent.mmkv.MMKV
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.GlobalJson5
|
||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader.moduleLoader
|
||||
import mqq.app.MobileQQ
|
||||
import java.io.File
|
||||
import java.util.Properties
|
||||
|
||||
internal object ShamrockConfig {
|
||||
private val ConfigDir = MobileQQ.getContext().getExternalFilesDir(null)!!
|
||||
.parentFile!!.resolve("Tencent/Shamrock").also {
|
||||
if (it.exists()) it.delete()
|
||||
it.mkdirs()
|
||||
}
|
||||
private val config = kotlin.runCatching {
|
||||
GlobalJson5.decodeFromString<ServiceConfig>(ConfigDir.resolve("config.json").also {
|
||||
if (!it.exists()) it.writeText("{}")
|
||||
}.readText())
|
||||
}.onFailure {
|
||||
LogCenter.log("您的配置文件出现错误: ${it.stackTraceToString()}", Level.ERROR)
|
||||
}.getOrElse {
|
||||
ServiceConfig()
|
||||
}
|
||||
internal object ShamrockConfig: ShamrockConfigV0() {
|
||||
|
||||
fun isInit(): Boolean {
|
||||
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
|
||||
return mmkv.getBoolean("isInit", false)
|
||||
}
|
||||
|
||||
private fun updateConfig(config: ServiceConfig = this.config) {
|
||||
ConfigDir.resolve("config.json").writeText(GlobalJson5.encodeToString(config))
|
||||
}
|
||||
|
||||
fun updateConfig(intent: Intent) {
|
||||
mmkv.apply {
|
||||
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
|
||||
putBoolean("tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式
|
||||
putInt("port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
|
||||
putBoolean("ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
|
||||
putBoolean("http", intent.getBooleanExtra("http", false)) // HTTP回调开关
|
||||
putString("http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址
|
||||
putBoolean("ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关
|
||||
putBoolean("use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码
|
||||
putBoolean("inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包
|
||||
putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式
|
||||
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
|
||||
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
|
||||
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
|
||||
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
|
||||
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
|
||||
putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试
|
||||
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
|
||||
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
|
||||
putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息
|
||||
putBoolean("forbid_useless_process", intent.getBooleanExtra("forbid_useless_process", false)) // 禁用QQ生成无用进程
|
||||
putBoolean("enable_old_bdh", intent.getBooleanExtra("enable_old_bdh", false)) // 启用旧版BDH
|
||||
intent.getStringExtra("up_res_group")?.let { putString("up_res_group", it) }
|
||||
} else {
|
||||
XposedBridge.log("[Shamrock] 已禁用自动同步配置")
|
||||
}
|
||||
config.defaultToken = intent.getStringExtra("token")
|
||||
config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true)
|
||||
val wsPort = intent.getIntExtra("ws_port", 5800)
|
||||
config.activeWebSocket = if (config.activeWebSocket == null) ConnectionConfig(
|
||||
address = "0.0.0.0",
|
||||
port = wsPort,
|
||||
) else config.activeWebSocket?.also {
|
||||
it.port = wsPort
|
||||
}
|
||||
config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", ",")?.filter { address ->
|
||||
address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://"))
|
||||
}?.map {
|
||||
ConnectionConfig(address = it)
|
||||
}?.toMutableList()
|
||||
putBoolean("isInit", true)
|
||||
}
|
||||
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
|
||||
updateConfig()
|
||||
}
|
||||
}
|
||||
|
||||
fun putDefaultSettings() {
|
||||
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
|
||||
if ((!isInit()) && (!mmkv.getBoolean("isEmergencyInitBefore", false))){
|
||||
mmkv.apply {
|
||||
putBoolean("tablet", false) // 强制平板模式
|
||||
putInt("port", 5700) // 主动HTTP端口
|
||||
putBoolean("ws", false) // 主动WS开关
|
||||
putBoolean("http", false) // HTTP回调开关
|
||||
putString("http_addr", null) // WebHook回调地址
|
||||
putBoolean("ws_client", false) // 被动WS开关
|
||||
putBoolean("use_cqcode", false) // 使用CQ码
|
||||
putBoolean("inject_packet", false) // 拦截无用包
|
||||
putBoolean("debug", false) // 调试模式
|
||||
putString("key_store", null) // 证书路径
|
||||
putString("ssl_pwd", null) // 证书密码
|
||||
putString("ssl_private_pwd", null) // 证书私钥密码
|
||||
putString("ssl_alias", null) // 证书别名
|
||||
putInt("ssl_port", 5701) // 主动HTTP端口
|
||||
putBoolean("alive_reply", true) // 自回复测试
|
||||
putBoolean("enable_self_msg", false) // 推送自己发的消息
|
||||
putBoolean("shell", false) // 开启Shell接口
|
||||
putBoolean("enable_sync_msg_as_sent_msg", true) // 推送同步消息
|
||||
putBoolean("forbid_useless_process", false) // 禁用QQ生成无用进程
|
||||
putBoolean("enable_old_bdh", false) // 启用旧版BDH
|
||||
putBoolean("antiTrace", false)
|
||||
putBoolean("super_anti", true)
|
||||
putString("up_res_group", "")
|
||||
|
||||
config.defaultToken = null
|
||||
// config.antiTrace = true
|
||||
val wsPort = 5800
|
||||
config.activeWebSocket =
|
||||
if (config.activeWebSocket == null) ConnectionConfig(
|
||||
address = "0.0.0.0",
|
||||
port = wsPort,
|
||||
) else config.activeWebSocket?.also {
|
||||
it.port = wsPort
|
||||
}
|
||||
config.passiveWebSocket = null
|
||||
putBoolean("isInit", true)
|
||||
putBoolean("isEmergencyInitBefore", true)
|
||||
}
|
||||
XposedBridge.log("[Shamrock] 强制初始化配置完成,请重新打开QQ")
|
||||
} else {
|
||||
XposedBridge.log("[Shamrock] 程序逻辑错误或错误地多次强制初始化")
|
||||
mmkv.putBoolean("isEmergencyInitBefore", false)
|
||||
XposedBridge.log("[Shamrock] 如果执意要再次强制初始化,请重新执行程序")
|
||||
}
|
||||
}
|
||||
|
||||
private val mmkv: MMKV
|
||||
get() = MMKVFetcher.mmkvWithId("shamrock_config")
|
||||
|
||||
fun getUpResGroup(): String {
|
||||
return mmkv.getString("up_res_group", "") ?: ""
|
||||
}
|
||||
|
||||
fun aliveReply(): Boolean {
|
||||
return mmkv.getBoolean("alive_reply", false)
|
||||
}
|
||||
|
||||
fun allowTempSession(): Boolean {
|
||||
return config.allowTempSession
|
||||
}
|
||||
|
||||
fun getGroupMsgRule(): GroupRule? {
|
||||
return config.rules?.groupRule
|
||||
}
|
||||
|
||||
fun getPrivateRule(): PrivateRule? {
|
||||
return config.rules?.privateRule
|
||||
}
|
||||
|
||||
fun enableSyncMsgAsSentMsg(): Boolean {
|
||||
return mmkv.getBoolean("enable_sync_msg_as_sent_msg", false)
|
||||
}
|
||||
|
||||
fun enableSelfMsg(): Boolean {
|
||||
return mmkv.getBoolean("enable_self_msg", false)
|
||||
}
|
||||
|
||||
fun forbidUselessProcess(): Boolean {
|
||||
return mmkv.getBoolean("forbid_useless_process", false)
|
||||
}
|
||||
|
||||
fun openWebSocketClient(): Boolean {
|
||||
return mmkv.getBoolean("ws_client", false)
|
||||
}
|
||||
|
||||
fun getWebSocketClientAddress(): List<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.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler
|
||||
@ -21,8 +22,9 @@ import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
internal object AioListener : IKernelMsgListener {
|
||||
@ -63,7 +65,9 @@ internal object AioListener : IKernelMsgListener {
|
||||
|
||||
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
|
||||
MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "pong", { _, _ -> })
|
||||
}
|
||||
} /*else if (ShamrockConfig.aliveReply() && rawMsg == ".shamrock.at_me") {
|
||||
MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "[CQ:at,qq=${record.senderUin}]", { _, _ -> })
|
||||
}*/
|
||||
|
||||
|
||||
val postType = if (record.senderUin == TicketSvc.getLongUin() && ShamrockConfig.enableSyncMsgAsSentMsg()) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package moe.fuqiuluo.shamrock.remote.service.listener
|
||||
|
||||
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@ -24,7 +25,6 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.asString
|
||||
import moe.fuqiuluo.shamrock.tools.readBuf32Long
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.PacketHandler
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.message.ContentHead
|
||||
import protobuf.message.MsgBody
|
||||
@ -32,18 +32,18 @@ import protobuf.message.ResponseHead
|
||||
import protobuf.push.*
|
||||
|
||||
internal object PrimitiveListener {
|
||||
fun registerListener() {
|
||||
PacketHandler.register("trpc.msg.olpush.OlPushService.MsgPush") { _, buffer ->
|
||||
GlobalScope.launch {
|
||||
fun onPush(fromServiceMsg: FromServiceMsg) {
|
||||
if (fromServiceMsg.wupBuffer == null) return
|
||||
try {
|
||||
val push = buffer.slice(4).decodeProtobuf<MessagePush>()
|
||||
val push = fromServiceMsg.wupBuffer.slice(4)
|
||||
.decodeProtobuf<MessagePush>()
|
||||
GlobalScope.launch {
|
||||
onMsgPush(push)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onMsgPush(push: MessagePush) {
|
||||
if (
|
||||
@ -188,6 +188,8 @@ internal object PrimitiveListener {
|
||||
}
|
||||
|
||||
private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) {
|
||||
if (body.msgContent == null) return
|
||||
|
||||
val event = runCatching {
|
||||
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||
}.getOrElse {
|
||||
@ -200,6 +202,7 @@ internal object PrimitiveListener {
|
||||
}.decodeProtobuf<GroupCommonTipsEvent>()
|
||||
}
|
||||
val groupId = event.groupCode.toLong()
|
||||
if (event.uniqueTitleChangeDetail == null) return
|
||||
val detail = event.uniqueTitleChangeDetail!!.first()
|
||||
|
||||
//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
|
||||
})
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import kotlin.random.Random
|
||||
|
||||
internal object PlatformUtils {
|
||||
const val QQ_9_0_8_VER = 5540
|
||||
const val QQ_9_0_65_VER = 6566
|
||||
|
||||
fun getQUA(): String {
|
||||
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
|
||||
|
@ -1,18 +1,19 @@
|
||||
package moe.fuqiuluo.shamrock.xposed
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Process
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||
import de.robv.android.xposed.XposedBridge.log
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.KeepAlive
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
|
||||
import moe.fuqiuluo.shamrock.tools.FuzzySearchClass
|
||||
import moe.fuqiuluo.shamrock.tools.GlobalUi
|
||||
import moe.fuqiuluo.shamrock.tools.afterHook
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.hooks.runFirstActions
|
||||
@ -156,6 +157,13 @@ internal class XposedEntry: IXposedHookLoadPackage {
|
||||
|
||||
log("Process Name = $processName")
|
||||
|
||||
|
||||
GlobalUi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
Handler.createAsync(ctx.mainLooper)
|
||||
} else {
|
||||
Handler(ctx.mainLooper)
|
||||
}
|
||||
|
||||
runFirstActions(ctx)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import mqq.app.MobileQQ
|
||||
import kotlin.random.Random
|
||||
|
||||
internal object AppTalker {
|
||||
val uriName = "content://moe.fuqiuluo.108.provider" // 你是真的闲,这都上个检测
|
||||
val URI = Uri.parse(uriName)
|
||||
private const val uriName = "content://moe.fuqiuluo.108.provider" // 你是真的闲,这都上个检测
|
||||
private val URI = Uri.parse(uriName)
|
||||
|
||||
fun talk(values: ContentValues, onFailure: ((Throwable) -> Unit)? = null) {
|
||||
val ctx = MobileQQ.getContext()
|
||||
@ -17,4 +17,20 @@ internal object AppTalker {
|
||||
onFailure?.invoke(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun talk(action: String, bodyBuilder: ContentValues.() -> Unit) {
|
||||
val values = ContentValues()
|
||||
values.put("__cmd", action)
|
||||
values.put("__hash", 0)
|
||||
bodyBuilder.invoke(values)
|
||||
talk(values)
|
||||
}
|
||||
|
||||
fun talk(action: String, onFailure: ((Throwable) -> Unit)? = null, bodyBuilder: ContentValues.() -> Unit = {}) {
|
||||
val values = ContentValues()
|
||||
values.put("__cmd", action)
|
||||
values.put("__hash", 0)
|
||||
bodyBuilder.invoke(values)
|
||||
talk(values, onFailure)
|
||||
}
|
||||
}
|
@ -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 moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.remote.service.PacketReceiver
|
||||
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
|
||||
import moe.fuqiuluo.shamrock.remote.service.listener.PrimitiveListener
|
||||
import moe.fuqiuluo.shamrock.tools.hookMethod
|
||||
@ -28,9 +27,6 @@ internal object NTServiceFetcher {
|
||||
val curHash = service.hashCode() + msgService.hashCode()
|
||||
if (isInitForNt(curHash)) return
|
||||
|
||||
PacketHandler.initPacketHandler()
|
||||
PacketReceiver.init()
|
||||
|
||||
LogCenter.log("Fetch kernel service successfully: $curKernelHash,$curHash,${PlatformUtils.isMainProcess()}")
|
||||
curKernelHash = curHash
|
||||
this.iKernelService = service
|
||||
@ -38,7 +34,6 @@ internal object NTServiceFetcher {
|
||||
|
||||
initNTKernelListener(msgService)
|
||||
antiBackgroundMode(sessionService)
|
||||
//hookGuildListener(sessionService)
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +65,7 @@ internal object NTServiceFetcher {
|
||||
//groupService.addKernelGroupListener(GroupEventListener)
|
||||
//LogCenter.log("Register Group listener successfully.")
|
||||
|
||||
PrimitiveListener.registerListener()
|
||||
//PrimitiveListener.registerListener()
|
||||
} catch (e: Throwable) {
|
||||
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
|
@ -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)
|
||||
internal class FixLibraryLoad: IAction {
|
||||
val redirectedLibrary =arrayOf(
|
||||
private val redirectedLibrary =arrayOf(
|
||||
"ffmpegkit_abidetect",
|
||||
"avutil",
|
||||
"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.XposedHook
|
||||
import mqq.app.MobileQQ
|
||||
import kotlin.concurrent.timer
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@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 kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.shamrock.remote.HTTPServer
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.tools.toast
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
|
||||
import moe.fuqiuluo.symbols.Process
|
||||
import moe.fuqiuluo.symbols.XposedHook
|
||||
import mqq.app.MobileQQ
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@XposedHook(Process.MAIN, priority = 1)
|
||||
class PullConfig: IAction {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
var isConfigOk = false
|
||||
external fun testNativeLibrary(): String
|
||||
}
|
||||
|
||||
private external fun testNativeLibrary(): String
|
||||
|
||||
override fun invoke(ctx: Context) {
|
||||
if (!PlatformUtils.isMainProcess()) return
|
||||
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
DynamicReceiver.register("fetchPort", IPCRequest {
|
||||
DataRequester.request("success", values = mapOf(
|
||||
"port" to HTTPServer.currServerPort,
|
||||
"voice" to NativeLoader.isVoiceLoaded
|
||||
))
|
||||
})
|
||||
DynamicReceiver.register("checkAndStartService", IPCRequest {
|
||||
if (HTTPServer.isServiceStarted) {
|
||||
HTTPServer.isServiceStarted = false
|
||||
}
|
||||
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)
|
||||
val isInit = ShamrockConfig.isInit()
|
||||
AppTalker.talk("init", onFailure = {
|
||||
if (isInit) {
|
||||
ctx.toast("Shamrock主进程未启动,将不会同步配置!")
|
||||
} else {
|
||||
ctx.toast("Shamrock主进程未启动,初始化失败!")
|
||||
GlobalScope.launch {
|
||||
delay(3.seconds)
|
||||
exitProcess(1)
|
||||
}
|
||||
} else {
|
||||
ctx.toast("Shamrock进程未启动,不会推送配置文件。")
|
||||
initAppService(ctx)
|
||||
}
|
||||
}, bodyBuilder = null) {
|
||||
isConfigOk = true
|
||||
ShamrockConfig.updateConfig(it)
|
||||
initAppService(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAppService(ctx: Context) {
|
||||
NativeLoader.load("shamrock")
|
||||
ctx.toast(testNativeLibrary())
|
||||
runServiceActions(ctx)
|
||||
})
|
||||
ctx.toast("同步配置中...")
|
||||
}
|
||||
}
|
@ -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) {
|
||||
try {
|
||||
if (name == "shamrock"
|
||||
|| (name == "clover" && isEmu)
|
||||
) {
|
||||
if (name == "shamrock" || (name == "clover" && isEmu)) {
|
||||
onLoadByResource(name)
|
||||
} else if (!onLoadByAbsolutePath(name)) {
|
||||
onLoadByExternalFile(name)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (name == "shamrock" && onLoadByAbsolutePath(name)) {
|
||||
return
|
||||
}
|
||||
LogCenter.log("LoadLibrary(name = $name) failed: ${e.stackTraceToString()}", Level.ERROR)
|
||||
XposedBridge.log(e)
|
||||
}
|
||||
}
|
||||
@ -95,7 +97,7 @@ internal object NativeLoader {
|
||||
if (soDir.isFile) soDir.delete()
|
||||
if (!soDir.exists()) soDir.mkdirs()
|
||||
val soPath = getLibFilePath(name)
|
||||
val soFile = File(soDir, name)
|
||||
val soFile = File(soDir, "lib$name.so")
|
||||
fun reloadSo(tmp: File? = null) {
|
||||
LogCenter.log("SO文件大小不一致或不存在,正在重新加载", Level.INFO)
|
||||
soFile.delete()
|
||||
@ -107,7 +109,7 @@ internal object NativeLoader {
|
||||
if (!soFile.exists()) {
|
||||
reloadSo()
|
||||
} else {
|
||||
val tmpSoFile = File(soDir, "$name.tmp").also { file ->
|
||||
val tmpSoFile = File(soDir, "lib$name.so.tmp").also { file ->
|
||||
if (file.exists()) file.delete()
|
||||
file.outputStream().use {
|
||||
moduleLoader.getResourceAsStream(soPath).use { origin ->
|
||||
|
Loading…
x
Reference in New Issue
Block a user