Merge branch 'master' of github.com:whitechi73/OpenShamrock

This commit is contained in:
ikechan8370 2023-12-06 15:53:11 +08:00
commit 48c9048a00
18 changed files with 387 additions and 202 deletions

View File

@ -18,7 +18,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-java@v3 - uses: actions/setup-java@v4.0.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17
@ -30,7 +30,7 @@ jobs:
echo "sdk.dir=${ANDROID_HOME}" > local.properties echo "sdk.dir=${ANDROID_HOME}" > local.properties
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2.9.0 uses: gradle/gradle-build-action@v2.10.0
- name: Build with Gradle - name: Build with Gradle
run: | run: |
@ -46,42 +46,41 @@ jobs:
KEY_ALIAS: ${{ secrets.SIGN_ALIAS }} KEY_ALIAS: ${{ secrets.SIGN_ALIAS }}
KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }} KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
- name: Install aapt
run: sudo apt-get update && sudo apt-get install -y aapt
- name: Set Shamrock Version - name: Set Shamrock Version
run: | run: |
apk_file=${{ env.APK_FILE_ALL }} version_name_all=$(basename -s .apk "${{ env.APK_FILE_ALL }}")
apk_dump=$(aapt dump badging "$apk_file") version_name_arm64=$(basename -s .apk "${{ env.APK_FILE_ARM64 }}")
version_name=$(sed -n "s/.*versionName='\([^']*\)'.*/\1/p" <<< "$apk_dump") version_name_x86_64=$(basename -s .apk "${{ env.APK_FILE_X86_64 }}")
echo "SHAMROCK_VERSION=$version_name" >> $GITHUB_ENV echo "SHAMROCK_VERSION_ALL=$version_name_all" >> $GITHUB_ENV
echo "SHAMROCK_VERSION_ARM64=$version_name_arm64" >> $GITHUB_ENV
echo "SHAMROCK_VERSION_x86_64=$version_name_x86_64" >> $GITHUB_ENV
- name: Show Artifacts SHA256 - name: Show Artifacts SHA256
run: | run: |
echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY
echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY
echo "|:--------:|:----------|" >> $GITHUB_STEP_SUMMARY echo "|:--------:|:----------|" >> $GITHUB_STEP_SUMMARY
all=($(sha256sum ${{ env.APK_FILE_ALL }})) all=($(sha256sum "${{ env.APK_FILE_ALL }}"))
echo "|all|$all" >> $GITHUB_STEP_SUMMARY echo "|all|$all" >> $GITHUB_STEP_SUMMARY
arm64=($(sha256sum ${{ env.APK_FILE_ARM64 }})) arm64=($(sha256sum "${{ env.APK_FILE_ARM64 }}"))
echo "|arm64|$arm64" >> $GITHUB_STEP_SUMMARY echo "|arm64|$arm64" >> $GITHUB_STEP_SUMMARY
x86_64=($(sha256sum ${{ env.APK_FILE_X86_64 }})) x86_64=($(sha256sum "${{ env.APK_FILE_X86_64 }}"))
echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY
- name: Upload ALL APK RELEASE - name: Upload ALL APK RELEASE
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-all name: "${{ env.SHAMROCK_VERSION_ALL }}"
path: ${{ env.APK_FILE_ALL }} path: "${{ env.APK_FILE_ALL }}"
- name: Upload ARM64 APK RELEASE - name: Upload ARM64 APK RELEASE
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-arm64 name: "${{ env.SHAMROCK_VERSION_ARM64 }}"
path: ${{ env.APK_FILE_ARM64 }} path: "${{ env.APK_FILE_ARM64 }}"
- name: Upload X86_64 APK RELEASE - name: Upload X86_64 APK RELEASE
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-x86_64 name: "${{ env.SHAMROCK_VERSION_x86_64 }}"
path: ${{ env.APK_FILE_X86_64 }} path: "${{ env.APK_FILE_X86_64 }}"

View File

