This commit is contained in:
fuqiuluo 2024-07-16 20:56:09 +08:00
parent 65ddca2ea4
commit e2f27cb36a
73 changed files with 1433 additions and 1523 deletions

View File

@ -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 {

View File

@ -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库成功~");
}

View File

@ -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 {

View File

@ -30,6 +30,7 @@ abstract class ModuleHandler {
}
}
putExtra("__hash", callbackId)
putExtra("__cmd", cmd)
}
}
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

@ -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);
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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(

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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!!
}

View File

@ -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,15 +627,49 @@ internal object GroupSvc: BaseSvc() {
}
}
fun getTroopMemberInfoByUinFromNt(
groupId: Long,
uin: Long
): Result<TroopMemberInfo> {
return kotlin.runCatching {
val api = QRoute.api(ITroopMemberListRepoApi::class.java)
api.getTroopMemberInfoSync(groupId.toString(), uin.toString(), null, groupId.toString())
?: throw Exception("获取群成员信息失败: NT兼容接口已废弃")
}
}
suspend fun getTroopMemberInfoByUinV3(
groupId: Long,
uin: Long
): TroopMemberNickInfo? {
if (PlatformUtils.getQQVersionCode() > QQ_9_0_65_VER) {
val api = QRoute.api(ITroopMemberListRepoApi::class.java)
return withTimeoutOrNull(5.seconds) {
suspendCancellableCoroutine<TroopMemberNickInfo> { continuation ->
api.fetchTroopMemberName(groupId.toString(), uin.toString(), null, groupId.toString()) {
continuation.resume(it)
}
}
}
} else {
return null
}
}
suspend fun getTroopMemberInfoByUinV2(
groupId: Long,
uin: Long,
refresh: Boolean = false
): Result<TroopMemberInfo> {
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
var info = service.getTroopMember(groupId.toString(), uin.toString())
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) {
info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull()
var info: TroopMemberInfo? = null
if (PlatformUtils.getQQVersionCode() <= QQ_9_0_65_VER) {
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
info = service.getTroopMember(groupId.toString(), uin.toString())
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) {
info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull()
}
} else {
info = getTroopMemberInfoByUinFromNt(groupId, uin).getOrNull()
}
if (info == null) {
info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let {
@ -645,35 +679,36 @@ internal object GroupSvc: BaseSvc() {
}
}
}
try {
if (info != null && (info.alias == null || info.alias.isBlank())) {
val req = group_member_info.ReqBody()
req.uint64_group_code.set(groupId)
req.uint64_uin.set(uin)
req.bool_new_client.set(true)
req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1)
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000)
if (respBuffer != null) {
val rsp = group_member_info.RspBody()
rsp.mergeFrom(respBuffer.slice(4))
if (rsp.msg_meminfo.str_location.has()) {
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
}
if (rsp.msg_meminfo.uint32_age.has()) {
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
}
if (rsp.msg_meminfo.bytes_group_honor.has()) {
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
val honor = troop_honor.GroupUserCardHonor()
honor.mergeFrom(honorBytes)
info.level = honor.level.get()
// 10315: medal_id not real group level
if (PlatformUtils.getQQVersionCode() <= QQ_9_0_8_VER) {
try {
if (info != null && (info.alias == null || info.alias.isBlank())) {
val req = group_member_info.ReqBody()
req.uint64_group_code.set(groupId)
req.uint64_uin.set(uin)
req.bool_new_client.set(true)
req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1)
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2.seconds)
if (respBuffer != null) {
val rsp = respBuffer.decodeToObject(group_member_info.RspBody())
if (rsp.msg_meminfo.str_location.has()) {
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
}
if (rsp.msg_meminfo.uint32_age.has()) {
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
}
if (rsp.msg_meminfo.bytes_group_honor.has()) {
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
val honor = troop_honor.GroupUserCardHonor()
honor.mergeFrom(honorBytes)
info.level = honor.level.get()
// 10315: medal_id not real group level
}
}
}
} catch (err: Throwable) {
LogCenter.log(err.stackTraceToString(), Level.WARN)
}
} catch (err: Throwable) {
LogCenter.log(err.stackTraceToString(), Level.WARN)
}
return if (info != null) {
Result.success(info)
@ -682,7 +717,7 @@ internal object GroupSvc: BaseSvc() {
}
}
suspend fun getTroopMemberInfoByUinViaNt(
private suspend fun getTroopMemberInfoByUinViaNt(
groupId: Long,
qq: Long,
timeout: Long = 5000L
@ -711,7 +746,7 @@ internal object GroupSvc: BaseSvc() {
return if (info != null) {
Result.success(info)
} else {
Result.failure(Exception("获取群成员信息失败"))
Result.failure(Exception("[NT]获取群成员信息失败"))
}
}
}
@ -933,7 +968,7 @@ internal object GroupSvc: BaseSvc() {
}
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray())
?: return Result.failure(Exception("操作失败"))
val rsp = structmsg.RspSystemMsgAction().mergeFrom(respBuffer.slice(4))
val rsp = respBuffer.decodeToObject(structmsg.RspSystemMsgAction())
return if (rsp.head.result.has()) {
if (rsp.head.result.get() == 0) {
Result.success(rsp.msg_detail.get())
@ -984,8 +1019,7 @@ internal object GroupSvc: BaseSvc() {
ArrayList()
} else {
try {
val msg = structmsg.RspSystemMsgNew()
msg.mergeFrom(respBuffer.slice(4))
val msg = respBuffer.decodeToObject(structmsg.RspSystemMsgNew())
return msg.groupmsgs.get().orEmpty()
} catch (err: Throwable) {
requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1)
@ -1178,8 +1212,7 @@ internal object GroupSvc: BaseSvc() {
return if (buffer == null) {
Result.failure(Exception("操作失败"))
} else {
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
val body = buffer.decodeToOidb()
val rsp = oidb_0xeb7.RspBody()
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
val doneInfo = rsp.signInWriteRsp.doneInfo

View File

@ -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())
}

View File

@ -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"))
)

View File

@ -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卡片消息
*/

View File

@ -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 ""
}

View File

@ -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
}

