Shamrock: fix #251

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-02-23 19:33:36 +08:00
parent b4c40e236a
commit 9ad66f2f92
6 changed files with 255 additions and 31 deletions

View File

@ -32,9 +32,9 @@ class ProtobufProcessor(
}.toList()
if (actions.isNotEmpty()) {
actions.forEach { clz ->
if (clz.isInternal()) return@forEach
if (clz.isPrivate()) return@forEach
actions.forEachIndexed { index, clz ->
if (clz.isInternal()) return@forEachIndexed
if (clz.isPrivate()) return@forEachIndexed
val packageName = "protobuf.auto"
val fileSpecBuilder = FileSpec.scriptBuilder("FastProtobuf", packageName)
@ -58,7 +58,7 @@ class ProtobufProcessor(
codeGenerator.createNewFile(
dependencies = Dependencies.ALL_FILES,
packageName = packageName,
fileName = clz.simpleName.asString() + "\$FP"
fileName = "FP${clz.simpleName.asString().hashCode()}"
).use { outputStream ->
outputStream.writer().use {
fileSpecBuilder.build().writeTo(it)

View File

@ -9,5 +9,6 @@ data class TrpcOidb(
@ProtoNumber(1) val cmd: Int = Int.MIN_VALUE,
@ProtoNumber(2) val service: Int = Int.MIN_VALUE,
@ProtoNumber(4) val buffer: ByteArray,
//@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(),
@ProtoNumber(12) val flag: Int = Int.MIN_VALUE,
): Protobuf<TrpcOidb>

View File

@ -0,0 +1,135 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.oidb.cmd0x11c5
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class Oidb0x11c5Req(
@ProtoNumber(1) val head: MultiMediaRoutingHead,
@ProtoNumber(3) val dataInfo: MultiMediaDataInfo
): Protobuf<Oidb0x11c5Req>
@Serializable
data class Oidb0x11c5Resp(
@ProtoNumber(1) val head: Head,
@ProtoNumber(3) val result: DownloadResult?
): Protobuf<Oidb0x11c5Resp> {
companion object {
@Serializable
data class Head(
//@ProtoNumber(1) val request: Request,
@ProtoNumber(3) val msg: String
) {
/*companion object {
@Serializable
data class Request(
@ProtoNumber(1) val u1: UInt,
@ProtoNumber(2) val u2: UInt
)
}*/
}
@Serializable
data class DownloadResult(
@ProtoNumber(1) val rkeyParam: String,
@ProtoNumber(2) val expire: UInt,
)
}
}
@Serializable
data class MultiMediaDataInfo(
@ProtoNumber(1) val multiMedia: MultiMedia,
@ProtoNumber(2) val ext: EXT,
) {
companion object {
@Serializable
data class MultiMedia(
@ProtoNumber(1) val picture: Picture,
@ProtoNumber(2) val fileId: String,
@ProtoNumber(3) val u1: UInt,
@ProtoNumber(4) val u2: UInt,
@ProtoNumber(5) val u3: UInt,
@ProtoNumber(6) val u4: UInt,
)
@Serializable
data class Picture(
@ProtoNumber(1) val size: ULong,
@ProtoNumber(2) val md5: String,
@ProtoNumber(3) val sha: String,
@ProtoNumber(4) val fileName: String,
@ProtoNumber(5) val u1: U3,
@ProtoNumber(6) val width: UInt,
@ProtoNumber(7) val height: UInt,
@ProtoNumber(8) val u2: UInt,
@ProtoNumber(9) val u3: UInt,
)
@Serializable
data class U3(
@ProtoNumber(1) val u1: UInt,
@ProtoNumber(2) val u2: UInt,
@ProtoNumber(3) val u3: UInt,
@ProtoNumber(4) val u4: UInt,
)
@Serializable
data class EXT(
@ProtoNumber(2) val u1: U1,
)
@Serializable
data class U1(
@ProtoNumber(1) val u1: UInt,
@ProtoNumber(3) val u2: UInt,
@ProtoNumber(4) val u3: U2,
@ProtoNumber(5) val u4: UInt,
)
@Serializable
data class U2(
@ProtoNumber(1) val u1: ByteArray,
@ProtoNumber(2) val u2: ByteArray,
@ProtoNumber(3) val u3: ByteArray,
)
}
}
@Serializable
data class MultiMediaRoutingHead(
@ProtoNumber(1) val request: Request,
@ProtoNumber(2) val peerUser: PeerUser,
@ProtoNumber(3) val u1: U1
) {
companion object {
@Serializable
data class U1(
@ProtoNumber(1) val u1: UInt,
)
@Serializable
data class Request(
@ProtoNumber(1) val u1: UInt,
@ProtoNumber(2) val u2: UInt
)
@Serializable
data class PeerUser(
@ProtoNumber(101) val u1: UInt,
@ProtoNumber(102) val u2: UInt,
@ProtoNumber(200) val u3: UInt,
@ProtoNumber(201) val peer: Peer,
)
@Serializable
data class Peer(
@ProtoNumber(1) val u1: UInt,
@ProtoNumber(2) val uid: String
)
}
}

View File

@ -184,19 +184,20 @@ internal object MsgElementConverter {
element: MsgElement
): MessageSegment {
val image = element.picElement
val md5 = image.md5HexStr ?: image.fileName
val md5 = (image.md5HexStr ?: image.fileName
.replace("{", "")
.replace("}", "")
.replace("-", "").split(".")[0]
.replace("-", "").split(".")[0])
.uppercase()
ImageDB.getInstance().imageMappingDao().insert(
ImageMapping(md5.uppercase(), chatType, image.fileSize)
ImageMapping(md5, chatType, image.fileSize)
)
//LogCenter.log(image.toString())
val originalUrl = image.originImageUrl ?: ""
//LogCenter.log({ "receive image: $image" }, Level.DEBUG)
LogCenter.log({ "receive image: $image" }, Level.DEBUG)
return MessageSegment(
type = "image",
@ -208,7 +209,16 @@ internal object MsgElementConverter {
md5
)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5)
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
)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5)
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
},

View File

@ -6,15 +6,15 @@ 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 io.ktor.util.Identity.decode
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.shamrock.helper.ContactHelper
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.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
@ -23,6 +23,11 @@ import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.symbols.decodeProtobuf
import mqq.app.MobileQQ
import protobuf.auto.toByteArray
import protobuf.oidb.TrpcOidb
import protobuf.oidb.cmd0x11c5.MultiMediaDataInfo
import protobuf.oidb.cmd0x11c5.MultiMediaRoutingHead
import protobuf.oidb.cmd0x11c5.Oidb0x11c5Req
import protobuf.oidb.cmd0x11c5.Oidb0x11c5Resp
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo
import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody
@ -34,22 +39,11 @@ import tencent.im.oidb.oidb_sso
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 MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn"
private const val C2C_PIC = "c2cpicdw.qpic.cn"
internal object RichProtoSvc: BaseSvc() {
var multiMediaRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
/*@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<IpData> = result.data.getParcelableArrayList("ip")!!
}
}*/
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
@ -167,7 +161,7 @@ internal object RichProtoSvc: BaseSvc() {
md5: String,
): String {
val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) GPRO_PIC_NT else GPRO_PIC
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey=$multiMediaRKey"
@ -177,19 +171,38 @@ internal object RichProtoSvc: BaseSvc() {
return "https://$domain/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2"
}
fun getC2CPicDownUrl(
suspend fun getC2CPicDownUrl(
originalUrl: String,
md5: String
md5: String,
peer: String = "",
fileId: String = "",
sha: String = "",
fileSize: ULong = 0uL,
width: UInt = 0u,
height: UInt = 0u
): String {
val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) GPRO_PIC_NT else C2C_PIC
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else C2C_PIC
if (originalUrl.isNotEmpty()) {
if (fileId.isNotEmpty()) getC2CNtPicRKey(
peer = ContactHelper.getUidByUinAsync(peer.toLong()),
fileId = fileId,
md5 = md5,
sha = sha,
fileSize = fileSize,
width = width,
height = height
).onSuccess {
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl$it"
}
}
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey=$multiMediaRKey"
}
return "https://$domain$originalUrl"
}
return "https://$$domain/offpic_new/0/123-0-${md5.uppercase()}/0?term=2"
return "https://$$domain/offpic_new/0/123-0-${md5}/0?term=2"
}
fun getGuildPicDownUrl(
@ -197,7 +210,7 @@ internal object RichProtoSvc: BaseSvc() {
md5: String
): String {
val isNtServer = originalUrl.startsWith("/download")
val domain = if (isNtServer) GPRO_PIC_NT else GPRO_PIC
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else GPRO_PIC
if (originalUrl.isNotEmpty()) {
if (isNtServer && !originalUrl.contains("rkey=")) {
return "https://$domain$originalUrl&rkey=$multiMediaRKey"
@ -207,6 +220,71 @@ internal object RichProtoSvc: BaseSvc() {
return "https://$domain/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2"
}
suspend fun getC2CNtPicRKey(
peer: String,
fileId: String,
md5: String,
sha: String,
fileSize: ULong,
width: UInt,
height: UInt
): Result<String> {
val req = run {
Oidb0x11c5Req(
MultiMediaRoutingHead(
request = MultiMediaRoutingHead.Companion.Request(u1 = 2u, u2 = 200u),
peerUser = MultiMediaRoutingHead.Companion.PeerUser(u1 = 2u, u2 = 1u, u3 = 1u, peer = MultiMediaRoutingHead.Companion.Peer(
u1 = 2u,
uid = peer
)),
u1 = MultiMediaRoutingHead.Companion.U1(2u)
),
MultiMediaDataInfo(
MultiMediaDataInfo.Companion.MultiMedia(
MultiMediaDataInfo.Companion.Picture(
size = fileSize,
md5 = md5.lowercase(),
sha = sha.lowercase(),
fileName = "${md5}.jpg",
u1 = MultiMediaDataInfo.Companion.U3(
u1 = 1u,
u2 = 1000u,
u3 = 0u,
u4 = 0u
),
width = width,
height = height,
u2 = 0u,
u3 = 1u
),
fileId = fileId,
u1 = 1u,
u2 = 0u,
u3 = 0u,
u4 = 0u
),
MultiMediaDataInfo.Companion.EXT(
u1 = MultiMediaDataInfo.Companion.U1(
u1 = 0u,
u2 = 0u,
u3 = MultiMediaDataInfo.Companion.U2(
u1 = EMPTY_BYTE_ARRAY,
u2 = EMPTY_BYTE_ARRAY,
u3 = EMPTY_BYTE_ARRAY
),
u4 = 1u
)
)
)
)
}.toByteArray()
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 0x11c5, 200, req, true)?.slice(4)
buffer?.decodeProtobuf<TrpcOidb>()?.buffer?.decodeProtobuf<Oidb0x11c5Resp>()?.result?.rkeyParam?.let {
return Result.success(it)
}
return Result.failure(Exception("unable to get c2c nt pic"))
}
suspend fun getC2CVideoDownUrl(
peerId: String,
md5: ByteArray,

View File

@ -18,7 +18,7 @@ internal object GetImage: IActionHandler() {
return invoke(file, echo)
}
operator fun invoke(file: String, echo: JsonElement = EmptyJsonString): String {
suspend operator fun invoke(file: String, echo: JsonElement = EmptyJsonString): String {
val fileMd5 = file
.replace("{", "")
.replace("}", "")