@ -24,7 +24,7 @@ android {
minSdk = 24 minSdk = 24
targetSdk = 33 targetSdk = 33
versionCode = (System.currentTimeMillis() / 1000).toInt() versionCode = (System.currentTimeMillis() / 1000).toInt()
versionName = "1.0.6-dev" + gitCommitHash() versionName = "1.0.7-dev" + gitCommitHash()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@ -122,16 +122,6 @@ fun LabFragment() {
return@Function true return@Function true
} }
Function(
title = "自动唤醒QQ",
desc = "QQ进程死亡时重新打开QQ进程前提本进程存活。",
descColor = color,
isSwitch = ShamrockConfig.enableAutoStart(ctx)
) {
ShamrockConfig.setAutoStart(ctx, it)
return@Function true
}
Function( Function(
title = "开启Shell接口", title = "开启Shell接口",
desc = "可能导致设备被入侵,请勿随意开启。", desc = "可能导致设备被入侵,请勿随意开启。",
@ -142,6 +132,16 @@ fun LabFragment() {
return@Function true return@Function true
} }
Function(
title = "自动唤醒QQ",
desc = "QQ进程死亡时重新打开QQ进程前提本进程存活。",
descColor = color,
isSwitch = ShamrockConfig.enableAutoStart(ctx)
) {
ShamrockConfig.setAutoStart(ctx, it)
return@Function true
}
kotlin.runCatching { kotlin.runCatching {
ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE) ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE)
}.onSuccess { }.onSuccess {
@ -155,6 +155,17 @@ fun LabFragment() {
scope.toast(ctx, LocalString.restartSysToast) scope.toast(ctx, LocalString.restartSysToast)
return@Function true return@Function true
} }
Function(
title = "禁用Doze模式",
desc = "禁止系统进入节能模式。",
descColor = color,
isSwitch = it.getBoolean("hook_doze", false)
) { value ->
it.edit().putBoolean("hook_doze", value).apply()
scope.toast(ctx, LocalString.restartSysToast)
return@Function true
}
}.onFailure { }.onFailure {
AppRuntime.log("无法启用附加选项LSPosed模块未激活或者不支持XSharedPreferences", Level.WARN) AppRuntime.log("无法启用附加选项LSPosed模块未激活或者不支持XSharedPreferences", Level.WARN)
} }

View File

