From 347115f8d3ee3efa1ffd5fac69b9349a7854b59c Mon Sep 17 00:00:00 2001 From: DarkKnight2122 <145188583+DarkKnight2122@users.noreply.github.com> Date: Sat, 25 Apr 2026 04:36:36 +0530 Subject: [PATCH] Snapchat Plus bug fixes --- .../core/event/EventDispatcher.kt | 3 +- .../core/features/impl/global/SnapchatPlus.kt | 96 +++++++++---------- .../core/ui/ViewAppearanceHelper.kt | 2 + 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt index 9765cd33..1fd1f29f 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt @@ -47,7 +47,7 @@ class EventDispatcher( cacheHook( methodParam.thisObject()::class.java ) { - hook(bindMethod.get().toString(), HookStage.BEFORE) bindViewMethod@{ param -> + hook(bindMethod.get().toString(), HookStage.AFTER) bindViewMethod@{ param -> val instance = param.thisObject() val view = instance::class.java.methods.firstOrNull { it.name == getViewMethod.get().toString() @@ -161,7 +161,6 @@ class EventDispatcher( adapter = param } ) { - if (canceled) param.setResult(null) postHookEvent() } } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/SnapchatPlus.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/SnapchatPlus.kt index aa5fbb98..f6cddd77 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/SnapchatPlus.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/SnapchatPlus.kt @@ -17,67 +17,59 @@ class SnapchatPlus: Feature("SnapchatPlus") { override fun init() { val snapchatPlusTier = context.config.global.snapchatPlus.getNullable() + if (snapchatPlusTier == null || snapchatPlusTier == "not_subscribed") return - if (snapchatPlusTier != null) { - context.mappings.useMapper(PlusSubscriptionMapper::class) { - classReference.get()?.hookConstructor(HookStage.AFTER) { param -> - param.thisObject().dataBuilder { - //subscription tier - if (get(tierField.getAsString()!!)?.javaClass?.isEnum == true) { - set(tierField.getAsString()!!, when (snapchatPlusTier) { - "not_subscribed" -> "NO_ACCESS" - "basic" -> "SNAPCHAT_PLUS" - "ad_free" -> "SNAPCHAT_PLUS_AD_FREE" - else -> "SNAPCHAT_PLUS" - }) - } else { - set(tierField.getAsString()!!, when (snapchatPlusTier) { - "not_subscribed" -> 1 - "basic" -> 2 - "ad_free" -> 3 - else -> 2 - }) - } + // Pre-calculate custom purchase date to eliminate main thread lag + val customPurchaseDateRaw = context.config.global.snapchatPlusPurchaseDate.get().trim() + val customPurchaseDateMillis = if (customPurchaseDateRaw.isNotEmpty()) { + runCatching { + LocalDate.parse(customPurchaseDateRaw, DateTimeFormatter.ISO_LOCAL_DATE) + .atStartOfDay(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli() + }.getOrNull() + } else (System.currentTimeMillis() - 7776000000L) // 3 months fallback - //subscription status - set(statusField.getAsString()!!, 2) - - val fallbackOriginalSubscriptionTime = System.currentTimeMillis() - 7776000000L - val customPurchaseDate = context.config.global.snapchatPlusPurchaseDate.get().trim() - val customPurchaseDateMillis = if (customPurchaseDate.isNotEmpty()) { - runCatching { - LocalDate - .parse(customPurchaseDate, DateTimeFormatter.ISO_LOCAL_DATE) - .atStartOfDay(ZoneId.systemDefault()) - .toInstant() - .toEpochMilli() - }.getOrNull() - } else { - null - } - - set( - originalSubscriptionTimeMillisField.getAsString()!!, - customPurchaseDateMillis ?: fallbackOriginalSubscriptionTime - ) - set(expirationTimeMillisField.getAsString()!!, expirationTimeMillis) + context.mappings.useMapper(PlusSubscriptionMapper::class) { + classReference.get()?.hookConstructor(HookStage.AFTER) { param -> + param.thisObject().dataBuilder { + //subscription tier + if (get(tierField.getAsString()!!)?.javaClass?.isEnum == true) { + set(tierField.getAsString()!!, when (snapchatPlusTier) { + "not_subscribed" -> "NO_ACCESS" + "basic" -> "SNAPCHAT_PLUS" + "ad_free" -> "SNAPCHAT_PLUS_AD_FREE" + else -> "SNAPCHAT_PLUS" + }) + } else { + set(tierField.getAsString()!!, when (snapchatPlusTier) { + "not_subscribed" -> 1 + "basic" -> 2 + "ad_free" -> 3 + else -> 2 + }) } + + //subscription status + set(statusField.getAsString()!!, 2) + + set( + originalSubscriptionTimeMillisField.getAsString()!!, + customPurchaseDateMillis + ) + set(expirationTimeMillisField.getAsString()!!, expirationTimeMillis) } } } + // Force enable all premium features in the catalog if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) { - findClass("com.snap.plus.FeatureCatalog").methods.last { - !it.name.contains("init") && - it.parameterTypes.isNotEmpty() && - it.parameterTypes[0].name != "java.lang.Boolean" - }.hook(HookStage.BEFORE) { param -> - val instance = param.thisObject() - val firstArg = param.argNullable(0) ?: return@hook - - instance.findFieldNamesByType(firstArg::class.java).forEach { fieldName -> - instance.setObjectField(fieldName, firstArg) + runCatching { + val featureCatalogClass = findClass("com.snap.plus.FeatureCatalog") + featureCatalogClass.hook("isFeatureEnabled", HookStage.BEFORE) { param -> + param.setResult(true) } + context.log.verbose("Successfully unlocked premium Snapchat features") } } } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/ui/ViewAppearanceHelper.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/ui/ViewAppearanceHelper.kt index 731cfff5..d606f6f6 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/ui/ViewAppearanceHelper.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/ui/ViewAppearanceHelper.kt @@ -137,6 +137,8 @@ fun View.onAttachChange(onAttach: (View.OnAttachStateChangeListener) -> Unit = { fun View.hideViewCompletely() { fun hide() { + if (visibility == View.GONE && layoutParams?.width == 0 && layoutParams?.height == 0) return + isEnabled = false visibility = View.GONE setWillNotDraw(true)