migration from libxposed API to YukiHook
This commit is contained in:
@@ -267,6 +267,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
|
compileOnly(files("../core/libs/LSPosed-api-1.0-SNAPSHOT.jar"))
|
||||||
implementation(project(":common"))
|
implementation(project(":common"))
|
||||||
implementation(project(":native"))
|
implementation(project(":native"))
|
||||||
implementation(libs.androidx.documentfile)
|
implementation(libs.androidx.documentfile)
|
||||||
|
|||||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
-dontwarn de.robv.android.xposed.**
|
|
||||||
-dontwarn org.mozilla.javascript.**
|
-dontwarn org.mozilla.javascript.**
|
||||||
|
-dontwarn android.app.AndroidAppHelper
|
||||||
|
-dontwarn java.lang.reflect.AnnotatedType
|
||||||
|
|
||||||
-keep class com.tonyodev.fetch2.** { *; }
|
-keep class com.tonyodev.fetch2.** { *; }
|
||||||
-keep class com.tonyodev.fetch2core.** { *; }
|
-keep class com.tonyodev.fetch2core.** { *; }
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.yukihookapi.api)
|
||||||
|
implementation(libs.kavaref.core)
|
||||||
|
implementation(libs.kavaref.extension)
|
||||||
compileOnly(files("libs/LSPosed-api-1.0-SNAPSHOT.jar"))
|
compileOnly(files("libs/LSPosed-api-1.0-SNAPSHOT.jar"))
|
||||||
implementation(libs.coroutines)
|
implementation(libs.coroutines)
|
||||||
implementation(libs.recyclerview)
|
implementation(libs.recyclerview)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package me.eternal.purrfectsnap.core.util.hook;
|
||||||
|
|
||||||
|
import com.highcapable.yukihookapi.hook.core.api.helper.YukiHookHelper;
|
||||||
|
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiHookCallback;
|
||||||
|
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook;
|
||||||
|
import com.highcapable.yukihookapi.hook.core.api.result.YukiHookResult;
|
||||||
|
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
|
||||||
|
public final class YukiHookBridge {
|
||||||
|
private YukiHookBridge() {}
|
||||||
|
|
||||||
|
public interface ParamCallback {
|
||||||
|
void accept(Object param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object newMemberHook(ParamCallback before, ParamCallback after) {
|
||||||
|
return new YukiMemberHook() {
|
||||||
|
@Override
|
||||||
|
public void beforeHookedMember$yukihookapi_core_release(YukiHookCallback.Param param) {
|
||||||
|
if (before != null) before.accept(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterHookedMember$yukihookapi_core_release(YukiHookCallback.Param param) {
|
||||||
|
if (after != null) after.accept(param);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object hookMember(Member member, Object callback) {
|
||||||
|
return YukiHookHelper.INSTANCE.hookMember$yukihookapi_core_release(member, (YukiHookCallback) callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object invokeOriginal(Member member, Object instance, Object[] args) {
|
||||||
|
return YukiHookHelper.INSTANCE.invokeOriginalMember$yukihookapi_core_release(member, instance, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unhook(Object hookResult) {
|
||||||
|
YukiHookResult result = (YukiHookResult) hookResult;
|
||||||
|
result.getHookedMember().remove$yukihookapi_core_release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Member member(Object param) {
|
||||||
|
return ((YukiHookCallback.Param) param).getMember();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object instance(Object param) {
|
||||||
|
return ((YukiHookCallback.Param) param).getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object[] args(Object param) {
|
||||||
|
return ((YukiHookCallback.Param) param).getArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object result(Object param) {
|
||||||
|
return ((YukiHookCallback.Param) param).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setResult(Object param, Object result) {
|
||||||
|
((YukiHookCallback.Param) param).setResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Throwable throwable(Object param) {
|
||||||
|
return ((YukiHookCallback.Param) param).getThrowable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setThrowable(Object param, Throwable throwable) {
|
||||||
|
((YukiHookCallback.Param) param).setThrowable(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,13 +10,15 @@ import me.eternal.purrfectsnap.core.util.hook.HookStage
|
|||||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||||
|
|
||||||
class XposedLoader : IXposedHookLoadPackage {
|
class XposedLoader : IXposedHookLoadPackage {
|
||||||
override fun handleLoadPackage(p0: XC_LoadPackage.LoadPackageParam) {
|
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) {
|
||||||
if (p0.packageName != Constants.SNAPCHAT_PACKAGE_NAME) return
|
if (param.packageName != Constants.SNAPCHAT_PACKAGE_NAME) return
|
||||||
// prevent loading in sub-processes
|
// prevent loading in sub-processes
|
||||||
if (p0.processName.contains(":")) return
|
if (param.processName.contains(":")) return
|
||||||
XposedBridge.log("Loading PurrfectSnap v${BuildConfig.VERSION_NAME}#${BuildConfig.GIT_HASH} (package: ${BuildConfig.APPLICATION_ID})")
|
XposedBridge.log(
|
||||||
Application::class.java.hook("attach", HookStage.BEFORE) { param ->
|
"Loading PurrfectSnap v${BuildConfig.VERSION_NAME}#${BuildConfig.GIT_HASH} (package: ${BuildConfig.APPLICATION_ID})"
|
||||||
PurrfectSnap().init(param.arg(0))
|
)
|
||||||
|
Application::class.java.hook("attach", HookStage.BEFORE) { hookParam ->
|
||||||
|
PurrfectSnap().init(hookParam.arg(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.content.Intent
|
|||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.robv.android.xposed.XposedHelpers
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
@@ -106,17 +105,17 @@ class BridgeClient(
|
|||||||
this@BridgeClient
|
this@BridgeClient
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
XposedHelpers.callMethod(
|
val handler = Handler(HandlerThread("BridgeClient").apply { start() }.looper)
|
||||||
|
this::class.java.methods.firstOrNull {
|
||||||
|
it.name == "bindServiceAsUser" && it.parameterTypes.size == 5
|
||||||
|
}?.invoke(
|
||||||
this,
|
this,
|
||||||
"bindServiceAsUser",
|
|
||||||
intent,
|
intent,
|
||||||
this@BridgeClient,
|
this@BridgeClient,
|
||||||
Context.BIND_AUTO_CREATE,
|
Context.BIND_AUTO_CREATE,
|
||||||
Handler(HandlerThread("BridgeClient").apply {
|
handler,
|
||||||
start()
|
|
||||||
}.looper),
|
|
||||||
Process.myUserHandle()
|
Process.myUserHandle()
|
||||||
)
|
) ?: throw NoSuchMethodException("bindServiceAsUser")
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
onFailure(it)
|
onFailure(it)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package me.eternal.purrfectsnap.core.features.impl
|
package me.eternal.purrfectsnap.core.features.impl
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
|
||||||
import me.eternal.purrfectsnap.core.features.Feature
|
import me.eternal.purrfectsnap.core.features.Feature
|
||||||
|
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||||
@@ -93,8 +92,10 @@ class ConfigurationOverride : Feature("Configuration Override") {
|
|||||||
) { param ->
|
) { param ->
|
||||||
val enumData = param.arg<Any>(0)
|
val enumData = param.arg<Any>(0)
|
||||||
val key = enumData.toString()
|
val key = enumData.toString()
|
||||||
val setValue: (Any?) -> Unit = { value ->
|
val setValue: (Any?) -> Unit = setValue@{ value ->
|
||||||
val valueHolder = XposedHelpers.callMethod(enumData, configEnumMapping["getValue"]?.getAsString())
|
val valueHolder = enumData::class.java.methods.firstOrNull {
|
||||||
|
it.name == configEnumMapping["getValue"]?.getAsString() && it.parameterCount == 0
|
||||||
|
}?.invoke(enumData) ?: return@setValue
|
||||||
valueHolder.setObjectField(configEnumMapping["defaultValueField"]?.getAsString()!!, value)
|
valueHolder.setObjectField(configEnumMapping["defaultValueField"]?.getAsString()!!, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,4 +149,4 @@ class ConfigurationOverride : Feature("Configuration Override") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import android.graphics.BitmapFactory
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import de.robv.android.xposed.XposedBridge
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import me.eternal.purrfectsnap.common.data.ContentType
|
import me.eternal.purrfectsnap.common.data.ContentType
|
||||||
import me.eternal.purrfectsnap.common.data.FileType
|
import me.eternal.purrfectsnap.common.data.FileType
|
||||||
@@ -29,6 +28,7 @@ import me.eternal.purrfectsnap.core.features.impl.downloader.decoder.MessageDeco
|
|||||||
import me.eternal.purrfectsnap.core.features.impl.experiments.BetterTranscript
|
import me.eternal.purrfectsnap.core.features.impl.experiments.BetterTranscript
|
||||||
import me.eternal.purrfectsnap.core.features.impl.spying.StealthMode
|
import me.eternal.purrfectsnap.core.features.impl.spying.StealthMode
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||||
|
import me.eternal.purrfectsnap.core.util.hook.YukiHookCompat
|
||||||
import me.eternal.purrfectsnap.core.util.hook.findRestrictedConstructor
|
import me.eternal.purrfectsnap.core.util.hook.findRestrictedConstructor
|
||||||
import me.eternal.purrfectsnap.core.util.hook.findRestrictedMethod
|
import me.eternal.purrfectsnap.core.util.hook.findRestrictedMethod
|
||||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||||
@@ -48,7 +48,7 @@ class Notifications : Feature("Notifications") {
|
|||||||
val userHandle: UserHandle
|
val userHandle: UserHandle
|
||||||
) {
|
) {
|
||||||
fun send() {
|
fun send() {
|
||||||
XposedBridge.invokeOriginalMethod(notifyAsUserMethod, notificationManager, arrayOf(
|
YukiHookCompat.invokeOriginal(notifyAsUserMethod, notificationManager, arrayOf(
|
||||||
tag, id, notification, userHandle
|
tag, id, notification, userHandle
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import me.eternal.purrfectsnap.bridge.task.TaskListener
|
import me.eternal.purrfectsnap.bridge.task.TaskListener
|
||||||
import me.eternal.purrfectsnap.common.data.ContentType
|
import me.eternal.purrfectsnap.common.data.ContentType
|
||||||
@@ -40,7 +38,9 @@ import me.eternal.purrfectsnap.core.messaging.MessageSender
|
|||||||
import me.eternal.purrfectsnap.core.ui.PurrfectOverlayPalette
|
import me.eternal.purrfectsnap.core.ui.PurrfectOverlayPalette
|
||||||
import me.eternal.purrfectsnap.core.ui.PurrfectOverlayTheme
|
import me.eternal.purrfectsnap.core.ui.PurrfectOverlayTheme
|
||||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectFieldOrNull
|
import me.eternal.purrfectsnap.core.util.ktx.getObjectFieldOrNull
|
||||||
|
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||||
|
import me.eternal.purrfectsnap.core.util.hook.Hooker
|
||||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||||
import me.eternal.purrfectsnap.core.util.hook.hookConstructor
|
import me.eternal.purrfectsnap.core.util.hook.hookConstructor
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -65,7 +65,7 @@ class SendOverride : Feature("Send Override") {
|
|||||||
private var notificationIdCounter = 1000
|
private var notificationIdCounter = 1000
|
||||||
private val backgroundHookLock = Any()
|
private val backgroundHookLock = Any()
|
||||||
private var backgroundHookRefs = 0
|
private var backgroundHookRefs = 0
|
||||||
private var backgroundHooks: List<XC_MethodHook.Unhook>? = null
|
private var backgroundHooks: List<Hooker.HookHandle>? = null
|
||||||
|
|
||||||
private fun acquireScheduledSendBackground(): () -> Unit {
|
private fun acquireScheduledSendBackground(): () -> Unit {
|
||||||
if (!context.config.messaging.scheduledSendAllowRunningInBackground.get()) return {}
|
if (!context.config.messaging.scheduledSendAllowRunningInBackground.get()) return {}
|
||||||
@@ -98,7 +98,7 @@ class SendOverride : Feature("Send Override") {
|
|||||||
return runCatching {
|
return runCatching {
|
||||||
val duplexClass = findClass("com.snapchat.client.duplex.DuplexClient\$CppProxy")
|
val duplexClass = findClass("com.snapchat.client.duplex.DuplexClient\$CppProxy")
|
||||||
val appStateMethod = duplexClass.methods.firstOrNull { it.name == "appStateChanged" } ?: return false
|
val appStateMethod = duplexClass.methods.firstOrNull { it.name == "appStateChanged" } ?: return false
|
||||||
val hooks = mutableListOf<XC_MethodHook.Unhook>()
|
val hooks = mutableListOf<Hooker.HookHandle>()
|
||||||
hooks.addAll(
|
hooks.addAll(
|
||||||
duplexClass.hook("appStateChanged", HookStage.BEFORE) { param ->
|
duplexClass.hook("appStateChanged", HookStage.BEFORE) { param ->
|
||||||
if (param.arg<Any>(0).toString() == "INACTIVE") param.setResult(null)
|
if (param.arg<Any>(0).toString() == "INACTIVE") param.setResult(null)
|
||||||
@@ -493,8 +493,11 @@ class SendOverride : Feature("Send Override") {
|
|||||||
if (shouldPreventSave) {
|
if (shouldPreventSave) {
|
||||||
try {
|
try {
|
||||||
val savePolicyEnumClass = runCatching {
|
val savePolicyEnumClass = runCatching {
|
||||||
XposedHelpers.findClass("com.snapchat.client.messaging.SavePolicy",
|
Class.forName(
|
||||||
localMessageContent.instanceNonNull().javaClass.classLoader)
|
"com.snapchat.client.messaging.SavePolicy",
|
||||||
|
false,
|
||||||
|
localMessageContent.instanceNonNull().javaClass.classLoader
|
||||||
|
)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
if (savePolicyEnumClass != null && savePolicyEnumClass.isEnum) {
|
if (savePolicyEnumClass != null && savePolicyEnumClass.isEnum) {
|
||||||
@@ -510,7 +513,7 @@ class SendOverride : Feature("Send Override") {
|
|||||||
|
|
||||||
if (savePolicyField != null) {
|
if (savePolicyField != null) {
|
||||||
savePolicyField.isAccessible = true
|
savePolicyField.isAccessible = true
|
||||||
XposedHelpers.setObjectField(localMessageContent.instanceNonNull(), "mSavePolicy", prohibitedEnum)
|
localMessageContent.instanceNonNull().setObjectField("mSavePolicy", prohibitedEnum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package me.eternal.purrfectsnap.core.features.impl.tweaks
|
package me.eternal.purrfectsnap.core.features.impl.tweaks
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
|
||||||
import me.eternal.purrfectsnap.common.data.ContentType
|
import me.eternal.purrfectsnap.common.data.ContentType
|
||||||
import me.eternal.purrfectsnap.common.data.MessagingRuleType
|
import me.eternal.purrfectsnap.common.data.MessagingRuleType
|
||||||
import me.eternal.purrfectsnap.common.util.protobuf.ProtoEditor
|
import me.eternal.purrfectsnap.common.util.protobuf.ProtoEditor
|
||||||
@@ -8,6 +7,7 @@ import me.eternal.purrfectsnap.common.util.protobuf.ProtoReader
|
|||||||
import me.eternal.purrfectsnap.core.event.events.impl.NativeUnaryCallEvent
|
import me.eternal.purrfectsnap.core.event.events.impl.NativeUnaryCallEvent
|
||||||
import me.eternal.purrfectsnap.core.event.events.impl.SendMessageWithContentEvent
|
import me.eternal.purrfectsnap.core.event.events.impl.SendMessageWithContentEvent
|
||||||
import me.eternal.purrfectsnap.core.features.MessagingRuleFeature
|
import me.eternal.purrfectsnap.core.features.MessagingRuleFeature
|
||||||
|
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||||
|
|
||||||
class UnsaveableMessages : MessagingRuleFeature(
|
class UnsaveableMessages : MessagingRuleFeature(
|
||||||
"Unsaveable Messages",
|
"Unsaveable Messages",
|
||||||
@@ -55,8 +55,9 @@ class UnsaveableMessages : MessagingRuleFeature(
|
|||||||
|
|
||||||
// Set mSavePolicy on Java object
|
// Set mSavePolicy on Java object
|
||||||
try {
|
try {
|
||||||
val savePolicyEnumClass = XposedHelpers.findClass(
|
val savePolicyEnumClass = Class.forName(
|
||||||
"com.snapchat.client.messaging.SavePolicy",
|
"com.snapchat.client.messaging.SavePolicy",
|
||||||
|
false,
|
||||||
localMessageContent.instanceNonNull().javaClass.classLoader
|
localMessageContent.instanceNonNull().javaClass.classLoader
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ class UnsaveableMessages : MessagingRuleFeature(
|
|||||||
savePolicyEnumClass as Class<out Enum<*>>,
|
savePolicyEnumClass as Class<out Enum<*>>,
|
||||||
"PROHIBITED"
|
"PROHIBITED"
|
||||||
)
|
)
|
||||||
XposedHelpers.setObjectField(localMessageContent.instanceNonNull(), "mSavePolicy", prohibitedEnum)
|
localMessageContent.instanceNonNull().setObjectField("mSavePolicy", prohibitedEnum)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
context.log.warn("UnsaveableMessages: Failed to set mSavePolicy: ${e.message}")
|
context.log.warn("UnsaveableMessages: Failed to set mSavePolicy: ${e.message}")
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package me.eternal.purrfectsnap.core.logger
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.robv.android.xposed.XposedBridge
|
|
||||||
import me.eternal.purrfectsnap.common.logger.AbstractLogger
|
import me.eternal.purrfectsnap.common.logger.AbstractLogger
|
||||||
import me.eternal.purrfectsnap.common.logger.LogChannel
|
import me.eternal.purrfectsnap.common.logger.LogChannel
|
||||||
import me.eternal.purrfectsnap.common.logger.LogLevel
|
import me.eternal.purrfectsnap.common.logger.LogLevel
|
||||||
import me.eternal.purrfectsnap.core.bridge.BridgeClient
|
import me.eternal.purrfectsnap.core.bridge.BridgeClient
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||||
|
import me.eternal.purrfectsnap.core.util.hook.YukiHookCompat
|
||||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
@SuppressLint("PrivateApi")
|
||||||
@@ -20,13 +20,12 @@ class CoreLogger(
|
|||||||
|
|
||||||
fun xposedLog(message: Any?, tag: String = TAG) {
|
fun xposedLog(message: Any?, tag: String = TAG) {
|
||||||
Log.println(Log.INFO, tag, message.toString())
|
Log.println(Log.INFO, tag, message.toString())
|
||||||
XposedBridge.log("$tag: $message")
|
Log.i(tag, message.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun xposedLog(message: Any?, throwable: Throwable, tag: String = TAG) {
|
fun xposedLog(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||||
Log.println(Log.INFO, tag, message.toString())
|
Log.println(Log.INFO, tag, message.toString())
|
||||||
XposedBridge.log("$tag: $message")
|
Log.i(tag, message.toString(), throwable)
|
||||||
XposedBridge.log(throwable)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ class CoreLogger(
|
|||||||
internalLog(tag, LogLevel.fromPriority(priority) ?: LogLevel.INFO, message)
|
internalLog(tag, LogLevel.fromPriority(priority) ?: LogLevel.INFO, message)
|
||||||
}
|
}
|
||||||
invokeOriginalPrintLog = { priority, tag, message ->
|
invokeOriginalPrintLog = { priority, tag, message ->
|
||||||
XposedBridge.invokeOriginalMethod(
|
YukiHookCompat.invokeOriginal(
|
||||||
printLnMethod,
|
printLnMethod,
|
||||||
null,
|
null,
|
||||||
arrayOf<Any?>(priority, tag, message)
|
arrayOf<Any?>(priority, tag, message)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package me.eternal.purrfectsnap.core.util
|
package me.eternal.purrfectsnap.core.util
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookAdapter
|
import me.eternal.purrfectsnap.core.util.hook.HookAdapter
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||||
import me.eternal.purrfectsnap.core.util.hook.Hooker
|
import me.eternal.purrfectsnap.core.util.hook.Hooker
|
||||||
@@ -39,7 +38,7 @@ class CallbackBuilder(
|
|||||||
val callbackInstance = createEmptyObject(callbackClass.constructors.first())!!
|
val callbackInstance = createEmptyObject(callbackClass.constructors.first())!!
|
||||||
val callbackInstanceHashCode: Int = callbackInstance.hashCode()
|
val callbackInstanceHashCode: Int = callbackInstance.hashCode()
|
||||||
val callbackInstanceClass = callbackInstance.javaClass
|
val callbackInstanceClass = callbackInstance.javaClass
|
||||||
val unhooks = mutableListOf<XC_MethodHook.Unhook>()
|
val unhooks = mutableListOf<Hooker.HookHandle>()
|
||||||
callbackInstanceClass.methods.forEach { method ->
|
callbackInstanceClass.methods.forEach { method ->
|
||||||
if (method.declaringClass != callbackInstanceClass) return@forEach
|
if (method.declaringClass != callbackInstanceClass) return@forEach
|
||||||
if (Modifier.isPrivate(method.modifiers)) return@forEach
|
if (Modifier.isPrivate(method.modifiers)) return@forEach
|
||||||
|
|||||||
@@ -1,69 +1,68 @@
|
|||||||
package me.eternal.purrfectsnap.core.util.hook
|
package me.eternal.purrfectsnap.core.util.hook
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
|
||||||
import de.robv.android.xposed.XposedBridge
|
|
||||||
import java.lang.reflect.Member
|
import java.lang.reflect.Member
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class HookAdapter(
|
class HookAdapter(
|
||||||
private val methodHookParam: XC_MethodHook.MethodHookParam<*>
|
private val methodHookParam: Any
|
||||||
) {
|
) {
|
||||||
fun <T : Any> thisObject(): T {
|
fun <T : Any> thisObject(): T {
|
||||||
return methodHookParam.thisObject as T
|
return YukiHookBridge.instance(methodHookParam) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> nullableThisObject(): T? {
|
fun <T : Any> nullableThisObject(): T? {
|
||||||
return methodHookParam.thisObject as T?
|
return YukiHookBridge.instance(methodHookParam) as T?
|
||||||
}
|
}
|
||||||
|
|
||||||
fun method(): Member {
|
fun method(): Member {
|
||||||
return methodHookParam.method
|
return YukiHookBridge.member(methodHookParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> arg(index: Int): T {
|
fun <T : Any> arg(index: Int): T {
|
||||||
return methodHookParam.args[index] as T
|
return YukiHookBridge.args(methodHookParam)[index] as T
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> argNullable(index: Int): T? {
|
fun <T : Any> argNullable(index: Int): T? {
|
||||||
return methodHookParam.args.getOrNull(index) as T?
|
return YukiHookBridge.args(methodHookParam).getOrNull(index) as T?
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setArg(index: Int, value: Any?) {
|
fun setArg(index: Int, value: Any?) {
|
||||||
if (index < 0 || index >= methodHookParam.args.size) return
|
val args = YukiHookBridge.args(methodHookParam)
|
||||||
methodHookParam.args[index] = value
|
if (index < 0 || index >= args.size) return
|
||||||
|
args[index] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun args(): Array<Any?> {
|
fun args(): Array<Any?> {
|
||||||
return methodHookParam.args
|
return YukiHookBridge.args(methodHookParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getResult(): Any? {
|
fun getResult(): Any? {
|
||||||
return methodHookParam.result
|
return YukiHookBridge.result(methodHookParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setResult(result: Any?) {
|
fun setResult(result: Any?) {
|
||||||
methodHookParam.result = result
|
YukiHookBridge.setResult(methodHookParam, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setThrowable(throwable: Throwable) {
|
fun setThrowable(throwable: Throwable) {
|
||||||
methodHookParam.throwable = throwable
|
YukiHookBridge.setThrowable(methodHookParam, throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearThrowable() {
|
fun clearThrowable() {
|
||||||
methodHookParam.throwable = null
|
YukiHookBridge.setThrowable(methodHookParam, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun throwable(): Throwable? {
|
fun throwable(): Throwable? {
|
||||||
return methodHookParam.throwable
|
return YukiHookBridge.throwable(methodHookParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOriginal(): Any? {
|
fun invokeOriginal(): Any? {
|
||||||
return XposedBridge.invokeOriginalMethod(method(), thisObject(), args())
|
return YukiHookCompat.invokeOriginal(method(), nullableThisObject<Any>(), args())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOriginal(args: Array<Any?>): Any? {
|
fun invokeOriginal(args: Array<Any?>): Any? {
|
||||||
return XposedBridge.invokeOriginalMethod(method(), thisObject(), args)
|
return YukiHookCompat.invokeOriginal(method(), nullableThisObject<Any>(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeOriginalSafe(errorCallback: Consumer<Throwable>) {
|
fun invokeOriginalSafe(errorCallback: Consumer<Throwable>) {
|
||||||
@@ -72,7 +71,7 @@ class HookAdapter(
|
|||||||
|
|
||||||
fun invokeOriginalSafe(args: Array<Any?>, errorCallback: Consumer<Throwable>) {
|
fun invokeOriginalSafe(args: Array<Any?>, errorCallback: Consumer<Throwable>) {
|
||||||
runCatching {
|
runCatching {
|
||||||
setResult(XposedBridge.invokeOriginalMethod(method(), thisObject(), args))
|
setResult(YukiHookCompat.invokeOriginal(method(), nullableThisObject<Any>(), args))
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
errorCallback.accept(it)
|
errorCallback.accept(it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package me.eternal.purrfectsnap.core.util.hook
|
||||||
|
|
||||||
|
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||||
|
|
||||||
|
object HookRuntime {
|
||||||
|
@Volatile
|
||||||
|
private var currentPackageParam: PackageParam? = null
|
||||||
|
|
||||||
|
fun attach(packageParam: PackageParam) {
|
||||||
|
currentPackageParam = packageParam
|
||||||
|
}
|
||||||
|
|
||||||
|
fun packageParam(): PackageParam {
|
||||||
|
return currentPackageParam ?: error("Yuki PackageParam is not initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,43 @@
|
|||||||
package me.eternal.purrfectsnap.core.util.hook
|
package me.eternal.purrfectsnap.core.util.hook
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
|
||||||
import de.robv.android.xposed.XposedBridge
|
|
||||||
import me.eternal.purrfectsnap.common.logger.AbstractLogger
|
import me.eternal.purrfectsnap.common.logger.AbstractLogger
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Member
|
import java.lang.reflect.Member
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
import java.lang.reflect.AccessibleObject
|
||||||
|
|
||||||
object Hooker {
|
object Hooker {
|
||||||
|
class HookHandle(private val unhooker: () -> Unit) {
|
||||||
|
fun unhook() = unhooker()
|
||||||
|
}
|
||||||
|
|
||||||
inline fun newMethodHook(
|
inline fun newMethodHook(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: (HookAdapter) -> Unit,
|
crossinline consumer: (HookAdapter) -> Unit,
|
||||||
crossinline filter: ((HookAdapter) -> Boolean) = { true }
|
crossinline filter: ((HookAdapter) -> Boolean) = { true }
|
||||||
): XC_MethodHook {
|
): Any {
|
||||||
return if (stage == HookStage.BEFORE) object : XC_MethodHook() {
|
return YukiHookBridge.newMemberHook(
|
||||||
override fun beforeHookedMethod(param: MethodHookParam<*>) {
|
{ param ->
|
||||||
runCatching {
|
if (stage == HookStage.BEFORE) {
|
||||||
HookAdapter(param).takeIf(filter)?.also(consumer)
|
runCatching {
|
||||||
}.onFailure {
|
HookAdapter(param).takeIf(filter)?.also(consumer)
|
||||||
AbstractLogger.directError("Failed to execute before hook", it)
|
}.onFailure {
|
||||||
|
AbstractLogger.directError("Failed to execute before hook", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ param ->
|
||||||
|
if (stage == HookStage.AFTER) {
|
||||||
|
runCatching {
|
||||||
|
HookAdapter(param).takeIf(filter)?.also(consumer)
|
||||||
|
}.onFailure {
|
||||||
|
AbstractLogger.directError("Failed to execute after hook", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else object : XC_MethodHook() {
|
)
|
||||||
override fun afterHookedMethod(param: MethodHookParam<*>) {
|
|
||||||
runCatching {
|
|
||||||
HookAdapter(param).takeIf(filter)?.also(consumer)
|
|
||||||
}.onFailure {
|
|
||||||
AbstractLogger.directError("Failed to execute after hook", it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun hook(
|
inline fun hook(
|
||||||
@@ -40,15 +46,24 @@ object Hooker {
|
|||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline filter: (HookAdapter) -> Boolean,
|
crossinline filter: (HookAdapter) -> Boolean,
|
||||||
noinline consumer: (HookAdapter) -> Unit
|
noinline consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = XposedBridge.hookAllMethods(clazz, methodName, newMethodHook(stage, consumer, filter))
|
): Set<HookHandle> = clazz.declaredMethods
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.name == methodName }
|
||||||
|
.map { method ->
|
||||||
|
method.isAccessible = true
|
||||||
|
val hookResult = YukiHookCompat.hookMember(method, newMethodHook(stage, consumer, filter))
|
||||||
|
HookHandle { YukiHookCompat.unhook(hookResult) }
|
||||||
|
}.toMutableSet()
|
||||||
|
|
||||||
inline fun hook(
|
inline fun hook(
|
||||||
member: Member,
|
member: Member,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline filter: ((HookAdapter) -> Boolean),
|
crossinline filter: ((HookAdapter) -> Boolean),
|
||||||
crossinline consumer: (HookAdapter) -> Unit
|
crossinline consumer: (HookAdapter) -> Unit
|
||||||
): XC_MethodHook.Unhook {
|
): HookHandle {
|
||||||
return XposedBridge.hookMethod(member, newMethodHook(stage, consumer, filter))
|
(member as? AccessibleObject)?.isAccessible = true
|
||||||
|
val hookResult = YukiHookCompat.hookMember(member, newMethodHook(stage, consumer, filter))
|
||||||
|
return HookHandle { YukiHookCompat.unhook(hookResult) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hook(
|
fun hook(
|
||||||
@@ -56,13 +71,13 @@ object Hooker {
|
|||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = hook(clazz, methodName, stage, { true }, consumer)
|
): Set<HookHandle> = hook(clazz, methodName, stage, { true }, consumer)
|
||||||
|
|
||||||
fun hook(
|
fun hook(
|
||||||
member: Member,
|
member: Member,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): XC_MethodHook.Unhook {
|
): HookHandle {
|
||||||
return hook(member, stage, { true }, consumer)
|
return hook(member, stage, { true }, consumer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +85,13 @@ object Hooker {
|
|||||||
clazz: Class<*>,
|
clazz: Class<*>,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer))
|
): Set<HookHandle> = clazz.declaredConstructors
|
||||||
|
.asSequence()
|
||||||
|
.map { constructor ->
|
||||||
|
constructor.isAccessible = true
|
||||||
|
val hookResult = YukiHookCompat.hookMember(constructor, newMethodHook(stage, consumer))
|
||||||
|
HookHandle { YukiHookCompat.unhook(hookResult) }
|
||||||
|
}.toMutableSet()
|
||||||
|
|
||||||
fun hookConstructor(
|
fun hookConstructor(
|
||||||
clazz: Class<*>,
|
clazz: Class<*>,
|
||||||
@@ -78,7 +99,10 @@ object Hooker {
|
|||||||
filter: ((HookAdapter) -> Boolean),
|
filter: ((HookAdapter) -> Boolean),
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer, filter))
|
clazz.declaredConstructors.forEach { constructor ->
|
||||||
|
constructor.isAccessible = true
|
||||||
|
YukiHookCompat.hookMember(constructor, newMethodHook(stage, consumer, filter))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun hookObjectMethod(
|
inline fun hookObjectMethod(
|
||||||
@@ -88,7 +112,7 @@ object Hooker {
|
|||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: (HookAdapter) -> Unit
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
): List<() -> Unit> {
|
): List<() -> Unit> {
|
||||||
val unhooks = mutableSetOf<XC_MethodHook.Unhook>()
|
val unhooks = mutableSetOf<HookHandle>()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
if (param.nullableThisObject<Any>().let {
|
if (param.nullableThisObject<Any>().let {
|
||||||
if (it == null) unhooks.forEach { u -> u.unhook() }
|
if (it == null) unhooks.forEach { u -> u.unhook() }
|
||||||
@@ -107,7 +131,7 @@ object Hooker {
|
|||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: (HookAdapter) -> Unit
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
val unhooks: MutableSet<HookHandle> = HashSet()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
hookConsumer(param)
|
hookConsumer(param)
|
||||||
unhooks.forEach{ it.unhook() }
|
unhooks.forEach{ it.unhook() }
|
||||||
@@ -121,7 +145,7 @@ object Hooker {
|
|||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: (HookAdapter) -> Unit
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
): Set<() -> Unit> {
|
): Set<() -> Unit> {
|
||||||
val unhooks = mutableSetOf<XC_MethodHook.Unhook>()
|
val unhooks = mutableSetOf<HookHandle>()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
if (param.nullableThisObject<Any>() != instance) return@hook
|
if (param.nullableThisObject<Any>() != instance) return@hook
|
||||||
unhooks.forEach { it.unhook() }
|
unhooks.forEach { it.unhook() }
|
||||||
@@ -137,7 +161,7 @@ object Hooker {
|
|||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: (HookAdapter) -> Unit
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
val unhooks: MutableSet<HookHandle> = HashSet()
|
||||||
hookConstructor(clazz, stage) { param->
|
hookConstructor(clazz, stage) { param->
|
||||||
hookConsumer(param)
|
hookConsumer(param)
|
||||||
unhooks.forEach{ it.unhook() }
|
unhooks.forEach{ it.unhook() }
|
||||||
@@ -160,25 +184,25 @@ fun Class<*>.hook(
|
|||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, consumer)
|
): Set<Hooker.HookHandle> = Hooker.hook(this, methodName, stage, consumer)
|
||||||
|
|
||||||
fun Class<*>.hook(
|
fun Class<*>.hook(
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
filter: (HookAdapter) -> Boolean,
|
filter: (HookAdapter) -> Boolean,
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, filter, consumer)
|
): Set<Hooker.HookHandle> = Hooker.hook(this, methodName, stage, filter, consumer)
|
||||||
|
|
||||||
fun Member.hook(
|
fun Member.hook(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): XC_MethodHook.Unhook = Hooker.hook(this, stage, consumer)
|
): Hooker.HookHandle = Hooker.hook(this, stage, consumer)
|
||||||
|
|
||||||
fun Member.hook(
|
fun Member.hook(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
filter: ((HookAdapter) -> Boolean),
|
filter: ((HookAdapter) -> Boolean),
|
||||||
consumer: (HookAdapter) -> Unit
|
consumer: (HookAdapter) -> Unit
|
||||||
): XC_MethodHook.Unhook = Hooker.hook(this, stage, filter, consumer)
|
): Hooker.HookHandle = Hooker.hook(this, stage, filter, consumer)
|
||||||
|
|
||||||
fun Array<Method>.hookAll(stage: HookStage, param: (HookAdapter) -> Unit) {
|
fun Array<Method>.hookAll(stage: HookStage, param: (HookAdapter) -> Unit) {
|
||||||
filter { it.declaringClass != Object::class.java && !Modifier.isAbstract(it.modifiers) }.forEach {
|
filter { it.declaringClass != Object::class.java && !Modifier.isAbstract(it.modifiers) }.forEach {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package me.eternal.purrfectsnap.core.util.hook
|
||||||
|
|
||||||
|
import java.lang.reflect.Member
|
||||||
|
|
||||||
|
object YukiHookCompat {
|
||||||
|
fun hookMember(member: Member, callback: Any): Any {
|
||||||
|
return YukiHookBridge.hookMember(member, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invokeOriginal(member: Member, instance: Any?, args: Array<Any?>): Any? {
|
||||||
|
return YukiHookBridge.invokeOriginal(member, instance, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unhook(result: Any) {
|
||||||
|
YukiHookBridge.unhook(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,19 @@
|
|||||||
package me.eternal.purrfectsnap.core.util.ktx
|
package me.eternal.purrfectsnap.core.util.ktx
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
private fun findDeclaredFieldRecursive(type: Class<*>, fieldName: String): java.lang.reflect.Field? {
|
||||||
|
var current: Class<*>? = type
|
||||||
|
while (current != null && current != Any::class.java) {
|
||||||
|
current.declaredFields.firstOrNull { it.name == fieldName }?.let { return it }
|
||||||
|
current = current.superclass
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun Any.getObjectField(fieldName: String): Any? {
|
fun Any.getObjectField(fieldName: String): Any? {
|
||||||
return XposedHelpers.getObjectField(this, fieldName)
|
val field = findDeclaredFieldRecursive(this::class.java, fieldName)
|
||||||
|
?: throw NoSuchFieldException("${this::class.java.name}#$fieldName")
|
||||||
|
field.isAccessible = true
|
||||||
|
return field.get(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Any.setEnumField(fieldName: String, value: String) {
|
fun Any.setEnumField(fieldName: String, value: String) {
|
||||||
@@ -14,7 +24,10 @@ fun Any.setEnumField(fieldName: String, value: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Any.setObjectField(fieldName: String, value: Any?) {
|
fun Any.setObjectField(fieldName: String, value: Any?) {
|
||||||
XposedHelpers.setObjectField(this, fieldName, value)
|
val field = findDeclaredFieldRecursive(this::class.java, fieldName)
|
||||||
|
?: throw NoSuchFieldException("${this::class.java.name}#$fieldName")
|
||||||
|
field.isAccessible = true
|
||||||
|
field.set(this, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Any.getObjectFieldOrNull(fieldName: String): Any? {
|
fun Any.getObjectFieldOrNull(fieldName: String): Any? {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package me.eternal.purrfectsnap.core.wrapper
|
package me.eternal.purrfectsnap.core.wrapper
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
|
||||||
import me.eternal.purrfectsnap.core.util.CallbackBuilder
|
import me.eternal.purrfectsnap.core.util.CallbackBuilder
|
||||||
|
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||||
|
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||||
import me.eternal.purrfectsnap.core.wrapper.impl.SnapUUID
|
import me.eternal.purrfectsnap.core.wrapper.impl.SnapUUID
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
@@ -20,13 +21,13 @@ abstract class AbstractWrapper(
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
operator fun getValue(obj: Any, property: KProperty<*>): T? {
|
operator fun getValue(obj: Any, property: KProperty<*>): T? {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val value = XposedHelpers.getObjectField(instance, fieldName)
|
val value = instance?.getObjectField(fieldName)
|
||||||
mapper?.invoke(value) ?: value as? T
|
mapper?.invoke(value) ?: value as? T
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun setValue(obj: Any, property: KProperty<*>, value: Any?) {
|
operator fun setValue(obj: Any, property: KProperty<*>, value: Any?) {
|
||||||
XposedHelpers.setObjectField(instance, fieldName, when (value) {
|
instance?.setObjectField(fieldName, when (value) {
|
||||||
is AbstractWrapper -> value.instance
|
is AbstractWrapper -> value.instance
|
||||||
is ArrayList<*> -> value.map { if (it is AbstractWrapper) it.instance else it }.toMutableList()
|
is ArrayList<*> -> value.map { if (it is AbstractWrapper) it.instance else it }.toMutableList()
|
||||||
else -> value
|
else -> value
|
||||||
@@ -56,13 +57,13 @@ abstract class AbstractWrapper(
|
|||||||
|
|
||||||
fun <T : Enum<*>> getEnumValue(fieldName: String, defaultValue: T?): T? {
|
fun <T : Enum<*>> getEnumValue(fieldName: String, defaultValue: T?): T? {
|
||||||
if (defaultValue == null || instance == null) return null
|
if (defaultValue == null || instance == null) return null
|
||||||
val mContentType = XposedHelpers.getObjectField(instance, fieldName) as? Enum<*> ?: return null
|
val mContentType = instance?.getObjectField(fieldName) as? Enum<*> ?: return null
|
||||||
return java.lang.Enum.valueOf(defaultValue::class.java, mContentType.name)
|
return java.lang.Enum.valueOf(defaultValue::class.java, mContentType.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun setEnumValue(fieldName: String, value: Enum<*>) {
|
fun setEnumValue(fieldName: String, value: Enum<*>) {
|
||||||
val type = instance!!.javaClass.declaredFields.find { it.name == fieldName }?.type as Class<out Enum<*>>
|
val type = instance!!.javaClass.declaredFields.find { it.name == fieldName }?.type as Class<out Enum<*>>
|
||||||
XposedHelpers.setObjectField(instance, fieldName, java.lang.Enum.valueOf(type, value.name))
|
instance?.setObjectField(fieldName, java.lang.Enum.valueOf(type, value.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package me.eternal.purrfectsnap.core.wrapper.impl.valdi
|
package me.eternal.purrfectsnap.core.wrapper.impl.valdi
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.lang.reflect.Proxy
|
import java.lang.reflect.Proxy
|
||||||
@@ -12,7 +12,7 @@ class ValdiContext(obj: Any): AbstractWrapper(obj) {
|
|||||||
val componentContext by field<WeakReference<Any?>>("componentContext")
|
val componentContext by field<WeakReference<Any?>>("componentContext")
|
||||||
|
|
||||||
val viewModelLegacy: Any?
|
val viewModelLegacy: Any?
|
||||||
get() = runCatching { XposedHelpers.getObjectField(instanceNonNull(), "viewModel") }.getOrNull()
|
get() = runCatching { instanceNonNull().getObjectField("viewModel") }.getOrNull()
|
||||||
?: instanceNonNull()::class.java.methods.firstOrNull { it.name == "getViewModel" && it.parameterTypes.isEmpty() }?.invoke(instanceNonNull())
|
?: instanceNonNull()::class.java.methods.firstOrNull { it.name == "getViewModel" && it.parameterTypes.isEmpty() }?.invoke(instanceNonNull())
|
||||||
|
|
||||||
fun enqueueNextRenderCallback(callback: () -> Unit) {
|
fun enqueueNextRenderCallback(callback: () -> Unit) {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ hiddenapibypass = "6.1"
|
|||||||
smart-exception-java = "0.2.1"
|
smart-exception-java = "0.2.1"
|
||||||
workmanager = "2.10.4"
|
workmanager = "2.10.4"
|
||||||
fetch = "3.4.1"
|
fetch = "3.4.1"
|
||||||
|
yukihookapi = "1.3.1"
|
||||||
|
kavaref = "1.0.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
|
accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
|
||||||
@@ -66,6 +68,9 @@ recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version
|
|||||||
rhino = { module = "org.mozilla:rhino", version.ref = "rhino" }
|
rhino = { module = "org.mozilla:rhino", version.ref = "rhino" }
|
||||||
rhino-android = { group = "com.faendir.rhino", name = "rhino-android", version.ref = "rhino-android" }
|
rhino-android = { group = "com.faendir.rhino", name = "rhino-android", version.ref = "rhino-android" }
|
||||||
smart-exception-java = { module = "com.arthenica:smart-exception-java", version.ref = "smart-exception-java" }
|
smart-exception-java = { module = "com.arthenica:smart-exception-java", version.ref = "smart-exception-java" }
|
||||||
|
yukihookapi-api = { module = "com.highcapable.yukihookapi:api", version.ref = "yukihookapi" }
|
||||||
|
kavaref-core = { module = "com.highcapable.kavaref:kavaref-core", version.ref = "kavaref" }
|
||||||
|
kavaref-extension = { module = "com.highcapable.kavaref:kavaref-extension", version.ref = "kavaref" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url = uri("https://api.xposed.info/") }
|
||||||
maven { url = uri("https://jitpack.io") }
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user