View File

@ -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? {

View File

@ -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

View File

@ -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,

View File

@ -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!!)

View File

@ -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"
)

View File

@ -124,18 +124,23 @@ internal class ElemMaker {
else -> {
qq = qqStr.toLong()
type = 0
"@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(
peerId.toLong(),
qq,
true
).let {
val info = it.getOrNull()
if (info == null)
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
else info.troopnick
.ifNullOrEmpty(info.friendnick)
.ifNullOrEmpty(qqStr)
})
val name = (data["name"].asStringOrNull
?: GroupSvc.getTroopMemberInfoByUinV3(peerId.toLong(), qq).let {
it?.troopNick
.ifNullOrEmpty(it?.friendNick)
.ifNullOrEmpty(it?.showName)
.ifNullOrEmpty(it?.autoRemark)
.ifNullOrEmpty(it?.colorNick)
}
?: GroupSvc.getTroopMemberInfoByUinV2(peerId.toLong(), qq, true).let {
val info = it.getOrNull()
if (info == null)
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
else info.troopnick
.ifNullOrEmpty(info.friendnick)
.ifNullOrEmpty(qqStr)
})
"@$name"
}
}

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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")

View File

@ -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()) {

View File

@ -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 -> {

View File

@ -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 {

View File

@ -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 ->

View File

@ -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)
}

View File

@ -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) {

View File

@ -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") {

View File

@ -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

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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>>()
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()) {

View File

@ -1,5 +1,6 @@
package moe.fuqiuluo.shamrock.remote.service.listener
import com.tencent.qphone.base.remote.FromServiceMsg
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -24,7 +25,6 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.readBuf32Long
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.xposed.helper.PacketHandler
import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.message.ContentHead
import protobuf.message.MsgBody
@ -32,16 +32,16 @@ import protobuf.message.ResponseHead
import protobuf.push.*
internal object PrimitiveListener {
fun registerListener() {
PacketHandler.register("trpc.msg.olpush.OlPushService.MsgPush") { _, buffer ->
fun onPush(fromServiceMsg: FromServiceMsg) {
if (fromServiceMsg.wupBuffer == null) return
try {
val push = fromServiceMsg.wupBuffer.slice(4)
.decodeProtobuf<MessagePush>()
GlobalScope.launch {
try {
val push = buffer.slice(4).decodeProtobuf<MessagePush>()
onMsgPush(push)
} catch (e: Exception) {
LogCenter.log(e.stackTraceToString(), Level.WARN)
}
onMsgPush(push)
}
} catch (e: Exception) {
LogCenter.log(e.stackTraceToString(), Level.WARN)
}
}
@ -188,6 +188,8 @@ internal object PrimitiveListener {
}
private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) {
if (body.msgContent == null) return
val event = runCatching {
body.msgContent!!.decodeProtobuf<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) {

View 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() }
}

View 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
})
}
}