@ -109,7 +109,7 @@ internal object CardSvc: BaseSvc() {
val dataService = app val dataService = app
.getRuntimeService(IProfileDataService::class.java, "all") .getRuntimeService(IProfileDataService::class.java, "all")
val card = refreshCardLock.withLock { val card = refreshCardLock.withLock {
suspendCancellableCoroutine<Card?> { suspendCancellableCoroutine {
app.addObserver(object: ProfileCardObserver() { app.addObserver(object: ProfileCardObserver() {
override fun onGetProfileCard(success: Boolean, obj: Any) { override fun onGetProfileCard(success: Boolean, obj: Any) {
app.removeObserver(this) app.removeObserver(this)

View File

@ -2,6 +2,7 @@
package moe.fuqiuluo.qqinterface.servlet package moe.fuqiuluo.qqinterface.servlet
import androidx.core.text.HtmlCompat
import com.tencent.common.app.AppInterface import com.tencent.common.app.AppInterface
import com.tencent.mobileqq.app.BusinessHandlerFactory import com.tencent.mobileqq.app.BusinessHandlerFactory
import com.tencent.mobileqq.app.QQAppInterface import com.tencent.mobileqq.app.QQAppInterface
@ -838,12 +839,13 @@ internal object GroupSvc: BaseSvc() {
senderId = obj["u"].asLong, senderId = obj["u"].asLong,
publishTime = obj["pubt"].asLong, publishTime = obj["pubt"].asLong,
message = GroupAnnouncementMessage( message = GroupAnnouncementMessage(
text = obj["msg"].asJsonObject["text"].asString, // text = obj["msg"].asJsonObject["text"].asString,
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map { text = fromHtml(obj["msg"].asJsonObject["text"].asString),
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map { pic ->
GroupAnnouncementMessageImage( GroupAnnouncementMessageImage(
id = it.jsonObject["id"].asString, id = pic.jsonObject["id"].asString,
width = it.jsonObject["w"].asString, width = pic.jsonObject["w"].asString,
height = it.jsonObject["h"].asString, height = pic.jsonObject["h"].asString,
) )
} ?: ArrayList() } ?: ArrayList()
) )
@ -854,6 +856,14 @@ internal object GroupSvc: BaseSvc() {
} }
} }
private fun fromHtml(htmlString: String): String {
return HtmlCompat
// 特殊处理&#10;目的是替换为换行符否则会被fromHtml忽略并移除
.fromHtml(htmlString.replace("&#10;", "[shamrockplaceholder]"), HtmlCompat.FROM_HTML_MODE_LEGACY)
.toString()
.replace("[shamrockplaceholder]", "\n")
}
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> { suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> {
val file = FileUtils.parseAndSave(image) val file = FileUtils.parseAndSave(image)

View File

@ -274,7 +274,7 @@ internal object MessageMaker {
element.elementType = MsgConstant.KELEMTYPEREPLY element.elementType = MsgConstant.KELEMTYPEREPLY
val reply = ReplyElement() val reply = ReplyElement()
val msgHash = data["id"].asString.toInt() val msgHash = data["id"].asInt
val mapping = MessageHelper.getMsgMappingByHash(msgHash) val mapping = MessageHelper.getMsgMappingByHash(msgHash)
?: return Result.failure(Exception("不存在该消息映射,无法回复消息")) ?: return Result.failure(Exception("不存在该消息映射,无法回复消息"))
@ -627,10 +627,16 @@ internal object MessageMaker {
else -> { else -> {
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure { val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure {
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR) LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
}.getOrThrow() }.getOrNull()
at.content = "@${info.troopnick if (info != null) {
at.content = "@${
info.troopnick
.ifNullOrEmpty(info.friendnick) .ifNullOrEmpty(info.friendnick)
.ifNullOrEmpty(qq)}" .ifNullOrEmpty(qq)
}"
} else {
at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}"
}
at.atType = MsgConstant.ATTYPEONE at.atType = MsgConstant.ATTYPEONE
at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong()) at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong())
} }

View File

@ -331,7 +331,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
val notify = tip.xmlElement val notify = tip.xmlElement
when(notify.busiId) { when(notify.busiId) {
/* 群戳一戳 */1061L -> {} /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
} }
} }

View File

@ -184,6 +184,32 @@ internal object MessageHelper {
} }
} }
suspend fun sendMessageNoCb(
chatType: Int,
peerId: String,
message: JsonArray,
fromId: String = peerId
): Pair<Int, Long> {
val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter {
it.elementType != -1
} as ArrayList<MsgElement>
val contact = generateContact(chatType, peerId, fromId)
val nonMsg: Boolean = message.isEmpty()
return if (!nonMsg) {
val service = QRoute.api(IMsgService::class.java)
return suspendCoroutine {
service.sendMsg(contact, uniseq.second, msg) { code, why ->
it.resume(code to uniseq.second)
}
}
} else {
-1 to uniseq.second
}
}
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact { suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) { val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
ContactHelper.getUidByUinAsync(id.toLong()) ContactHelper.getUidByUinAsync(id.toLong())

View File

@ -2,11 +2,7 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
import kotlinx.coroutines.GlobalScope import kotlinx.serialization.json.*
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
@ -19,21 +15,6 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult
import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
sealed interface ForwardMsgNode {
class MessageIdNode(
val id: Int
) : ForwardMsgNode
open class MessageNode(
val name: String,
val content: JsonElement?
) : ForwardMsgNode
object EmptyNode : MessageNode("", null)
}
internal object SendForwardMessage : IActionHandler() { internal object SendForwardMessage : IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String { override suspend fun internalHandle(session: ActionSession): String {
@ -78,7 +59,7 @@ internal object SendForwardMessage : IActionHandler() {
suspend operator fun invoke( suspend operator fun invoke(
chatType: Int, chatType: Int,
peerId: String, peerId: String,
message: JsonArray, messages: JsonArray,
echo: JsonElement = EmptyJsonString echo: JsonElement = EmptyJsonString
): String { ): String {
kotlin.runCatching { kotlin.runCatching {
@ -87,79 +68,91 @@ internal object SendForwardMessage : IActionHandler() {
val msgService = sessionService.msgService val msgService = sessionService.msgService
val selfUin = TicketSvc.getUin() val selfUin = TicketSvc.getUin()
val msgs = message.map { val multiNodes = messages.map {
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段 if (it.asJsonObject["type"].asStringOrNull != "node") {
LogCenter.log("包含非node类型节点", Level.WARN)
return@map null
}
if (it.asJsonObject["data"] !is JsonObject) {
LogCenter.log("data字段错误", Level.WARN)
return@map null
}
it.asJsonObject["data"].asJsonObject.let { data -> it.asJsonObject["data"].asJsonObject.let { data ->
if (data.containsKey("content")) { if (data.containsKey("id")) {
if (data["content"] is JsonArray) { val record = MsgSvc.getMsg(data["id"].asInt).getOrNull()
data["content"].asJsonArray.forEach { msg -> if (record == null) {
if (msg.asJsonObject["type"].asStringOrNull == "node") { LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN)
LogCenter.log("合并转发消息不支持嵌套", Level.WARN) return@map null
return@map ForwardMsgNode.EmptyNode
}
}
}
ForwardMsgNode.MessageNode(
name = data["name"].asStringOrNull ?: "",
content = data["content"]
)
} else ForwardMsgNode.MessageIdNode(data["id"].asInt)
}
}.map {
if (it is ForwardMsgNode.MessageIdNode) {
val recordResult = MsgSvc.getMsg(it.id)
if (!recordResult.isFailure) {
ForwardMsgNode.EmptyNode
} else { } else {
val record = recordResult.getOrThrow() record.peerName to record.toSegments().map { segment ->
ForwardMsgNode.MessageNode(
name = record.sendMemberName
.ifBlank { record.sendNickName }
.ifBlank { record.sendRemarkName }
.ifBlank { record.peerName },
content = record.toSegments().map { segment ->
segment.toJson() segment.toJson()
}.json }.json
) }
} else if (data.containsKey("content")) {
(data["name"].asStringOrNull ?: "Anno") to when (val raw = data["content"]) {
is JsonObject -> raw.asJsonArray
is JsonArray -> raw.asJsonArray
else -> MessageHelper.decodeCQCode(raw.asString)
} }
} else { } else {
it as ForwardMsgNode.MessageNode LogCenter.log("消息节点缺少id或content字段", Level.WARN)
return@map null
}
}.let { node ->
val content = node.second.map { msg ->
when (msg.asJsonObject["type"].asStringOrNull ?: "text") {
"at" -> {
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put(
"text", "@${
msg.asJsonObject["data"].asJsonObject["name"].asStringOrNull.ifNullOrEmpty(
msg.asJsonObject["data"].asJsonObject["qq"].asString
)
}"
)
}
} }
}.filter {
it.content != null
} }
val multiNodes = msgs.map { node -> "voice" -> {
suspendCoroutine { buildJsonObject {
GlobalScope.launch { put("type", "text")
var msgId: Long = 0 putJsonObject("data") {
msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, put("text", "[语音]")
selfUin,
node.content!!.let { msg ->
if (msg is JsonArray) msg
else if (msg is JsonObject) listOf(msg).jsonArray
else MessageHelper.decodeCQCode(msg.asString)
},
{ code, why ->
if (code != 0) {
error("合并转发消息节点消息发送失败:$code($why)")
}
it.resume(node.name to msgId)
}).first
}.invokeOnCompletion {
it?.let {
LogCenter.log("合并转发消息节点消息发送失败:${it.stackTraceToString()}", Level.ERROR)
} }
} }
} }
"node" -> {
LogCenter.log("合并转发消息暂时不支持嵌套", Level.WARN)
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put("text", "[合并转发消息]")
} }
}
}
else -> msg
}
}.json
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content)
if (result.first != 0) {
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
}
result.second to node.first
}
}.filterNotNull()
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
val to = MessageHelper.generateContact(chatType, peerId) val to = MessageHelper.generateContact(chatType, peerId)
val uniseq = MessageHelper.generateMsgId(chatType) val uniseq = MessageHelper.generateMsgId(chatType)
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply { msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) } multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) }
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first)) }.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first))
return ok( return ok(
@ -174,7 +167,7 @@ internal object SendForwardMessage : IActionHandler() {
return logic("合并转发消息失败(unknown error)", echo) return logic("合并转发消息失败(unknown error)", echo)
} }
override val requiredParams: Array<String> = arrayOf("message") override val requiredParams: Array<String> = arrayOf("messages")
override fun path(): String = "send_forward_msg" override fun path(): String = "send_forward_msg"
} }

