kavaref migration
This commit is contained in:
@@ -46,6 +46,8 @@ dependencies {
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.androidx.documentfile)
|
||||
implementation(libs.rhino)
|
||||
implementation(libs.kavaref.core)
|
||||
implementation(libs.kavaref.extension)
|
||||
implementation(libs.rhino.android) {
|
||||
exclude(group = "org.mozilla", module = "rhino-runtime")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package me.eternal.purrfectsnap.common.util.ktx;
|
||||
|
||||
import com.highcapable.kavaref.KavaRef;
|
||||
import com.highcapable.kavaref.condition.FieldCondition;
|
||||
import com.highcapable.kavaref.resolver.FieldResolver;
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class KavaRefFieldBridge {
|
||||
private KavaRefFieldBridge() {}
|
||||
|
||||
private static FieldResolver<Object> resolveField(Object instance, String fieldName) {
|
||||
Class<?> current = instance.getClass();
|
||||
while (current != null && current != Object.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) current);
|
||||
FieldResolver<Object> resolver = scope.firstFieldOrNull(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
condition.name(fieldName);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
if (resolver != null) return resolver.of(instance);
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getField(Object instance, String fieldName) throws NoSuchFieldException {
|
||||
FieldResolver<Object> resolver = resolveField(instance, fieldName);
|
||||
if (resolver == null) throw new NoSuchFieldException(instance.getClass().getName() + "#" + fieldName);
|
||||
return resolver.get();
|
||||
}
|
||||
|
||||
public static void setField(Object instance, String fieldName, Object value) throws NoSuchFieldException {
|
||||
FieldResolver<Object> resolver = resolveField(instance, fieldName);
|
||||
if (resolver == null) throw new NoSuchFieldException(instance.getClass().getName() + "#" + fieldName);
|
||||
resolver.set(value);
|
||||
}
|
||||
|
||||
public static String[] getAllFieldNames(Object instance) {
|
||||
List<String> result = new ArrayList<>();
|
||||
Class<?> current = instance.getClass();
|
||||
while (current != null && current != Object.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) current);
|
||||
List<FieldResolver<Object>> fields = scope.field(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
for (FieldResolver<Object> field : fields) result.add(field.getSelf().getName());
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import me.eternal.purrfectsnap.common.scripting.bindings.BindingsContext
|
||||
import me.eternal.purrfectsnap.common.scripting.impl.JavaInterfaces
|
||||
import me.eternal.purrfectsnap.common.scripting.impl.Networking
|
||||
import me.eternal.purrfectsnap.common.scripting.impl.Protobuf
|
||||
import me.eternal.purrfectsnap.common.util.ktx.KavaRefFieldBridge
|
||||
import me.eternal.purrfectsnap.common.scripting.ktx.contextScope
|
||||
import me.eternal.purrfectsnap.common.scripting.ktx.putFunction
|
||||
import me.eternal.purrfectsnap.common.scripting.ktx.scriptable
|
||||
@@ -81,18 +82,18 @@ class JSModule(
|
||||
val obj = args?.get(0) ?: return@putFunction Undefined.instance
|
||||
val name = args[1].toString()
|
||||
val value = args[2]
|
||||
val field = obj.javaClass.declaredFields.find { it.name == name } ?: return@putFunction Undefined.instance
|
||||
field.isAccessible = true
|
||||
field.set(obj, value.toPrimitiveValue(lazy { field.type.name }))
|
||||
runCatching {
|
||||
KavaRefFieldBridge.getField(obj, name)?.javaClass?.name
|
||||
}.getOrNull()?.let { typeName ->
|
||||
KavaRefFieldBridge.setField(obj, name, value.toPrimitiveValue(lazy { typeName }))
|
||||
} ?: return@putFunction Undefined.instance
|
||||
Undefined.instance
|
||||
}
|
||||
|
||||
moduleObject.putFunction("getField") { args ->
|
||||
val obj = args?.get(0) ?: return@putFunction Undefined.instance
|
||||
val name = args[1].toString()
|
||||
val field = obj.javaClass.declaredFields.find { it.name == name } ?: return@putFunction Undefined.instance
|
||||
field.isAccessible = true
|
||||
field.get(obj)
|
||||
runCatching { KavaRefFieldBridge.getField(obj, name) }.getOrNull() ?: Undefined.instance
|
||||
}
|
||||
|
||||
moduleObject.putFunction("sleep") { args ->
|
||||
@@ -141,9 +142,14 @@ class JSModule(
|
||||
}
|
||||
}
|
||||
|
||||
// Expose static fields if they can be read through class reflection.
|
||||
clazz.declaredFields.filter { Modifier.isStatic(it.modifiers) }.forEach { field ->
|
||||
field.isAccessible = true
|
||||
defineProperty(field.name, { field.get(null) }, { value -> field.set(null, value) }, 0)
|
||||
defineProperty(
|
||||
field.name,
|
||||
{ runCatching { field.get(null) }.getOrNull() },
|
||||
{ value -> runCatching { field.set(null, value) } },
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
if (get("newInstance") == null) {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package me.eternal.purrfectsnap.core.util.ktx;
|
||||
|
||||
import com.highcapable.kavaref.KavaRef;
|
||||
import com.highcapable.kavaref.condition.FieldCondition;
|
||||
import com.highcapable.kavaref.resolver.FieldResolver;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class KavaRefFieldBridge {
|
||||
private KavaRefFieldBridge() {}
|
||||
|
||||
private static FieldResolver<Object> resolveField(Object instance, String fieldName) {
|
||||
Class<?> current = instance.getClass();
|
||||
while (current != null && current != Object.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) current);
|
||||
FieldResolver<Object> resolver = scope.firstFieldOrNull(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
condition.name(fieldName);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
if (resolver != null) {
|
||||
return resolver.of(instance);
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getField(Object instance, String fieldName) throws NoSuchFieldException {
|
||||
FieldResolver<Object> resolver = resolveField(instance, fieldName);
|
||||
if (resolver == null) throw new NoSuchFieldException(instance.getClass().getName() + "#" + fieldName);
|
||||
return resolver.get();
|
||||
}
|
||||
|
||||
public static void setField(Object instance, String fieldName, Object value) throws NoSuchFieldException {
|
||||
FieldResolver<Object> resolver = resolveField(instance, fieldName);
|
||||
if (resolver == null) throw new NoSuchFieldException(instance.getClass().getName() + "#" + fieldName);
|
||||
resolver.set(value);
|
||||
}
|
||||
|
||||
public static Class<?> getFieldType(Object instance, String fieldName) throws NoSuchFieldException {
|
||||
FieldResolver<Object> resolver = resolveField(instance, fieldName);
|
||||
if (resolver == null) throw new NoSuchFieldException(instance.getClass().getName() + "#" + fieldName);
|
||||
return resolver.getSelf().getType();
|
||||
}
|
||||
|
||||
public static String[] findFieldNamesByType(Object instance, Class<?> fieldType) {
|
||||
List<String> result = new ArrayList<>();
|
||||
Class<?> current = instance.getClass();
|
||||
while (current != null && current != Object.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) current);
|
||||
List<FieldResolver<Object>> fields = scope.field(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
condition.type(fieldType);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
for (FieldResolver<Object> field : fields) {
|
||||
result.add(field.getSelf().getName());
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String[] getAllFieldNames(Object instance) {
|
||||
List<String> result = new ArrayList<>();
|
||||
Class<?> current = instance.getClass();
|
||||
while (current != null && current != Object.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) current);
|
||||
List<FieldResolver<Object>> fields = scope.field(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
for (FieldResolver<Object> field : fields) {
|
||||
result.add(field.getSelf().getName());
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static Object getStaticField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) clazz);
|
||||
FieldResolver<Object> resolver = scope.firstFieldOrNull(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
condition.name(fieldName);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
if (resolver == null) throw new NoSuchFieldException(clazz.getName() + "#" + fieldName);
|
||||
return resolver.get();
|
||||
}
|
||||
|
||||
public static Object findStaticFieldByType(Class<?> clazz, Class<?> fieldType) {
|
||||
@SuppressWarnings("unchecked")
|
||||
KavaRef.MemberScope<Object> scope = (KavaRef.MemberScope<Object>) KavaRef.resolveClass((Class<Object>) clazz);
|
||||
List<FieldResolver<Object>> fields = scope.field(new Function1<FieldCondition<Object>, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(FieldCondition<Object> condition) {
|
||||
condition.type(fieldType);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
});
|
||||
for (FieldResolver<Object> field : fields) {
|
||||
try {
|
||||
Object value = field.get();
|
||||
if (value != null && value.getClass() == fieldType) return value;
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ import me.eternal.purrfectsnap.core.features.impl.messaging.Messaging
|
||||
import me.eternal.purrfectsnap.core.ui.ViewAppearanceHelper
|
||||
import me.eternal.purrfectsnap.core.util.EvictingMap
|
||||
import me.eternal.purrfectsnap.core.util.dataBuilder
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findStaticObjectFieldByType
|
||||
import me.eternal.purrfectsnap.mapper.impl.FriendRelationshipChangerMapper
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
@@ -1502,11 +1503,7 @@ class BulkMessagingAction : AbstractAction() {
|
||||
val pageTypeClass = pageType.getAsClass() ?: return@runCatching context.log.error("Could not find page type class")
|
||||
val method = f9lClass.declaredMethods.firstOrNull { it.name == addFriendMethodName }
|
||||
?: return@runCatching context.log.error("Could not find $addFriendMethodName method")
|
||||
fun findStaticField(clazz: Class<*>): Any? = clazz.declaredFields.firstOrNull { field ->
|
||||
java.lang.reflect.Modifier.isStatic(field.modifiers) && field.type == clazz
|
||||
}?.let { field ->
|
||||
runCatching { field.isAccessible = true; field.get(null)?.takeIf { it.javaClass == clazz } }.getOrNull()
|
||||
}
|
||||
fun findStaticField(clazz: Class<*>): Any? = clazz.findStaticObjectFieldByType(clazz)
|
||||
val enumClass = method.parameterTypes[2]
|
||||
val enumConstants = enumClass.enumConstants ?: enumClass.getMethod("values").invoke(null) as? Array<*>
|
||||
?: return@runCatching context.log.error("Could not get enum constants")
|
||||
|
||||
@@ -46,6 +46,7 @@ import me.eternal.purrfectsnap.core.action.AbstractAction
|
||||
import me.eternal.purrfectsnap.core.event.events.impl.ActivityResultEvent
|
||||
import me.eternal.purrfectsnap.core.features.impl.experiments.AddFriendSourceSpoof
|
||||
import me.eternal.purrfectsnap.core.features.impl.messaging.Messaging
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findStaticObjectFieldByType
|
||||
import me.eternal.purrfectsnap.core.util.EvictingMap
|
||||
import me.eternal.purrfectsnap.core.wrapper.impl.Snapchatter
|
||||
import me.eternal.purrfectsnap.common.util.snap.BitmojiSelfie
|
||||
@@ -103,15 +104,7 @@ class ManageFriendList : AbstractAction() {
|
||||
|
||||
// Helper function to find static field by trying fallback
|
||||
fun findStaticField(clazz: Class<*>): Any? {
|
||||
// Fallback: find any static field of the same type
|
||||
return clazz.declaredFields.firstOrNull { field ->
|
||||
java.lang.reflect.Modifier.isStatic(field.modifiers) && field.type == clazz
|
||||
}?.let { field ->
|
||||
runCatching {
|
||||
field.isAccessible = true
|
||||
field.get(null)?.takeIf { it.javaClass == clazz }
|
||||
}.getOrNull()
|
||||
}
|
||||
return clazz.findStaticObjectFieldByType(clazz)
|
||||
}
|
||||
|
||||
// Get enum constant for USERNAME
|
||||
|
||||
@@ -2,6 +2,8 @@ package me.eternal.purrfectsnap.core.features.impl.global
|
||||
|
||||
import me.eternal.purrfectsnap.core.features.Feature
|
||||
import me.eternal.purrfectsnap.core.util.dataBuilder
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findFieldNamesByType
|
||||
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||
import me.eternal.purrfectsnap.core.util.hook.hookConstructor
|
||||
@@ -54,11 +56,10 @@ class SnapchatPlus: Feature("SnapchatPlus") {
|
||||
val instance = param.thisObject<Any>()
|
||||
val firstArg = param.argNullable<Any>(0) ?: return@hook
|
||||
|
||||
instance::class.java.declaredFields.filter { it.type == firstArg::class.java }.forEach {
|
||||
it.isAccessible = true
|
||||
it.set(instance, firstArg)
|
||||
instance.findFieldNamesByType(firstArg::class.java).forEach { fieldName ->
|
||||
instance.setObjectField(fieldName, firstArg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +193,7 @@ class SendOverride : Feature("Send Override") {
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
val field = result.messageContent.instanceNonNull().javaClass.getDeclaredField("mAllowsTranscription")
|
||||
field.isAccessible = true
|
||||
field.set(result.messageContent.instanceNonNull(), false)
|
||||
result.messageContent.instanceNonNull().setObjectField("mAllowsTranscription", false)
|
||||
}
|
||||
}
|
||||
}.toByteArray()
|
||||
@@ -508,13 +506,7 @@ class SendOverride : Feature("Send Override") {
|
||||
}.getOrNull()
|
||||
|
||||
if (prohibitedEnum != null) {
|
||||
val savePolicyField = localMessageContent.instanceNonNull().javaClass.declaredFields
|
||||
.find { it.name == "mSavePolicy" }
|
||||
|
||||
if (savePolicyField != null) {
|
||||
savePolicyField.isAccessible = true
|
||||
localMessageContent.instanceNonNull().setObjectField("mSavePolicy", prohibitedEnum)
|
||||
}
|
||||
localMessageContent.instanceNonNull().setObjectField("mSavePolicy", prohibitedEnum)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -24,6 +24,8 @@ import me.eternal.purrfectsnap.core.features.MessagingRuleFeature
|
||||
import me.eternal.purrfectsnap.core.ui.addForegroundDrawable
|
||||
import me.eternal.purrfectsnap.core.ui.removeForegroundDrawable
|
||||
import me.eternal.purrfectsnap.core.util.EvictingMap
|
||||
import me.eternal.purrfectsnap.core.util.ktx.KavaRefFieldBridge
|
||||
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@@ -175,11 +177,12 @@ class MessageLogger : MessagingRuleFeature("MessageLogger", MessagingRuleType.ME
|
||||
}
|
||||
|
||||
//serialize all properties of messageJsonObject and put mMessageContent & mMetadata in the message object
|
||||
messageInstance::class.java.declaredFields.forEach { field ->
|
||||
if (field.name != "mMessageContent" && field.name != "mMetadata") return@forEach
|
||||
field.isAccessible = true
|
||||
deletedMessageObject[field.name]?.let { fieldValue ->
|
||||
field.set(messageInstance, context.gson.fromJson(fieldValue, field.type))
|
||||
listOf("mMessageContent", "mMetadata").forEach { fieldName ->
|
||||
deletedMessageObject[fieldName]?.let { fieldValue ->
|
||||
runCatching {
|
||||
val fieldType = KavaRefFieldBridge.getFieldType(messageInstance, fieldName)
|
||||
messageInstance.setObjectField(fieldName, context.gson.fromJson(fieldValue, fieldType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package me.eternal.purrfectsnap.core.util
|
||||
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||
import me.eternal.purrfectsnap.core.util.ktx.KavaRefFieldBridge
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
|
||||
@@ -35,28 +38,26 @@ class DataClassBuilder(
|
||||
private val instance: Any,
|
||||
) {
|
||||
fun set(fieldName: String, value: Any?) {
|
||||
val field = instance::class.java.declaredFields.firstOrNull { it.name == fieldName } ?: return
|
||||
val fieldType = field.type ?: return
|
||||
field.isAccessible = true
|
||||
val fieldType = runCatching { KavaRefFieldBridge.getFieldType(instance, fieldName) }.getOrNull() ?: return
|
||||
|
||||
when {
|
||||
fieldType.isEnum -> {
|
||||
val enumValue = fieldType.enumConstants?.firstOrNull { it.toString() == value } ?: return
|
||||
field.set(instance, enumValue)
|
||||
instance.setObjectField(fieldName, enumValue)
|
||||
}
|
||||
fieldType.isPrimitive -> {
|
||||
when (fieldType) {
|
||||
Boolean::class.javaPrimitiveType -> field.setBoolean(instance, value as Boolean)
|
||||
Byte::class.javaPrimitiveType -> field.setByte(instance, value as Byte)
|
||||
Char::class.javaPrimitiveType -> field.setChar(instance, value as Char)
|
||||
Short::class.javaPrimitiveType -> field.setShort(instance, value as Short)
|
||||
Int::class.javaPrimitiveType -> field.setInt(instance, value as Int)
|
||||
Long::class.javaPrimitiveType -> field.setLong(instance, value as Long)
|
||||
Float::class.javaPrimitiveType -> field.setFloat(instance, value as Float)
|
||||
Double::class.javaPrimitiveType -> field.setDouble(instance, value as Double)
|
||||
Boolean::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Boolean)
|
||||
Byte::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Byte)
|
||||
Char::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Char)
|
||||
Short::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Short)
|
||||
Int::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Int)
|
||||
Long::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Long)
|
||||
Float::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Float)
|
||||
Double::class.javaPrimitiveType -> instance.setObjectField(fieldName, value as Double)
|
||||
}
|
||||
}
|
||||
else -> field.set(instance, value)
|
||||
else -> instance.setObjectField(fieldName, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,23 +65,20 @@ class DataClassBuilder(
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> get(fieldName: String): T? {
|
||||
val field = instance::class.java.declaredFields.firstOrNull { it.name == fieldName } ?: return null
|
||||
field.isAccessible = true
|
||||
return field.get(instance) as? T
|
||||
return instance.getObjectField(fieldName) as? T
|
||||
}
|
||||
|
||||
fun from(fieldName: String, new: Boolean = false, callback: DataClassBuilder.() -> Unit) {
|
||||
val field = instance::class.java.declaredFields.firstOrNull { it.name == fieldName } ?: return
|
||||
field.isAccessible = true
|
||||
val fieldType = runCatching { KavaRefFieldBridge.getFieldType(instance, fieldName) }.getOrNull() ?: return
|
||||
|
||||
val lazyInstance by lazy { CallbackBuilder.createEmptyObject(field.type.constructors.firstOrNull() ?: return@lazy null) ?: return@lazy null }
|
||||
val lazyInstance by lazy { CallbackBuilder.createEmptyObject(fieldType.constructors.firstOrNull() ?: return@lazy null) ?: return@lazy null }
|
||||
val builderInstance = if (new) lazyInstance else {
|
||||
field.get(instance).takeIf { it != null } ?: lazyInstance
|
||||
instance.getObjectField(fieldName).takeIf { it != null } ?: lazyInstance
|
||||
}
|
||||
|
||||
DataClassBuilder(builderInstance ?: return).apply(callback)
|
||||
|
||||
field.set(instance, builderInstance)
|
||||
instance.setObjectField(fieldName, builderInstance)
|
||||
}
|
||||
|
||||
fun <T> cast(type: Class<T>, callback: T.() -> Unit) {
|
||||
@@ -88,10 +86,8 @@ class DataClassBuilder(
|
||||
}
|
||||
|
||||
fun interceptFieldInterface(fieldName: String, callback: (args: Array<Any?>, originalCall: (Array<Any?>) -> Any?) -> Any?) {
|
||||
val field = instance.javaClass.declaredFields.firstOrNull { it.name == fieldName } ?: return
|
||||
field.isAccessible = true
|
||||
set(fieldName, field.get(instance)?.let { makeFunctionProxy(it, callback) } ?: return)
|
||||
set(fieldName, instance.getObjectField(fieldName)?.let { makeFunctionProxy(it, callback) } ?: return)
|
||||
}
|
||||
|
||||
fun build() = instance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.eternal.purrfectsnap.core.util
|
||||
|
||||
import me.eternal.purrfectsnap.common.Constants
|
||||
import me.eternal.purrfectsnap.core.ModContext
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getStaticObjectField
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
@@ -40,9 +41,9 @@ object LSPatchUpdater {
|
||||
val obfuscatedModulePath by lazy {
|
||||
(runCatching {
|
||||
context::class.java.classLoader?.loadClass("org.lsposed.lspatch.share.Constants")
|
||||
}.getOrNull())?.declaredFields?.firstOrNull { it.name == "MANAGER_PACKAGE_NAME" }?.also {
|
||||
it.isAccessible = true
|
||||
}?.get(null) as? String
|
||||
}.getOrNull())?.let { clazz ->
|
||||
runCatching { clazz.getStaticObjectField("MANAGER_PACKAGE_NAME") as? String }.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
val embeddedModule = context.androidContext.cacheDir
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
package me.eternal.purrfectsnap.core.util.ktx
|
||||
|
||||
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? {
|
||||
return KavaRefFieldBridge.getField(this, fieldName)
|
||||
}
|
||||
|
||||
fun Any.getObjectField(fieldName: String): Any? {
|
||||
val field = findDeclaredFieldRecursive(this::class.java, fieldName)
|
||||
?: throw NoSuchFieldException("${this::class.java.name}#$fieldName")
|
||||
field.isAccessible = true
|
||||
return field.get(this)
|
||||
fun Any.findFieldNamesByType(type: Class<*>): List<String> {
|
||||
return KavaRefFieldBridge.findFieldNamesByType(this, type).toList()
|
||||
}
|
||||
|
||||
fun Any.allFieldNames(): List<String> {
|
||||
return KavaRefFieldBridge.getAllFieldNames(this).toList()
|
||||
}
|
||||
|
||||
fun Class<*>.getStaticObjectField(fieldName: String): Any? {
|
||||
return KavaRefFieldBridge.getStaticField(this, fieldName)
|
||||
}
|
||||
|
||||
fun Class<*>.findStaticObjectFieldByType(type: Class<*>): Any? {
|
||||
return KavaRefFieldBridge.findStaticFieldByType(this, type)
|
||||
}
|
||||
|
||||
fun Any.setEnumField(fieldName: String, value: String) {
|
||||
this::class.java.getDeclaredField(fieldName)
|
||||
.type.enumConstants?.firstOrNull { it.toString() == value }?.let { enum ->
|
||||
val enumType = KavaRefFieldBridge.getFieldType(this, fieldName)
|
||||
enumType.enumConstants?.firstOrNull { it.toString() == value }?.let { enum ->
|
||||
setObjectField(fieldName, enum)
|
||||
}
|
||||
}
|
||||
|
||||
fun Any.setObjectField(fieldName: String, value: Any?) {
|
||||
val field = findDeclaredFieldRecursive(this::class.java, fieldName)
|
||||
?: throw NoSuchFieldException("${this::class.java.name}#$fieldName")
|
||||
field.isAccessible = true
|
||||
field.set(this, value)
|
||||
KavaRefFieldBridge.setField(this, fieldName, value)
|
||||
}
|
||||
|
||||
fun Any.getObjectFieldOrNull(fieldName: String): Any? {
|
||||
|
||||
@@ -63,7 +63,9 @@ abstract class AbstractWrapper(
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun setEnumValue(fieldName: String, value: Enum<*>) {
|
||||
val type = instance!!.javaClass.declaredFields.find { it.name == fieldName }?.type as Class<out Enum<*>>
|
||||
val currentValue = instance?.getObjectField(fieldName) as? Enum<*> ?: return
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val type = currentValue.javaClass as Class<out Enum<*>>
|
||||
instance?.setObjectField(fieldName, java.lang.Enum.valueOf(type, value.name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl
|
||||
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findFieldNamesByType
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.util.ktx.setObjectField
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
|
||||
class ScSize(
|
||||
obj: Any?
|
||||
) : AbstractWrapper(obj) {
|
||||
private val firstField by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.first { it.type == Int::class.javaPrimitiveType }.also { it.isAccessible = true }
|
||||
private val intFields by lazy {
|
||||
instanceNonNull().findFieldNamesByType(Int::class.javaPrimitiveType ?: Int::class.java)
|
||||
}
|
||||
private val firstFieldName by lazy { intFields.first() }
|
||||
private val secondFieldName by lazy { intFields.last() }
|
||||
|
||||
private val secondField by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.last { it.type == Int::class.javaPrimitiveType }.also { it.isAccessible = true }
|
||||
}
|
||||
|
||||
|
||||
var first: Int get() = firstField.getInt(instanceNonNull())
|
||||
var first: Int get() = instanceNonNull().getObjectField(firstFieldName) as Int
|
||||
set(value) {
|
||||
firstField.setInt(instanceNonNull(), value)
|
||||
instanceNonNull().setObjectField(firstFieldName, value)
|
||||
}
|
||||
|
||||
var second: Int get() = secondField.getInt(instanceNonNull())
|
||||
var second: Int get() = instanceNonNull().getObjectField(secondFieldName) as Int
|
||||
set(value) {
|
||||
secondField.setInt(instanceNonNull(), value)
|
||||
instanceNonNull().setObjectField(secondFieldName, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ package me.eternal.purrfectsnap.core.wrapper.impl.media
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import me.eternal.purrfectsnap.common.data.download.MediaEncryptionKeyPair
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findFieldNamesByType
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.lang.reflect.Field
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
@@ -44,28 +45,19 @@ class EncryptionWrapper(
|
||||
// Key + IV extraction
|
||||
|
||||
val keySpec: ByteArray by lazy {
|
||||
manualKey ?: searchByteArrayField(32)[instance] as ByteArray
|
||||
manualKey ?: searchByteArrayField(32)?.let { instanceNonNull().getObjectField(it) as? ByteArray }
|
||||
?: throw NoSuchFieldException("Failed to find 32-byte key field")
|
||||
}
|
||||
|
||||
val ivKeyParameterSpec: ByteArray by lazy {
|
||||
manualIv ?: searchByteArrayField(16)[instance] as ByteArray
|
||||
manualIv ?: searchByteArrayField(16)?.let { instanceNonNull().getObjectField(it) as? ByteArray }
|
||||
?: throw NoSuchFieldException("Failed to find 16-byte IV field")
|
||||
}
|
||||
|
||||
private fun searchByteArrayField(length: Int): Field {
|
||||
return instanceNonNull()::class.java.declaredFields.first { field ->
|
||||
try {
|
||||
field.isAccessible = true
|
||||
|
||||
if (!field.type.isArray ||
|
||||
field.type.componentType != Byte::class.javaPrimitiveType
|
||||
) return@first false
|
||||
|
||||
val value = field.get(instanceNonNull()) as? ByteArray
|
||||
value?.size == length
|
||||
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
private fun searchByteArrayField(length: Int): String? {
|
||||
val fields = instanceNonNull().findFieldNamesByType(ByteArray::class.java)
|
||||
return fields.firstOrNull { fieldName ->
|
||||
(instanceNonNull().getObjectField(fieldName) as? ByteArray)?.size == length
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl.media.dash
|
||||
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findFieldNamesByType
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
|
||||
class LongformVideoPlaylistItem(obj: Any?) : AbstractWrapper(obj) {
|
||||
private val chapterList by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.first { it.type == List::class.java }
|
||||
private val chapterListField by lazy {
|
||||
instanceNonNull().findFieldNamesByType(List::class.java).first()
|
||||
}
|
||||
val chapters: List<SnapChapter>
|
||||
get() = (chapterList.get(instanceNonNull()) as List<*>).map { SnapChapter(it) }
|
||||
}
|
||||
get() = (instanceNonNull().getObjectField(chapterListField) as List<*>).map { SnapChapter(it) }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl.media.dash
|
||||
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findFieldNamesByType
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
|
||||
class SnapChapter (obj: Any?) : AbstractWrapper(obj) {
|
||||
private val longFields by lazy {
|
||||
instanceNonNull().findFieldNamesByType(Long::class.javaPrimitiveType ?: Long::class.java)
|
||||
}
|
||||
val snapId by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.first { it.type == Long::class.javaPrimitiveType }.get(instanceNonNull()) as Long
|
||||
instanceNonNull().getObjectField(longFields.first()) as Long
|
||||
}
|
||||
val startTimeMs by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.filter { it.type == Long::class.javaPrimitiveType }[1].get(instanceNonNull()) as Long
|
||||
instanceNonNull().getObjectField(longFields[1]) as Long
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl.media.dash
|
||||
|
||||
import me.eternal.purrfectsnap.core.util.ktx.findFieldNamesByType
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
|
||||
class SnapPlaylistItem (obj: Any?) : AbstractWrapper(obj) {
|
||||
val snapId by lazy {
|
||||
instanceNonNull().javaClass.declaredFields.first { it.type == Long::class.javaPrimitiveType }.get(instanceNonNull()) as Long
|
||||
private val longFields by lazy {
|
||||
instanceNonNull().findFieldNamesByType(Long::class.javaPrimitiveType ?: Long::class.java)
|
||||
}
|
||||
}
|
||||
val snapId by lazy {
|
||||
instanceNonNull().getObjectField(longFields.first()) as Long
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ APP_VERSION_NAME=1.3.2
|
||||
APP_VERSION_CODE=272
|
||||
debug_build_hash=18fe2a814d0e2eb5
|
||||
psIntegrityPinnedSha256=
|
||||
EXPECTED_CERT_SHA256=371cb9fbebbe9880d147f59bab36d61eee122854ef8c9ee1ecf12b82368bcf10
|
||||
EXPECTED_CERT_SHA256=0f188cb17d8ea4902ee15ce98f4928ba4226a4790fa3c52f62d776b08e46ca1c
|
||||
android.disallowKotlinSourceSets=false
|
||||
android.sourceset.disallowProvider=false
|
||||
|
||||
Reference in New Issue
Block a user