View File

@ -16,7 +16,8 @@ import mqq.app.MobileQQ
import kotlin.random.Random
internal object PlatformUtils {
const val QQ_9_0_8_VER = 5540
const val QQ_9_0_8_VER = 5540
const val QQ_9_0_65_VER = 6566
fun getQUA(): String {
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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 }
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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",

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)
}
})
}
}

View File

@ -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)
}
}
}

View File

@ -4,92 +4,39 @@ package moe.fuqiuluo.shamrock.xposed.hooks
import android.content.Context
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import moe.fuqiuluo.shamrock.remote.HTTPServer
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
import moe.fuqiuluo.shamrock.tools.toast
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver
import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
import moe.fuqiuluo.symbols.Process
import moe.fuqiuluo.symbols.XposedHook
import mqq.app.MobileQQ
import kotlin.concurrent.thread
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds
@XposedHook(Process.MAIN, priority = 1)
class PullConfig: IAction {
companion object {
@JvmStatic
var isConfigOk = false
external fun testNativeLibrary(): String
}
private external fun testNativeLibrary(): String
override fun invoke(ctx: Context) {
if (!PlatformUtils.isMainProcess()) return
GlobalScope.launch(Dispatchers.Default) {
DynamicReceiver.register("fetchPort", IPCRequest {
DataRequester.request("success", values = mapOf(
"port" to HTTPServer.currServerPort,
"voice" to NativeLoader.isVoiceLoaded
))
})
DynamicReceiver.register("checkAndStartService", IPCRequest {
if (HTTPServer.isServiceStarted) {
HTTPServer.isServiceStarted = false
val isInit = ShamrockConfig.isInit()
AppTalker.talk("init", onFailure = {
if (isInit) {
ctx.toast("Shamrock主进程未启动将不会同步配置")
} else {
ctx.toast("Shamrock主进程未启动初始化失败")
GlobalScope.launch {
delay(3.seconds)
exitProcess(1)
}
initAppService(MobileQQ.getContext())
})
DynamicReceiver.register("push_config", IPCRequest {
ctx.toast("动态推送配置文件成功。")
ShamrockConfig.updateConfig(it)
})
DynamicReceiver.register("change_port", IPCRequest {
when (it.getStringExtra("type")) {
"port" -> {
ctx.toast("动态修改HTTP端口成功。")
HTTPServer.changePort(it.getIntExtra("port", 5700))
}
"ws_port" -> {
ctx.toast("动态修改WS端口不支持。")
}
"restart" -> {
if(HTTPServer.isServiceStarted) {
ctx.toast("重启HTTPServer完成。")
HTTPServer.restart()
}
}
}
})
DataRequester.request("init", onFailure = {
if (!ShamrockConfig.isInit()) {
ctx.toast("请启动Shamrock主进程以初始化服务进程将退出。")
ShamrockConfig.putDefaultSettings()
thread {
Thread.sleep(3000)
exitProcess(1)
}
} else {
ctx.toast("Shamrock进程未启动不会推送配置文件。")
initAppService(ctx)
}
}, bodyBuilder = null) {
isConfigOk = true
ShamrockConfig.updateConfig(it)
initAppService(ctx)
}
}
}
private fun initAppService(ctx: Context) {
NativeLoader.load("shamrock")
ctx.toast(testNativeLibrary())
runServiceActions(ctx)
})
ctx.toast("同步配置中...")
}
}

View File

@ -0,0 +1,7 @@
package moe.fuqiuluo.shamrock.xposed.hooks.interacts
import android.content.Intent
interface IInteract {
operator fun invoke(intent: Intent)
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}
}
}
}

View File

@ -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 ->