diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/perf/block/BinderMethodProxy.java b/qqinterface/src/main/java/com/tencent/mobileqq/perf/block/BinderMethodProxy.java new file mode 100644 index 00000000..68840aa7 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/perf/block/BinderMethodProxy.java @@ -0,0 +1,29 @@ +package com.tencent.mobileqq.perf.block; + +import android.os.Bundle; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import epic.EIPCClient; +import epic.EIPCResult; +import kotlin.Metadata; +import kotlin.jvm.JvmStatic; + +public final class BinderMethodProxy { + @NotNull + public static final BinderMethodProxy INSTANCE; + + static { + INSTANCE = new BinderMethodProxy(); + } + + @JvmStatic + public static EIPCResult callServer(@NotNull EIPCClient client, @Nullable String module, @Nullable String action, @Nullable Bundle bundle) { + //MainBlockMethodMonitor.onMethodStart(); + //EIPCResult callServer = client.callServer(str, str2, bundle); + //MainBlockMethodMonitor.onMethodEnd(); + //return callServer; + return null; + } +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/qipc/QIPCClientHelper.java b/qqinterface/src/main/java/com/tencent/mobileqq/qipc/QIPCClientHelper.java new file mode 100644 index 00000000..e49ea2d1 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/qipc/QIPCClientHelper.java @@ -0,0 +1,13 @@ +package com.tencent.mobileqq.qipc; + +import epic.EIPCClient; + +public class QIPCClientHelper { + public static synchronized QIPCClientHelper getInstance() { + return null; + } + + public EIPCClient getClient() { + return null; + } +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/qmmkv/MMKVOptionEntity.java b/qqinterface/src/main/java/com/tencent/mobileqq/qmmkv/MMKVOptionEntity.java new file mode 100644 index 00000000..da039c34 --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/qmmkv/MMKVOptionEntity.java @@ -0,0 +1,7 @@ +package com.tencent.mobileqq.qmmkv; + +public class MMKVOptionEntity { + public String decodeString(String str, String str2) { + return ""; + } +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/qmmkv/QMMKV.java b/qqinterface/src/main/java/com/tencent/mobileqq/qmmkv/QMMKV.java new file mode 100644 index 00000000..be85a6eb --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/qmmkv/QMMKV.java @@ -0,0 +1,9 @@ +package com.tencent.mobileqq.qmmkv; + +import android.content.Context; + +public class QMMKV { + public static MMKVOptionEntity from(Context context, String str) { + return null; + } +} diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/transfile/dns/IpData.java b/qqinterface/src/main/java/com/tencent/mobileqq/transfile/dns/IpData.java new file mode 100644 index 00000000..9c521eea --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/transfile/dns/IpData.java @@ -0,0 +1,40 @@ +package com.tencent.mobileqq.transfile.dns; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +public class IpData implements Parcelable { + public int mFailedCount; + public String mIp; + public int mPort; + public int mType; + + /** + * Describe the kinds of special objects contained in this Parcelable + * instance's marshaled representation. For example, if the object will + * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)}, + * the return value of this method must include the + * {@link #CONTENTS_FILE_DESCRIPTOR} bit. + * + * @return a bitmask indicating the set of special object types marshaled + * by this Parcelable object instance. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + + } +} diff --git a/qqinterface/src/main/java/epic/EIPCClient.java b/qqinterface/src/main/java/epic/EIPCClient.java new file mode 100644 index 00000000..c94024af --- /dev/null +++ b/qqinterface/src/main/java/epic/EIPCClient.java @@ -0,0 +1,4 @@ +package epic; + +public class EIPCClient { +} diff --git a/qqinterface/src/main/java/epic/EIPCResult.java b/qqinterface/src/main/java/epic/EIPCResult.java new file mode 100644 index 00000000..326f32ab --- /dev/null +++ b/qqinterface/src/main/java/epic/EIPCResult.java @@ -0,0 +1,11 @@ +package epic; + +import android.os.Bundle; + +public class EIPCResult { + public Bundle data; + + public boolean isSuccess() { + return false; + } +} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt index b7d810b5..fb8c4c07 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt @@ -1,5 +1,6 @@ package moe.fuqiuluo.qqinterface.servlet.msg.convert +import com.tencent.mobileqq.qmmkv.QMMKV import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgElement import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc @@ -13,6 +14,8 @@ import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.tools.asJsonObject import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.json +import mqq.app.MobileQQ +import kotlin.jvm.internal.Intrinsics internal sealed class MessageElemConverter: IMessageConvert { /** @@ -135,14 +138,16 @@ internal sealed class MessageElemConverter: IMessageConvert { ImageMapping(md5.uppercase(), chatType, image.fileSize) ) + //LogCenter.log(image.toString()) + return MessageSegment( type = "image", data = hashMapOf( "file" to md5, "url" to when(chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(md5) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(md5) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(md5) + MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(image.originImageUrl, md5) + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(image.originImageUrl, md5) + MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(image.originImageUrl, md5) else -> unknownChatType(chatType) }, "subType" to image.picSubType, diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt index 61a63094..98232fc4 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt @@ -1,9 +1,13 @@ @file:OptIn(ExperimentalSerializationApi::class) package moe.fuqiuluo.qqinterface.servlet.transfile +import android.os.Bundle import com.tencent.mobileqq.pb.ByteStringMicro +import com.tencent.mobileqq.perf.block.BinderMethodProxy +import com.tencent.mobileqq.qipc.QIPCClientHelper import com.tencent.mobileqq.transfile.FileMsg import com.tencent.mobileqq.transfile.api.IProtoReqManager +import com.tencent.mobileqq.transfile.dns.IpData import com.tencent.mobileqq.transfile.protohandler.RichProto import com.tencent.mobileqq.transfile.protohandler.RichProtoProc import kotlinx.coroutines.suspendCancellableCoroutine @@ -19,18 +23,35 @@ 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 mqq.app.MobileQQ import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody import protobuf.oidb.cmd0xfc2.Oidb0xfc2RspBody -import mqq.app.MobileQQ import tencent.im.cs.cmd0x346.cmd0x346 import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.cmd0xe37.cmd0xe37 import tencent.im.oidb.oidb_sso +import java.util.ArrayList import kotlin.coroutines.resume +private const val GPRO_PIC = "gchat.qpic.cn" +private const val GPRO_PIC_NT = "multimedia.nt.qq.com.cn" +private const val C2C_PIC = "c2cpicdw.qpic.cn" + internal object RichProtoSvc: BaseSvc() { + /*@Deprecated("Use RichProtoSvc.getQQDns instead", ReplaceWith("getQQDns(domain)")) + fun getQQDns(domain: String) { + val bundle = Bundle() + bundle.putString("domain", "xxx") + bundle.putInt("businessType", 1) + val result = BinderMethodProxy + .callServer(QIPCClientHelper.getInstance().client, "InnerDnsModule", "reqDomain2IpList", bundle) + if (result.isSuccess) { + val ipList: ArrayList = result.data.getParcelableArrayList("ip")!! + } + }*/ + suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String { val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, ProtoBuf.encodeToByteArray( Oidb0xfc2ReqBody( @@ -142,19 +163,35 @@ internal object RichProtoSvc: BaseSvc() { } fun getGroupPicDownUrl( + originalUrl: String, md5: String ): String { - return "http://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2" + val domain = if (originalUrl.contains("rkey=")) GPRO_PIC_NT else GPRO_PIC + if (originalUrl.isNotEmpty()) { + return "https://$domain$originalUrl" + } + return "https://$domain/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2" } fun getC2CPicDownUrl( + originalUrl: String, md5: String ): String { - return "https://c2cpicdw.qpic.cn/offpic_new/0/123-0-${md5.uppercase()}/0?term=2" + if (originalUrl.isNotEmpty()) { + return "https://$C2C_PIC$originalUrl" + } + return "https://$C2C_PIC/offpic_new/0/123-0-${md5.uppercase()}/0?term=2" } - fun getGuildPicDownUrl(md5: String): String { - return "https://gchat.qpic.cn/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2" + fun getGuildPicDownUrl( + originalUrl: String, + md5: String + ): String { + val domain = if (originalUrl.contains("rkey=")) GPRO_PIC_NT else GPRO_PIC + if (originalUrl.isNotEmpty()) { + return "https://$domain$originalUrl" + } + return "https://$domain/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2" } suspend fun getC2CVideoDownUrl( @@ -321,12 +358,4 @@ internal object RichProtoSvc: BaseSvc() { RichProtoProc.procRichProtoReq(richProtoReq) } } - - suspend fun getGuildPttDownUrl( - peerId: String, - md5Hex: String, - fileUUId: String - ): String { - return "unsupported" - } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt index f0a5c3ce..1965315b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt @@ -35,8 +35,8 @@ internal object GetImage: IActionHandler() { image.size, image.fileName, when(image.chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(fileMd5) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(fileMd5) + MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl("", fileMd5) + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl("", fileMd5) else -> error("Not supported chat type: ${image.chatType}, convertMsgElementsToMsgSegment::Pic") } ), echo = echo) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt index a9bf3fae..e64a633c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt @@ -103,7 +103,7 @@ internal object UploadGroupFile : IActionHandler() { // 根据文件大小调整超时时间 val msgIdPair = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEGROUP) - val info = (withTimeoutOrNull((srcFile.length() / (300 * 1024)) * 1000 + 5000) { + val info = (withTimeoutOrNull((srcFile.length() / (125 * 1024)) * 1000 + 5000) { val msgService = QRoute.api(IMsgService::class.java) val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId) suspendCancellableCoroutine { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt index 9873f2f5..576324fe 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt @@ -1,12 +1,42 @@ +@file:Suppress("UNUSED_VARIABLE", "LocalVariableName") + package moe.fuqiuluo.shamrock.xposed.hooks import android.content.Context +import com.tencent.mobileqq.perf.block.BinderMethodProxy +import com.tencent.mobileqq.qmmkv.MMKVOptionEntity +import de.robv.android.xposed.XposedBridge +import epic.EIPCClient +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.tools.beforeHook +import moe.fuqiuluo.shamrock.tools.hookMethod +import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader +import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.XposedHook +import java.lang.reflect.Modifier -@XposedHook(priority = -1) +@XposedHook(priority = -1, process = Process.ALL) internal class HookForDebug: IAction { override fun invoke(ctx: Context) { - /* + /*val NtDnsManager = LuoClassloader.load("com.tencent.qqnt.dns.NtDnsManager")!! + val NtDnsInternal = NtDnsManager.declaredMethods.first { + !Modifier.isStatic(it.modifiers) && it.parameterCount == 0 + }.returnType + XposedBridge.hookMethod(NtDnsInternal.declaredMethods.first { + it.parameterCount == 2 + && it.parameterTypes[0] == String::class.java + && it.parameterTypes[1] == Int::class.java + && it.returnType == ArrayList::class.java + }, beforeHook { + val domain = it.args[0] as String + val type = it.args[1] as Int + LogCenter.log("NtDnsManager: reqDomain2IpList($domain, $type)") + LogCenter.log(Exception().stackTraceToString()) + })*/ + } +} + +/* val httpEngineService = AppRuntimeFetcher.appRuntime .getRuntimeService(IHttpEngineService::class.java, "all") httpEngineService.javaClass.hookMethod("sendReq").before { @@ -19,7 +49,17 @@ internal class HookForDebug: IAction { LogCenter.log("请求地址: ${req.mReqUrl}") LogCenter.log("请求: ${req.toInnerValuesString(NetReq::class.java)}") } + } + BinderMethodProxy::class.java.hookMethod("callServer").before { + val action = it.args[2] as String + if (action == "reqDomain2IpList") { + LogCenter.log(Exception().stackTraceToString()) + } + } + EIPCClient::class.java.hookMethod("callServer").before { + val module = it.args[0] as String + val action = it.args[1] as String + if (action == "reqDomain2IpList" || module.contains("dns", ignoreCase = true)) { + LogCenter.log(Exception().stackTraceToString()) + } }*/ - - } -} \ No newline at end of file