kavaref migration

This commit is contained in:
particle-box
2026-03-03 21:23:26 +05:30
parent 956bdda6c0
commit 1ccdaee879
19 changed files with 316 additions and 129 deletions

View File

@@ -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")
}

View File

@@ -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]);
}
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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) {

View File

@@ -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))
}
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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? {

View File

@@ -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))
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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) }
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}

View File

@@ -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