View File

@ -29,7 +29,7 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
import moe.fuqiuluo.shamrock.tools.respond import moe.fuqiuluo.shamrock.tools.respond
fun Routing.messageAction() { fun Routing.messageAction() {
route("/send_group_forward_msg") { route("/send_group_forward_(msg|message)".toRegex()) {
post { post {
val groupId = fetchPostOrNull("group_id") val groupId = fetchPostOrNull("group_id")
val messages = fetchPostJsonArray("messages") val messages = fetchPostJsonArray("messages")
@ -40,7 +40,7 @@ fun Routing.messageAction() {
} }
} }
route("/send_private_forward_msg") { route("/send_private_forward_(msg|message)".toRegex()) {
post { post {
val userId = fetchPostOrNull("user_id") val userId = fetchPostOrNull("user_id")
val messages = fetchPostJsonArray("messages") val messages = fetchPostJsonArray("messages")
@ -51,6 +51,18 @@ fun Routing.messageAction() {
} }
} }
route("/send_forward_(msg|message)".toRegex()) {
post {
val userId = fetchPostOrNull("user_id")
val groupId = fetchPostOrNull("group_id")
val messages = fetchPostJsonArray("messages")
call.respondText(SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId ?: groupId?: "", messages), ContentType.Application.Json)
}
get {
respond(false, Status.InternalHandlerError, "Not support GET method")
}
}
getOrPost("/get_forward_msg") { getOrPost("/get_forward_msg") {
val id = fetchOrThrow("id") val id = fetchOrThrow("id")
call.respondText(GetForwardMsg(id), ContentType.Application.Json) call.respondText(GetForwardMsg(id), ContentType.Application.Json)

View File

@ -25,6 +25,7 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.RequestEvent
import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType
import moe.fuqiuluo.shamrock.remote.service.data.push.RequestType import moe.fuqiuluo.shamrock.remote.service.data.push.RequestType
import moe.fuqiuluo.shamrock.remote.service.data.push.Sender 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.ShamrockDsl
import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.json
import java.util.ArrayList import java.util.ArrayList
@ -81,8 +82,8 @@ internal object GlobalEventTransmitter: BaseSvc() {
sender = Sender( sender = Sender(
userId = record.senderUin, userId = record.senderUin,
nickname = record.sendNickName nickname = record.sendNickName
.ifBlank { record.sendMemberName }
.ifBlank { record.sendRemarkName } .ifBlank { record.sendRemarkName }
.ifBlank { record.sendMemberName }
.ifBlank { record.peerName }, .ifBlank { record.peerName },
card = record.sendMemberName, card = record.sendMemberName,
role = when (record.senderUin) { role = when (record.senderUin) {
@ -222,6 +223,24 @@ internal object GlobalEventTransmitter: BaseSvc() {
* 群聊通知 通知器 * 群聊通知 通知器
*/ */
object GroupNoticeTransmitter { object GroupNoticeTransmitter {
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean {
pushNotice(NoticeEvent(
time = time,
selfId = app.longAccountUin,
postType = PostType.Notice,
type = NoticeType.Notify,
subType = NoticeSubType.Sign,
userId = target,
groupId = groupCode,
target = target,
signDetail = SignDetail(
rankImg = rankImg,
action = action
)
))
return true
}
suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean { suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
pushNotice(NoticeEvent( pushNotice(NoticeEvent(
time = time, time = time,

View File

@ -4,6 +4,8 @@ import android.content.Intent
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString 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.GlobalJson5
import moe.fuqiuluo.shamrock.utils.MMKVFetcher import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import mqq.app.MobileQQ import mqq.app.MobileQQ
@ -15,10 +17,14 @@ internal object ShamrockConfig {
if (it.exists()) it.delete() if (it.exists()) it.delete()
it.mkdirs() it.mkdirs()
} }
private val Config: ServiceConfig by lazy { private val Config = kotlin.runCatching {
GlobalJson5.decodeFromString(ConfigDir.resolve("config.json").also { GlobalJson5.decodeFromString<ServiceConfig>(ConfigDir.resolve("config.json").also {
if (!it.exists()) it.writeText("{}") if (!it.exists()) it.writeText("{}")
}.readText()) }.readText())
}.onFailure {
LogCenter.log("您的配置文件出现错误: ${it.stackTraceToString()}", Level.ERROR)
}.getOrElse {
ServiceConfig()
} }
fun isInit(): Boolean { fun isInit(): Boolean {

View File

@ -42,6 +42,8 @@ internal enum class NoticeSubType {
@SerialName("kick_me") KickMe, @SerialName("kick_me") KickMe,
@SerialName("poke") Poke, @SerialName("poke") Poke,
@SerialName("sign") Sign,
@SerialName("title") Title, @SerialName("title") Title,
@SerialName("delete") Delete, @SerialName("delete") Delete,
@ -87,6 +89,9 @@ internal data class NoticeEvent(
// 戳一戳 // 戳一戳
@SerialName("poke_detail") val pokeDetail: PokeDetail? = null, @SerialName("poke_detail") val pokeDetail: PokeDetail? = null,
// 群打卡
@SerialName("sign_detail") val signDetail: SignDetail? = null,
) )
/** /**
@ -132,3 +137,10 @@ internal data class PokeDetail (
@SerialName("action_img_url") @SerialName("action_img_url")
val actionImg: String? = "https://tianquan.gtimg.cn/nudgeaction/item/0/expression.jpg", val actionImg: String? = "https://tianquan.gtimg.cn/nudgeaction/item/0/expression.jpg",
) )
@Serializable
internal data class SignDetail (
val action: String? = "今日第1个打卡",
@SerialName("rank_img")
val rankImg: String? = "",
)

View File

@ -182,7 +182,9 @@ internal object AioListener: IKernelMsgListener {
.updateMsgSeqByMsgHash(msgHash, record.msgSeq.toInt()) .updateMsgSeqByMsgHash(msgHash, record.msgSeq.toInt())
} }
if (!ShamrockConfig.enableSelfMsg() || record.senderUin != TicketSvc.getLongUin()) if (!ShamrockConfig.enableSelfMsg()
|| record.senderUin != TicketSvc.getLongUin()
|| record.peerUin == TicketSvc.getLongUin())
return@launch return@launch
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString()) val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())

View File

@ -63,6 +63,7 @@ internal object PrimitiveListener {
subType = pb[1, 2, 2].asInt subType = pb[1, 2, 2].asInt
} }
val msgTime = pb[1, 2, 6].asLong val msgTime = pb[1, 2, 6].asLong
try {
when (msgType) { when (msgType) {
33 -> onGroupMemIncreased(msgTime, pb) 33 -> onGroupMemIncreased(msgTime, pb)
34 -> onGroupMemberDecreased(msgTime, pb) 34 -> onGroupMemberDecreased(msgTime, pb)
@ -82,10 +83,13 @@ internal object PrimitiveListener {
12 -> onGroupBan(msgTime, pb) 12 -> onGroupBan(msgTime, pb)
16 -> onGroupTitleChange(msgTime, pb) 16 -> onGroupTitleChange(msgTime, pb)
17 -> onGroupRecall(msgTime, pb) 17 -> onGroupRecall(msgTime, pb)
20 -> onGroupPoke(msgTime, pb) 20 -> onGroupPokeAndGroupSign(msgTime, pb)
21 -> onEssenceMessage(msgTime, pb) 21 -> onEssenceMessage(msgTime, pb)
} }
} }
} catch (e: Exception) {
LogCenter.log("onMsgPush(msgType: $msgType): "+e.stackTraceToString(), Level.WARN)
}
} }
private suspend fun onC2cPoke(msgTime: Long, pb: ProtoMap) { private suspend fun onC2cPoke(msgTime: Long, pb: ProtoMap) {
@ -153,8 +157,21 @@ internal object PrimitiveListener {
private suspend fun onCardChange(msgTime: Long, pb: ProtoMap) { private suspend fun onCardChange(msgTime: Long, pb: ProtoMap) {
val targetId = pb[1, 3, 2, 1, 13, 2].asUtf8String var detail = pb[1, 3, 2]
val newCardList = pb[1, 3, 2, 1, 13, 3].asList if (detail !is ProtoMap) {
try {
val readPacket = ByteReadPacket(detail.asByteArray)
readPacket.readBuf32Long()
readPacket.discardExact(1)
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
readPacket.release()
} catch (e: Exception) {
LogCenter.log("onCardChange error: ${e.stackTraceToString()}", Level.WARN)
}
}
val targetId = detail[1, 13, 2].asUtf8String
val newCardList = detail[1, 13, 3].asList
var newCard = "" var newCard = ""
newCardList newCardList
.value .value
@ -163,7 +180,7 @@ internal object PrimitiveListener {
newCard = it[2].asUtf8String newCard = it[2].asUtf8String
} }
} }
val groupId = pb[1, 3, 2, 1, 13, 4].asLong val groupId = pb[1, 13, 4].asLong
var oldCard = "" var oldCard = ""
val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong() val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong()
LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard") LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard")
@ -181,13 +198,18 @@ internal object PrimitiveListener {
} }
private suspend fun onGroupTitleChange(msgTime: Long, pb: ProtoMap) { private suspend fun onGroupTitleChange(msgTime: Long, pb: ProtoMap) {
val groupCode = pb[1, 1, 1].asULong var detail = pb[1, 3, 2]
if (detail !is ProtoMap) {
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray) try {
val detail = if (readPacket.readBuf32Long() == groupCode) { val readPacket = ByteReadPacket(detail.asByteArray)
readPacket.readBuf32Long()
readPacket.discardExact(1) readPacket.discardExact(1)
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
} else pb[1, 3, 2] readPacket.release()
} catch (e: Exception) {
LogCenter.log("onGroupTitleChange error: ${e.stackTraceToString()}", Level.WARN)
}
}
val targetUin = detail[5, 5].asLong val targetUin = detail[5, 5].asLong
@ -212,13 +234,18 @@ internal object PrimitiveListener {
} }
private suspend fun onEssenceMessage(msgTime: Long, pb: ProtoMap) { private suspend fun onEssenceMessage(msgTime: Long, pb: ProtoMap) {
val groupCode = pb[1, 1, 1].asULong var detail = pb[1, 3, 2]
if (detail !is ProtoMap) {
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray) try {
val detail = if (readPacket.readBuf32Long() == groupCode) { val readPacket = ByteReadPacket(detail.asByteArray)
readPacket.readBuf32Long()
readPacket.discardExact(1) readPacket.discardExact(1)
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
} else pb[1, 3, 2] readPacket.release()
} catch (e: Exception) {
LogCenter.log("onEssenceMessage error: ${e.stackTraceToString()}", Level.WARN)
}
}
val groupId = detail[4].asLong val groupId = detail[4].asLong
val mesSeq = detail[37].asInt val mesSeq = detail[37].asInt
@ -254,31 +281,26 @@ internal object PrimitiveListener {
} }
private suspend fun onGroupPoke(time: Long, pb: ProtoMap) { private suspend fun onGroupPokeAndGroupSign(time: Long, pb: ProtoMap) {
val groupCode1 = pb[1, 1, 1].asULong var detail = pb[1, 3, 2]
var groupCode: Long = groupCode1
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray)
val groupCode2 = readPacket.readBuf32Long()
var detail = if (groupCode2 == groupCode1) {
groupCode = groupCode2
readPacket.discardExact(1)
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
} else pb[1, 3, 2]
if (detail !is ProtoMap) { if (detail !is ProtoMap) {
groupCode = groupCode2 try {
val readPacket = ByteReadPacket(detail.asByteArray)
readPacket.discardExact(4)
readPacket.discardExact(1) readPacket.discardExact(1)
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
}
readPacket.release() readPacket.release()
} catch (e: Exception) {
LogCenter.log("onGroupPokeAndGroupSign error: ${e.stackTraceToString()}", Level.WARN)
}
}
lateinit var target: String lateinit var target: String
lateinit var operation: String lateinit var operation: String
var action: String? = null var action: String? = null
var suffix: String? = null var suffix: String? = null
var actionImg: String? = null var actionImg: String? = null
var rankImg: String? = null
val groupCode = detail[4].asULong
detail[26][7] detail[26][7]
.asList .asList
.value .value
@ -287,14 +309,23 @@ internal object PrimitiveListener {
when (it[1].asUtf8String) { when (it[1].asUtf8String) {
"uin_str1" -> operation = value "uin_str1" -> operation = value
"uin_str2" -> target = value "uin_str2" -> target = value
// "nick_str1" -> operation_nick = value
// "nick_str2" -> operation_nick = value
"action_str" -> action = value "action_str" -> action = value
"alt_str1" -> action = value "alt_str1" -> action = value
"suffix_str" -> suffix = value "suffix_str" -> suffix = value
"action_img_url" -> actionImg = value "action_img_url" -> actionImg = value
}
}
LogCenter.log("群戳一戳($groupCode): $operation $action $target $suffix")
"mqq_uin" -> target = value
// "mqq_nick" -> operation_nick = value
"user_sign" -> action = value
"rank_img" -> rankImg = value
// "sign_word" -> 我也要打卡
}
}
when (detail[26][2].asInt) {
1061 -> {
LogCenter.log("群戳一戳($groupCode): $operation $action $target $suffix")
if (!GlobalEventTransmitter.GroupNoticeTransmitter if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode) .transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode)
) { ) {
@ -302,6 +333,21 @@ internal object PrimitiveListener {
} }
} }
1068 -> {
LogCenter.log("群打卡($groupCode): $action $target")
if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupSign(time, target.toLong(), action, rankImg, groupCode)
) {
LogCenter.log("群打卡推送失败!", Level.WARN)
}
}
else -> {
LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail[2].asInt}", Level.WARN)
}
}
}
private suspend fun onC2CRecall(time: Long, pb: ProtoMap) { private suspend fun onC2CRecall(time: Long, pb: ProtoMap) {
val operationUid = pb[1, 3, 2, 1, 1].asUtf8String val operationUid = pb[1, 3, 2, 1, 1].asUtf8String
val msgSeq = pb[1, 3, 2, 1, 20].asLong val msgSeq = pb[1, 3, 2, 1, 20].asLong

View File

@ -5,16 +5,13 @@ import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import de.robv.android.xposed.XposedBridge.log import de.robv.android.xposed.XposedBridge.log
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.utils.MMKVFetcher import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import moe.fuqiuluo.shamrock.xposed.loader.ActionLoader import moe.fuqiuluo.shamrock.xposed.loader.ActionLoader
import moe.fuqiuluo.shamrock.xposed.loader.FuckAMS import moe.fuqiuluo.shamrock.xposed.loader.KeepAlive
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
import moe.fuqiuluo.shamrock.tools.FuzzySearchClass import moe.fuqiuluo.shamrock.tools.FuzzySearchClass
import moe.fuqiuluo.shamrock.tools.afterHook import moe.fuqiuluo.shamrock.tools.afterHook
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
import mqq.app.MobileQQ import mqq.app.MobileQQ
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
@ -42,7 +39,7 @@ internal class XposedEntry: IXposedHookLoadPackage {
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) { override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) {
when (param.packageName) { when (param.packageName) {
PACKAGE_NAME_QQ -> entryMQQ(param.classLoader) PACKAGE_NAME_QQ -> entryMQQ(param.classLoader)
"android" -> FuckAMS.injectAMS(param.classLoader) "android" -> KeepAlive(param.classLoader)
PACKAGE_NAME_TIM -> entryTim(param.classLoader) PACKAGE_NAME_TIM -> entryTim(param.classLoader)
} }
} }

View File

@ -7,14 +7,12 @@ import android.content.pm.VersionedPackage
import android.os.Build import android.os.Build
import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XSharedPreferences import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
import moe.fuqiuluo.shamrock.tools.hookMethod import moe.fuqiuluo.shamrock.tools.hookMethod
import moe.fuqiuluo.shamrock.xposed.XposedEntry import moe.fuqiuluo.shamrock.xposed.XposedEntry
import moe.fuqiuluo.shamrock.xposed.loader.FuckAMS
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
@ -68,6 +66,8 @@ class AntiDetection: IAction {
} }
private fun antiFindPackage(context: Context) { private fun antiFindPackage(context: Context) {
if (isAntiFindPackage) return
val packageManager = context.packageManager val packageManager = context.packageManager
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock", 0) val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock", 0)
val packageInfo = packageManager.getPackageInfo("moe.fuqiuluo.shamrock", 0) val packageInfo = packageManager.getPackageInfo("moe.fuqiuluo.shamrock", 0)
@ -76,7 +76,7 @@ class AntiDetection: IAction {
val packageName = it.args[0] as String val packageName = it.args[0] as String
if(packageName == "moe.fuqiuluo.shamrock") { if(packageName == "moe.fuqiuluo.shamrock") {
LogCenter.log("AntiDetection: 检测到对Shamrock的检测欺骗PackageManager(GA)", Level.WARN) LogCenter.log("AntiDetection: 检测到对Shamrock的检测欺骗PackageManager(GA)", Level.WARN)
it.throwable = PackageManager.NameNotFoundException() it.throwable = PackageManager.NameNotFoundException("Hided")
} else if (packageName == "moe.fuqiuluo.shamrock.hided") { } else if (packageName == "moe.fuqiuluo.shamrock.hided") {
it.result = applicationInfo it.result = applicationInfo
} }
@ -102,6 +102,8 @@ class AntiDetection: IAction {
} }
} }
} }
isAntiFindPackage = true
} }
private fun antiMemoryWalking() { private fun antiMemoryWalking() {
@ -207,4 +209,9 @@ class AntiDetection: IAction {
}.toTypedArray() }.toTypedArray()
} }
} }
companion object {
@JvmStatic
var isAntiFindPackage = false
}
} }

View File

@ -3,14 +3,15 @@ package moe.fuqiuluo.shamrock.xposed.loader
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.os.Build import android.os.Build
import com.arthenica.ffmpegkit.BuildConfig import android.os.Process
import de.robv.android.xposed.XSharedPreferences import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import moe.fuqiuluo.shamrock.tools.hookMethod import moe.fuqiuluo.shamrock.tools.hookMethod
import java.lang.reflect.Method import java.lang.reflect.Method
import kotlin.concurrent.timer
internal object FuckAMS { internal object KeepAlive {
private val KeepPackage = arrayOf( private val KeepPackage = arrayOf(
"com.tencent.mobileqq", "moe.fuqiuluo.shamrock" "com.tencent.mobileqq", "moe.fuqiuluo.shamrock"
) )
@ -20,7 +21,46 @@ internal object FuckAMS {
private lateinit var METHOD_IS_KILLED: Method private lateinit var METHOD_IS_KILLED: Method
private var allowPersistent: Boolean = false private var allowPersistent: Boolean = false
fun injectAMS(loader: ClassLoader) { operator fun invoke(loader: ClassLoader) {
val pref = XSharedPreferences("moe.fuqiuluo.shamrock", "shared_config")
hookAMS(pref, loader)
hookDoze(pref, loader)
}
private fun hookDoze(pref: XSharedPreferences, loader: ClassLoader) {
if (pref.file.canRead() && pref.getBoolean("hook_doze", false)) {
val result = runCatching {
val DeviceIdleController = XposedHelpers.findClass("com.android.server.DeviceIdleController", loader)
?: return@runCatching -1
val becomeActiveLocked = XposedHelpers.findMethodBestMatch(DeviceIdleController, "becomeActiveLocked", String::class.java, Integer.TYPE)
?: return@runCatching -2
if (!becomeActiveLocked.isAccessible) {
becomeActiveLocked.isAccessible = true
}
DeviceIdleController.hookMethod("onStart").after {
XposedBridge.log("[Shamrock] DeviceIdleController onStart")
timer(initialDelay = 120_000L, period = 240_000L) {
XposedBridge.log("[Shamrock] try to wakeup screen")
becomeActiveLocked.invoke(it.thisObject, "screen", Process.myUid())
}
}
DeviceIdleController.hookMethod("becomeInactiveIfAppropriateLocked").before {
XposedBridge.log("[Shamrock] DeviceIdleController becomeInactiveIfAppropriateLocked")
it.result = Unit
}
DeviceIdleController.hookMethod("stepIdleStateLocked").before {
XposedBridge.log("[Shamrock] DeviceIdleController stepIdleStateLocked")
it.result = Unit
}
return@runCatching 0
}.getOrElse { -5 }
if(result < 0) {
XposedBridge.log("[Shamrock] Unable to hookDoze: $result")
}
}
}
private fun hookAMS(pref: XSharedPreferences, loader: ClassLoader) {
kotlin.runCatching { kotlin.runCatching {
val ActivityManagerService = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", loader) val ActivityManagerService = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", loader)
ActivityManagerService.hookMethod("newProcessRecordLocked").after { ActivityManagerService.hookMethod("newProcessRecordLocked").after {
@ -30,7 +70,6 @@ internal object FuckAMS {
XposedBridge.log("[Shamrock] Plan A failed: ${it.message}") XposedBridge.log("[Shamrock] Plan A failed: ${it.message}")
} }
val pref = XSharedPreferences("moe.fuqiuluo.shamrock", "shared_config")
if (pref.file.canRead()) { if (pref.file.canRead()) {
allowPersistent = pref.getBoolean("persistent", false) allowPersistent = pref.getBoolean("persistent", false)
XposedBridge.log("[Shamrock] allowPersistent = $allowPersistent") XposedBridge.log("[Shamrock] allowPersistent = $allowPersistent")