From e166da74653263fe94f64af8250045797c513b15 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Tue, 18 Apr 2023 09:36:00 +0200 Subject: [PATCH 01/22] Update to Grindr 9.6.0 to support the most basic functionality --- app/build.gradle | 6 +++--- .../java/com/eljaviluki/grindrplus/Hooker.kt | 10 +++++----- .../com/eljaviluki/grindrplus/Obfuscation.kt | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 969629d6..96f5fa17 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ plugins { ext.versionMajor = 1 ext.versionMinor = 1 ext.versionPatch = 6 -ext.grindrVersion = '8.23.0' +ext.grindrVersion = '9.6.0' private String genVersionName() { String versionName = "${ext.versionMajor}.${ext.versionMinor}.${ext.versionPatch}" @@ -18,12 +18,12 @@ private String getVersionNameSuffix() { } android { - compileSdk 32 + compileSdk 33 defaultConfig { applicationId 'com.eljaviluki.grindrplus' minSdk 21 - targetSdk 32 + targetSdk 33 versionCode 14 versionName genVersionName() versionNameSuffix getVersionNameSuffix() diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 8cef02e6..3acac947 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -54,11 +54,11 @@ class Hooker : IXposedHookLoadPackage { e.message?.let { Logger.xLog(it) } } - try { + /*try { Hooks.addExtraProfileFields() } catch (e : Exception) { e.message?.let { Logger.xLog(it) } - } + }*/ try { Hooks.hookUserSessionImpl() @@ -103,11 +103,11 @@ class Hooker : IXposedHookLoadPackage { e.message?.let { Logger.xLog(it) } } - try { + /*try { Hooks.preventRecordProfileViews() } catch (e : Exception) { e.message?.let { Logger.xLog(it) } - } + }*/ try { Hooks.makeMessagesAlwaysRemovable() @@ -126,7 +126,7 @@ class Hooker : IXposedHookLoadPackage { } companion object { - const val TARGET_PKG_VERSION_NAME = "8.23.0" + const val TARGET_PKG_VERSION_NAME = "9.6.0" var pkgParam: LoadPackageParam by InitOnce() var appContext: Context by InitOnce() diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 6c5aea97..d9d722d9 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -17,14 +17,14 @@ object Obfuscation { object Experiment { private const val _experiment = "$_base.experiment" - const val IExperimentsManager = "$_experiment.c" + const val IExperimentsManager = "$_experiment.a" } } object experiment { private const val _experiment = Constants.GRINDR_PKG + ".experiment" - const val Experiments = "$_experiment.b" + const val Experiments = "$_experiment.f" object Experiments_ { const val uncheckedIsEnabled_expMgr = "c" } @@ -110,15 +110,15 @@ object Obfuscation { object storage { private const val _storage = Constants.GRINDR_PKG + ".storage" - const val UserSession = "$_storage.f1" + const val UserSession = "$_storage.z0" const val IUserSession = "$_storage.UserSession" object IUserSession_ { const val hasFeature_feature = "a" - const val isFree = "k" - const val isNoXtraUpsell = "r" - const val isXtra = "g" - const val isUnlimited = "s" + const val isFree = "g" + const val isNoXtraUpsell = "y" + const val isXtra = "o" + const val isUnlimited = "x" } } @@ -147,7 +147,7 @@ object Obfuscation { object utils { private const val _utils = Constants.GRINDR_PKG + ".utils" - const val ProfileUtils = "$_utils.v0" + const val ProfileUtils = "$_utils.n0" object ProfileUtils_ { const val onlineIndicatorDuration = "b" } From 6700b1a494b99dc0dffc045e3e9a2d9baea5dcc7 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Tue, 18 Apr 2023 21:37:52 +0200 Subject: [PATCH 02/22] Fix addExtraProfileFields Fix makeMessagesAlwaysRemovable Fix notifyBlockStatusViaToast Fix UserSession obfuscation --- .../main/java/com/eljaviluki/grindrplus/Hooker.kt | 4 ++-- .../main/java/com/eljaviluki/grindrplus/Hooks.kt | 13 +++++++++---- .../java/com/eljaviluki/grindrplus/Obfuscation.kt | 14 +++++++------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 3acac947..c9291aad 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -54,11 +54,11 @@ class Hooker : IXposedHookLoadPackage { e.message?.let { Logger.xLog(it) } } - /*try { + try { Hooks.addExtraProfileFields() } catch (e : Exception) { e.message?.let { Logger.xLog(it) } - }*/ + } try { Hooks.hookUserSessionImpl() diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 645fde86..88719a44 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -507,12 +507,17 @@ object Hooks { } fun notifyBlockStatusViaToast() { - val class_BlockByHelper = findClass( - GApp.persistence.cache.BlockByHelper, + val class_BlockedByHelper = findClass( + GApp.persistence.cache.BlockedByHelper, Hooker.pkgParam.classLoader ) - findAndHookMethod(class_BlockByHelper, GApp.persistence.cache.BlockByHelper_.addBlockByProfile, String::class.java, object : XC_MethodHook(){ + val class_Continuation = findClass( + "kotlin.coroutines.Continuation", + Hooker.pkgParam.classLoader + ) + + findAndHookMethod(class_BlockedByHelper, GApp.persistence.cache.BlockByHelper_.addBlockByProfile, String::class.java, class_Continuation, object : XC_MethodHook(){ override fun beforeHookedMethod(param: MethodHookParam?) { val profileId: String = param!!.args[0] as String ContextCompat.getMainExecutor(Hooker.appContext).execute { @@ -521,7 +526,7 @@ object Hooks { } }) - findAndHookMethod(class_BlockByHelper, GApp.persistence.cache.BlockByHelper_.removeBlockByProfile, String::class.java, object : XC_MethodHook(){ + findAndHookMethod(class_BlockedByHelper, GApp.persistence.cache.BlockByHelper_.removeBlockByProfile, String::class.java, class_Continuation, object : XC_MethodHook(){ override fun beforeHookedMethod(param: MethodHookParam?) { val profileId: String = param!!.args[0] as String ContextCompat.getMainExecutor(Hooker.appContext).execute { diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index d9d722d9..794ae0cb 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -68,7 +68,7 @@ object Obfuscation { object cache { private const val _cache = "$_persistence.cache" - const val BlockByHelper = "$_cache.BlockByHelper" + const val BlockedByHelper = "$_cache.BlockedByHelper" object BlockByHelper_ { const val addBlockByProfile = "addBlockByProfile" const val removeBlockByProfile = "removeBlockByProfile" @@ -102,8 +102,8 @@ object Obfuscation { const val color = "$_R.m0" object color_ { - const val grindr_gold_star_gay = "D" - const val grindr_pure_white = "T" + const val grindr_gold_star_gay = "F" + const val grindr_pure_white = "V" } } @@ -115,8 +115,8 @@ object Obfuscation { const val IUserSession = "$_storage.UserSession" object IUserSession_ { const val hasFeature_feature = "a" - const val isFree = "g" - const val isNoXtraUpsell = "y" + const val isFree = "r" + const val isNoXtraUpsell = "g" const val isXtra = "o" const val isUnlimited = "x" } @@ -139,7 +139,7 @@ object Obfuscation { const val ChatBaseFragmentV2 = "$_chat.ChatBaseFragmentV2" object ChatBaseFragmentV2_ { - const val _canBeUnsent = "X1" + const val _canBeUnsent = "w1" } } } @@ -156,7 +156,7 @@ object Obfuscation { object view { private const val _view = Constants.GRINDR_PKG + ".view" - const val ExtendedProfileFieldView = "$_view.v4" + const val ExtendedProfileFieldView = "$_view.z4" object ExtendedProfileFieldView_ { const val setLabel = "l" const val setValue = "n" From 4c92687b670c8d55293021ef3026b1a48ed54191 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Thu, 20 Apr 2023 15:55:32 +0200 Subject: [PATCH 03/22] Set _canBeUnsent to right value --- app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 794ae0cb..810da83e 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -139,7 +139,7 @@ object Obfuscation { const val ChatBaseFragmentV2 = "$_chat.ChatBaseFragmentV2" object ChatBaseFragmentV2_ { - const val _canBeUnsent = "w1" + const val _canBeUnsent = "Y1" } } } From 6e884a3cab211090beaac944be69fc8f45cb1367 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 21 Apr 2023 17:37:38 +0200 Subject: [PATCH 04/22] Show blocks/unblocks in chat history --- .../java/com/eljaviluki/grindrplus/Hooker.kt | 2 +- .../java/com/eljaviluki/grindrplus/Hooks.kt | 33 +++++++++++++++++++ .../com/eljaviluki/grindrplus/Obfuscation.kt | 5 +++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index c9291aad..26c6badb 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -116,7 +116,7 @@ class Hooker : IXposedHookLoadPackage { } try { - Hooks.notifyBlockStatusViaToast() + Hooks.showBlocksInChat() } catch (e : Exception) { e.message?.let { Logger.xLog(it) } } diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 88719a44..1fb1c821 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -506,6 +506,7 @@ object Hooks { ) } + /* fun notifyBlockStatusViaToast() { val class_BlockedByHelper = findClass( GApp.persistence.cache.BlockedByHelper, @@ -535,4 +536,36 @@ object Hooks { } }) } + */ + + fun showBlocksInChat() { + val chatMessage = findClass(GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader) + + findAndHookMethod(GApp.model.ChatMessageParserCoroutine, + Hooker.pkgParam.classLoader, + "invokeSuspend", + Any::class.java, + object : XC_MethodHook() { + @Throws(Throwable::class) + override fun beforeHookedMethod(param: MethodHookParam) { + } + + @Throws(Throwable::class) + override fun afterHookedMethod(param: MethodHookParam) { + val result = param.result + if (!chatMessage.isInstance(result)) return + val type = callMethod(result, GApp.persistence.model.ChatMessage_.getType) as String + when (type) { + "block" -> { + callMethod(result, GApp.persistence.model.ChatMessage_.setType, "text") + callMethod(result, GApp.persistence.model.ChatMessage_.setBody, "[You have been blocked.]") + } + "unblock" -> { + callMethod(result, GApp.persistence.model.ChatMessage_.setType, "text") + callMethod(result, GApp.persistence.model.ChatMessage_.setBody, "[You have been unblocked.]") + } + } + } + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 810da83e..b1e455c1 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -60,6 +60,8 @@ object Obfuscation { const val getMpuFree = "getMpuFree" const val getMpuXtra = "getMpuXtra" } + + const val ChatMessageParserCoroutine = "$_model.ChatMessageParser\$parseXmppMessage$2" } object persistence { @@ -82,6 +84,9 @@ object Obfuscation { const val ChatMessage = "$_model.ChatMessage" object ChatMessage_ { const val TAP_TYPE_NONE = "TAP_TYPE_NONE" + const val getType = "getType" + const val setType = "setType" + const val setBody = "setBody" } const val Profile = "$_model.Profile" From d84a7b7e68cfa231ac7c9791f2d289694ca6e163 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sat, 22 Apr 2023 17:03:50 +0200 Subject: [PATCH 05/22] Don't remove blocked profiles --- .../com/eljaviluki/grindrplus/Constants.kt | 1 + .../java/com/eljaviluki/grindrplus/Hooker.kt | 6 + .../java/com/eljaviluki/grindrplus/Hooks.kt | 184 +++++++++++++++--- .../com/eljaviluki/grindrplus/Obfuscation.kt | 14 ++ 4 files changed, 176 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Constants.kt b/app/src/main/java/com/eljaviluki/grindrplus/Constants.kt index eb55df28..d6ae9ec9 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Constants.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Constants.kt @@ -12,5 +12,6 @@ object Constants { val RETURN_INTEGER_MAX_VALUE: XC_MethodReplacement = returnConstant(Int.MAX_VALUE) val RETURN_LONG_MAX_VALUE: XC_MethodReplacement = returnConstant(Long.MAX_VALUE) val RETURN_ZERO: XC_MethodReplacement = returnConstant(0) + val RETURN_UNIT: XC_MethodReplacement = returnConstant(Unit) } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 26c6badb..677c9534 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -120,6 +120,12 @@ class Hooker : IXposedHookLoadPackage { } catch (e : Exception) { e.message?.let { Logger.xLog(it) } } + + try { + Hooks.keepChatsOfBlockedProfiles() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } } } ) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 1fb1c821..f5a47b5d 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -7,18 +7,19 @@ import android.os.Build import android.util.AttributeSet import android.view.View import android.view.Window -import de.robv.android.xposed.XC_MethodHook import android.view.WindowManager import android.widget.FrameLayout import android.widget.Toast import androidx.core.content.ContextCompat -import com.eljaviluki.grindrplus.Constants.Returns.RETURN_TRUE import com.eljaviluki.grindrplus.Constants.Returns.RETURN_FALSE import com.eljaviluki.grindrplus.Constants.Returns.RETURN_INTEGER_MAX_VALUE import com.eljaviluki.grindrplus.Constants.Returns.RETURN_LONG_MAX_VALUE +import com.eljaviluki.grindrplus.Constants.Returns.RETURN_TRUE +import com.eljaviluki.grindrplus.Constants.Returns.RETURN_UNIT import com.eljaviluki.grindrplus.Constants.Returns.RETURN_ZERO import com.eljaviluki.grindrplus.Obfuscation.GApp import com.eljaviluki.grindrplus.decorated.persistence.model.Profile +import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers.* @@ -83,7 +84,12 @@ object Hooks { Hooker.pkgParam.classLoader ) - val checkNotNullParameterMethod = findMethodExact(class_Intrinsics, "checkNotNullParameter", Object::class.java, String::class.java) + val checkNotNullParameterMethod = findMethodExact( + class_Intrinsics, + "checkNotNullParameter", + Object::class.java, + String::class.java + ) findAndHookMethod( class_ProfileFieldsView, @@ -122,18 +128,31 @@ object Hooks { val profile = Profile(it) addProfileFieldUi("Profile ID", profile.profileId, 0).also { view -> view.setOnLongClickListener { - val clipboard = Hooker.appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboard = + Hooker.appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("Profile ID", profile.profileId) clipboard.setPrimaryClip(clip) - Toast.makeText(Hooker.appContext, "Profile ID copied to clipboard", Toast.LENGTH_SHORT).show() + Toast.makeText( + Hooker.appContext, + "Profile ID copied to clipboard", + Toast.LENGTH_SHORT + ).show() true } } - addProfileFieldUi("Last Seen", if (profile.seen != 0L) Utils.toReadableDate(profile.seen) else "N/A", 1) + addProfileFieldUi( + "Last Seen", + if (profile.seen != 0L) Utils.toReadableDate(profile.seen) else "N/A", + 1 + ) if (profile.weight != 0.0 && profile.height != 0.0) - addProfileFieldUi("Body Mass Index", Utils.getBmiDescription(profile.weight, profile.height), 2) + addProfileFieldUi( + "Body Mass Index", + Utils.getBmiDescription(profile.weight, profile.height), + 2 + ) } //.setVisibility() of param.thisObject to always VISIBLE (otherwise if the profile has no fields, the additional ones will not be shown) @@ -141,8 +160,15 @@ object Hooks { } //By default, the views are added to the end of the list. - private fun addProfileFieldUi(label: CharSequence, value: CharSequence, where: Int = -1) : FrameLayout { - val hooked = XposedBridge.hookMethod(checkNotNullParameterMethod, XC_MethodReplacement.DO_NOTHING) + private fun addProfileFieldUi( + label: CharSequence, + value: CharSequence, + where: Int = -1 + ): FrameLayout { + val hooked = XposedBridge.hookMethod( + checkNotNullParameterMethod, + XC_MethodReplacement.DO_NOTHING + ) val extendedProfileFieldView = newInstance(class_ExtendedProfileFieldView, context, null as AttributeSet?) hooked.unhook() @@ -379,14 +405,14 @@ object Hooks { "android.location.Location", Hooker.pkgParam.classLoader ) - + findAndHookMethod( class_Location, "isFromMockProvider", RETURN_FALSE ) - if(Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= 31) { findAndHookMethod( class_Location, "isMock", @@ -413,9 +439,13 @@ object Hooks { * * @author ElJaviLuki */ - fun hookOnlineIndicatorDuration(duration : Duration){ + fun hookOnlineIndicatorDuration(duration: Duration) { val class_ProfileUtils = findClass(GApp.utils.ProfileUtils, Hooker.pkgParam.classLoader) - setStaticLongField(class_ProfileUtils, GApp.utils.ProfileUtils_.onlineIndicatorDuration, duration.inWholeMilliseconds) + setStaticLongField( + class_ProfileUtils, + GApp.utils.ProfileUtils_.onlineIndicatorDuration, + duration.inWholeMilliseconds + ) } /** @@ -425,9 +455,13 @@ object Hooks { */ fun unlimitedTaps() { val class_TapsAnimLayout = findClass(GApp.view.TapsAnimLayout, Hooker.pkgParam.classLoader) - val class_ChatMessage = findClass(GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader) + val class_ChatMessage = + findClass(GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader) - val tapTypeToHook = getStaticObjectField(class_ChatMessage, GApp.persistence.model.ChatMessage_.TAP_TYPE_NONE) + val tapTypeToHook = getStaticObjectField( + class_ChatMessage, + GApp.persistence.model.ChatMessage_.TAP_TYPE_NONE + ) //Reset the tap value to allow multitapping. findAndHookMethod( @@ -435,7 +469,7 @@ object Hooks { GApp.view.TapsAnimLayout_.setTapType, String::class.java, Boolean::class.javaPrimitiveType, - object : XC_MethodHook(){ + object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { setObjectField( param.thisObject, @@ -467,7 +501,8 @@ object Hooks { * @author ElJaviLuki */ fun removeExpirationOnExpiringPhotos() { - val class_ExpiringImageBody = findClass(GApp.model.ExpiringImageBody, Hooker.pkgParam.classLoader) + val class_ExpiringImageBody = + findClass(GApp.model.ExpiringImageBody, Hooker.pkgParam.classLoader) findAndHookMethod( class_ExpiringImageBody, GApp.model.ExpiringImageBody_.getDuration, @@ -475,13 +510,14 @@ object Hooks { ) } - fun preventRecordProfileViews(){ + fun preventRecordProfileViews() { val class_Continuation = findClass( "kotlin.coroutines.Continuation", Hooker.pkgParam.classLoader ) - val class_GrindrRestService = findClass(GApp.api.GrindrRestService, Hooker.pkgParam.classLoader) + val class_GrindrRestService = + findClass(GApp.api.GrindrRestService, Hooker.pkgParam.classLoader) findAndHookMethod( class_GrindrRestService, GApp.api.GrindrRestService_.recordProfileViews, @@ -491,13 +527,14 @@ object Hooks { ) } - fun makeMessagesAlwaysRemovable(){ + fun makeMessagesAlwaysRemovable() { val class_ChatBaseFragmentV2 = findClass( GApp.ui.chat.ChatBaseFragmentV2, Hooker.pkgParam.classLoader ) - val class_ChatMessage = findClass(GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader) + val class_ChatMessage = + findClass(GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader) findAndHookMethod( class_ChatBaseFragmentV2, GApp.ui.chat.ChatBaseFragmentV2_._canBeUnsent, @@ -546,26 +583,115 @@ object Hooks { "invokeSuspend", Any::class.java, object : XC_MethodHook() { - @Throws(Throwable::class) - override fun beforeHookedMethod(param: MethodHookParam) { - } - - @Throws(Throwable::class) override fun afterHookedMethod(param: MethodHookParam) { val result = param.result if (!chatMessage.isInstance(result)) return - val type = callMethod(result, GApp.persistence.model.ChatMessage_.getType) as String + val type = + callMethod(result, GApp.persistence.model.ChatMessage_.getType) as String when (type) { "block" -> { callMethod(result, GApp.persistence.model.ChatMessage_.setType, "text") - callMethod(result, GApp.persistence.model.ChatMessage_.setBody, "[You have been blocked.]") + callMethod( + result, + GApp.persistence.model.ChatMessage_.setBody, + "[You have been blocked.]" + ) } "unblock" -> { callMethod(result, GApp.persistence.model.ChatMessage_.setType, "text") - callMethod(result, GApp.persistence.model.ChatMessage_.setBody, "[You have been unblocked.]") + callMethod( + result, + GApp.persistence.model.ChatMessage_.setBody, + "[You have been unblocked.]" + ) } } } }) } + + fun keepChatsOfBlockedProfiles() { + val class_Continuation = findClass( + "kotlin.coroutines.Continuation", + Hooker.pkgParam.classLoader + ) + + findAndHookMethod( + GApp.manager.BlockInteractor, + Hooker.pkgParam.classLoader, + GApp.manager.BlockInteractor_.blockstream, + String::class.java, + Boolean::class.javaPrimitiveType, + class_Continuation, + RETURN_UNIT + ) + + findAndHookMethod( + GApp.manager.BlockInteractor, + Hooker.pkgParam.classLoader, + GApp.manager.BlockInteractor_.processAndRemoveBlockedProfiles, + List::class.java, + Boolean::class.javaPrimitiveType, + class_Continuation, + RETURN_UNIT + ) + + findAndHookMethod( + GApp.persistence.repository.ProfileRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.ProfileRepo_.delete, + List::class.java, + class_Continuation, + RETURN_UNIT + ) + + val queries = mapOf( + "\n" + + " SELECT * FROM conversation \n" + + " LEFT JOIN blocks ON blocks.profileId = conversation_id\n" + + " LEFT JOIN banned ON banned.profileId = conversation_id\n" + + " WHERE blocks.profileId is NULL AND banned.profileId is NULL\n" + + " ORDER BY conversation.pin DESC, conversation.last_message_timestamp DESC, conversation.conversation_id DESC\n" + + " " + to "\n" + + " SELECT * FROM conversation \n" + + " LEFT JOIN blocks ON blocks.profileId = conversation_id\n" + + " LEFT JOIN banned ON banned.profileId = conversation_id\n" + + " WHERE banned.profileId is NULL\n" + + " ORDER BY conversation.pin DESC, conversation.last_message_timestamp DESC, conversation.conversation_id DESC\n" + + " ", + "\n" + + " SELECT * FROM conversation\n" + + " LEFT JOIN profile ON profile.profile_id = conversation.conversation_id\n" + + " LEFT JOIN blocks ON blocks.profileId = conversation_id\n" + + " LEFT JOIN banned ON banned.profileId = conversation_id\n" + + " WHERE blocks.profileId is NULL AND banned.profileId is NULL AND unread >= :minUnreadCount AND is_group_chat in (:isGroupChat)\n" + + " AND (:minLastSeen = 0 OR seen > :minLastSeen)\n" + + " AND (1 IN (:isFavorite) AND 0 IN (:isFavorite) OR is_favorite in (:isFavorite))\n" + + " ORDER BY conversation.pin DESC, conversation.last_message_timestamp DESC, conversation.conversation_id DESC\n" + + " " + to "\n" + + " SELECT * FROM conversation\n" + + " LEFT JOIN profile ON profile.profile_id = conversation.conversation_id\n" + + " LEFT JOIN blocks ON blocks.profileId = conversation_id\n" + + " LEFT JOIN banned ON banned.profileId = conversation_id\n" + + " WHERE banned.profileId is NULL AND unread >= :minUnreadCount AND is_group_chat in (:isGroupChat)\n" + + " AND (:minLastSeen = 0 OR seen > :minLastSeen)\n" + + " AND (1 IN (:isFavorite) AND 0 IN (:isFavorite) OR is_favorite in (:isFavorite))\n" + + " ORDER BY conversation.pin DESC, conversation.last_message_timestamp DESC, conversation.conversation_id DESC\n" + + " " + ) + + findAndHookMethod("androidx.room.RoomSQLiteQuery", + Hooker.pkgParam.classLoader, + "acquire", + String::class.java, + Int::class.javaPrimitiveType, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + val query = param.args[0] + param.args[0] = queries.getOrDefault(query, query) + } + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index b1e455c1..b8f8b87d 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -30,6 +30,15 @@ object Obfuscation { } } + object manager { + private const val _manager = Constants.GRINDR_PKG + ".manager" + const val BlockInteractor = "$_manager.o" + object BlockInteractor_ { + const val blockstream = "n" + const val processAndRemoveBlockedProfiles = "D" + } + } + object model { private const val _model = Constants.GRINDR_PKG + ".model" @@ -99,6 +108,11 @@ object Obfuscation { object ChatRepo_ { const val checkMessageForVideoCall = "checkMessageForVideoCall" } + + const val ProfileRepo = "$_repository.ProfileRepo" + object ProfileRepo_ { + const val delete = "delete" + } } } From 53f8d6010813fe6072e1be3850ca8b28cc520b4a Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sun, 23 Apr 2023 16:15:32 +0200 Subject: [PATCH 06/22] Fix preventRecordProfileViews --- .../java/com/eljaviluki/grindrplus/Hooker.kt | 11 +++- .../java/com/eljaviluki/grindrplus/Hooks.kt | 66 ++++++++++++++++--- .../com/eljaviluki/grindrplus/Obfuscation.kt | 25 +++++-- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 677c9534..16d356cb 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -103,11 +103,11 @@ class Hooker : IXposedHookLoadPackage { e.message?.let { Logger.xLog(it) } } - /*try { + try { Hooks.preventRecordProfileViews() } catch (e : Exception) { e.message?.let { Logger.xLog(it) } - }*/ + } try { Hooks.makeMessagesAlwaysRemovable() @@ -126,6 +126,12 @@ class Hooker : IXposedHookLoadPackage { } catch (e: Exception) { e.message?.let { Logger.xLog(it) } } + + try { + Hooks.localSavedPhrases() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } } } ) @@ -137,5 +143,6 @@ class Hooker : IXposedHookLoadPackage { var pkgParam: LoadPackageParam by InitOnce() var appContext: Context by InitOnce() var pkgVersionName: String by InitOnce() + val sharedPref by lazy { appContext.getSharedPreferences("phrases", Context.MODE_PRIVATE) } } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index f5a47b5d..22be1959 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -511,20 +511,22 @@ object Hooks { } fun preventRecordProfileViews() { - val class_Continuation = findClass( - "kotlin.coroutines.Continuation", - Hooker.pkgParam.classLoader - ) - - val class_GrindrRestService = - findClass(GApp.api.GrindrRestService, Hooker.pkgParam.classLoader) findAndHookMethod( - class_GrindrRestService, - GApp.api.GrindrRestService_.recordProfileViews, + GApp.ui.profileV2.CruiseProfilesViewModel, + Hooker.pkgParam.classLoader, + GApp.ui.profileV2.CruiseProfilesViewModel_.recordProfileViews, List::class.java, - class_Continuation, XC_MethodReplacement.DO_NOTHING ) + + findAndHookMethod( + GApp.persistence.repository.ProfileRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.ProfileRepo_.recordProfileView, + String::class.java, + "kotlin.coroutines.Continuation", + RETURN_UNIT + ) } fun makeMessagesAlwaysRemovable() { @@ -694,4 +696,48 @@ object Hooks { } }) } + + fun localSavedPhrases() { + /*val class_Continuation = findClass( + "kotlin.coroutines.Continuation", + Hooker.pkgParam.classLoader + ) + + findAndHookMethod( + GApp.interactor.phrase.PhraseInteractor, + Hooker.pkgParam.classLoader, + GApp.interactor.phrase.PhraseInteractor_.addSavedPhrase, + String::class.java, + Boolean::class.javaPrimitiveType, + Boolean::class.javaPrimitiveType, + "kotlin.jvm.functions.Function0", + class_Continuation, + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any { + XposedBridge.log(param.args[0].toString()) + Hooker.sharedPref.get + callMethod(param.args[3], "invoke") + return callStaticMethod( + findClass("com.grindrapp.android.network.either.b", Hooker.pkgParam.classLoader), + "b", + getStaticObjectField(findClass("kotlin.Unit", Hooker.pkgParam.classLoader), "INSTANCE") + ) + } + }) + + findAndHookMethod( + GApp.interactor.phrase.PhraseInteractor, + Hooker.pkgParam.classLoader, + GApp.interactor.phrase.PhraseInteractor_.deleteSavedPhrase, + String::class.java, + Boolean::class.javaPrimitiveType, + Boolean::class.javaPrimitiveType, + class_Continuation, + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any { + XposedBridge.log(param.args[0].toString()) + return Unit + } + })*/ + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index b8f8b87d..4df6abf3 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -4,11 +4,6 @@ object Obfuscation { object GApp { object api { private const val _api = Constants.GRINDR_PKG + ".api" - - const val GrindrRestService = "$_api.GrindrRestService" - object GrindrRestService_ { - const val recordProfileViews = "W" - } } object base { @@ -30,6 +25,20 @@ object Obfuscation { } } + object interactor { + private const val _interactor = Constants.GRINDR_PKG + ".interactor" + + object phrase { + private const val _phrase = "$_interactor.phrase" + + const val PhraseInteractor = "$_phrase.a" + object PhraseInteractor_ { + const val addSavedPhrase = "c" + const val deleteSavedPhrase = "e" + } + } + } + object manager { private const val _manager = Constants.GRINDR_PKG + ".manager" const val BlockInteractor = "$_manager.o" @@ -112,6 +121,7 @@ object Obfuscation { const val ProfileRepo = "$_repository.ProfileRepo" object ProfileRepo_ { const val delete = "delete" + const val recordProfileView = "recordProfileView" } } } @@ -151,6 +161,11 @@ object Obfuscation { object ProfileFieldsView_ { const val setProfile = "h" } + + const val CruiseProfilesViewModel = "$_profileV2.CruiseProfilesViewModel" + object CruiseProfilesViewModel_ { + const val recordProfileViews = "a1" + } } object chat { From 12a39c1ecb444c3f68692e7a211ff9025a218fab Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sun, 23 Apr 2023 17:41:36 +0200 Subject: [PATCH 07/22] Add localSavedPhrases --- .../java/com/eljaviluki/grindrplus/Hooks.kt | 175 ++++++++++++++---- .../com/eljaviluki/grindrplus/Obfuscation.kt | 29 +++ 2 files changed, 171 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 22be1959..6827a907 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -698,46 +698,155 @@ object Hooks { } fun localSavedPhrases() { - /*val class_Continuation = findClass( - "kotlin.coroutines.Continuation", - Hooker.pkgParam.classLoader + + val class_ChatRestService = + findClass(GApp.api.ChatRestService, Hooker.pkgParam.classLoader) + + val class_PhrasesRestService = + findClass(GApp.api.PhrasesRestService, Hooker.pkgParam.classLoader) + + val createSuccessResult = findMethodExact( + GApp.network.either.ResultHelper, + Hooker.pkgParam.classLoader, + GApp.network.either.ResultHelper_.createSuccess, + Any::class.java ) - findAndHookMethod( - GApp.interactor.phrase.PhraseInteractor, + val constructor_AddSavedPhraseResponse = findConstructorExact( + GApp.model.AddSavedPhraseResponse, + Hooker.pkgParam.classLoader, + String::class.java + ) + + val constructor_PhrasesResponse = findConstructorExact( + GApp.model.PhrasesResponse, + Hooker.pkgParam.classLoader, + Map::class.java + ) + + val constructor_Phrase = findConstructorExact( + GApp.persistence.model.Phrase, Hooker.pkgParam.classLoader, - GApp.interactor.phrase.PhraseInteractor_.addSavedPhrase, String::class.java, - Boolean::class.javaPrimitiveType, - Boolean::class.javaPrimitiveType, - "kotlin.jvm.functions.Function0", - class_Continuation, - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any { - XposedBridge.log(param.args[0].toString()) - Hooker.sharedPref.get - callMethod(param.args[3], "invoke") - return callStaticMethod( - findClass("com.grindrapp.android.network.either.b", Hooker.pkgParam.classLoader), - "b", - getStaticObjectField(findClass("kotlin.Unit", Hooker.pkgParam.classLoader), "INSTANCE") - ) - } - }) + String::class.java, + Long::class.javaPrimitiveType, + Int::class.javaPrimitiveType + ) findAndHookMethod( - GApp.interactor.phrase.PhraseInteractor, + "retrofit2.Retrofit", Hooker.pkgParam.classLoader, - GApp.interactor.phrase.PhraseInteractor_.deleteSavedPhrase, - String::class.java, - Boolean::class.javaPrimitiveType, - Boolean::class.javaPrimitiveType, - class_Continuation, - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any { - XposedBridge.log(param.args[0].toString()) - return Unit + "create", + Class::class.java, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val service = param.result + val proxyClass = service::class.java + when { + class_ChatRestService.isInstance(service) -> { + findAndHookMethod( + proxyClass, + GApp.api.ChatRestService_.addSavedPhrase, + GApp.model.AddSavedPhraseResponse, + "kotlin.coroutines.Continuation", + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any? { + val phrase = + getObjectField(param.args[0], "phrase") as String + val id = Hooker.sharedPref.getInt("id_counter", 0) + 1 + val currentPhrases = + Hooker.sharedPref.getStringSet("phrases", emptySet())!! + Hooker.sharedPref.edit() + .putInt("id_counter", id) + .putStringSet("phrases", currentPhrases + id.toString()) + .putString("phrase_${id}_text", phrase) + .putInt("phrase_${id}_frequency", 0) + .putLong("phrase_${id}_timestamp", 0) + .apply() + val response = + constructor_AddSavedPhraseResponse.newInstance(id.toString()) + return createSuccessResult.invoke(null, response) + } + } + ) + findAndHookMethod( + proxyClass, + GApp.api.ChatRestService_.deleteSavedPhrase, + String::class.java, + "kotlin.coroutines.Continuation", + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any? { + val id = param.args[0] as String + val currentPhrases = + Hooker.sharedPref.getStringSet("phrases", emptySet())!! + Hooker.sharedPref.edit() + .putStringSet("phrases", currentPhrases - id) + .remove("phrase_${id}_text") + .remove("phrase_${id}_frequency") + .remove("phrase_${id}_timestamp") + .apply() + return createSuccessResult.invoke(null, Unit) + } + } + ) + findAndHookMethod( + proxyClass, + GApp.api.ChatRestService_.increaseSavedPhraseClickCount, + String::class.java, + "kotlin.coroutines.Continuation", + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any? { + val id = param.args[0] as String + val currentFrequency = + Hooker.sharedPref.getInt("phrase_${id}_text", 0) + Hooker.sharedPref.edit() + .putInt("phrase_${id}_text", currentFrequency + 1) + .apply() + return createSuccessResult.invoke(null, Unit) + } + } + ) + } + class_PhrasesRestService.isInstance(service) -> { + findAndHookMethod( + proxyClass, + GApp.api.PhrasesRestService_.getSavedPhrases, + "kotlin.coroutines.Continuation", + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any? { + val phrases = + Hooker.sharedPref.getStringSet("phrases", emptySet())!! + .map { id -> + val text = Hooker.sharedPref.getString( + "phrase_${id}_text", + "" + ) + val timestamp = Hooker.sharedPref.getLong( + "phrase_${id}_timestamp", + 0 + ) + val frequency = Hooker.sharedPref.getInt( + "phrase_${id}_frequency", + 0 + ) + id to constructor_Phrase.newInstance( + id, + text, + timestamp, + frequency + ) + } + .toMap() + val phrasesResponse = + constructor_PhrasesResponse.newInstance(phrases) + return createSuccessResult.invoke(null, phrasesResponse) + } + } + ) + } + } } - })*/ + } + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 4df6abf3..60609358 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -4,6 +4,18 @@ object Obfuscation { object GApp { object api { private const val _api = Constants.GRINDR_PKG + ".api" + + const val ChatRestService = "$_api.ChatRestService" + object ChatRestService_ { + const val addSavedPhrase = "a" + const val deleteSavedPhrase = "r" + const val increaseSavedPhraseClickCount = "G" + } + + const val PhrasesRestService = "$_api.o" + object PhrasesRestService_ { + const val getSavedPhrases = "a" + } } object base { @@ -80,6 +92,22 @@ object Obfuscation { } const val ChatMessageParserCoroutine = "$_model.ChatMessageParser\$parseXmppMessage$2" + + const val AddSavedPhraseRequest = "$_model.AddSavedPhraseRequest" + const val AddSavedPhraseResponse = "$_model.AddSavedPhraseResponse" + const val PhrasesResponse = "$_model.PhrasesResponse" + } + object network { + private const val _network = Constants.GRINDR_PKG + ".network" + + object either { + private const val _either = "$_network.either" + + const val ResultHelper = "$_either.b" + object ResultHelper_ { + const val createSuccess = "b" + } + } } object persistence { @@ -108,6 +136,7 @@ object Obfuscation { } const val Profile = "$_model.Profile" + const val Phrase = "$_model.Phrase" } object repository { From 6fe63f8ea237a3e1ff8f934e47deeba7ccd3118a Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sun, 23 Apr 2023 18:24:38 +0200 Subject: [PATCH 08/22] Use Proxy for better compatibility --- .../java/com/eljaviluki/grindrplus/Hooks.kt | 200 +++++++++--------- 1 file changed, 97 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 6827a907..98776501 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -23,6 +23,7 @@ import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers.* +import java.lang.reflect.Proxy import kotlin.time.Duration object Hooks { @@ -733,6 +734,96 @@ object Hooks { Int::class.javaPrimitiveType ) + fun hookChatRestService(service: Any): Any { + val invocationHandler = Proxy.getInvocationHandler(service) + return Proxy.newProxyInstance( + Hooker.pkgParam.classLoader, + arrayOf(class_ChatRestService) + ) { proxy, method, args -> + when (method.name) { + GApp.api.ChatRestService_.addSavedPhrase -> { + val phrase = + getObjectField(args[0], "phrase") as String + val id = Hooker.sharedPref.getInt("id_counter", 0) + 1 + val currentPhrases = + Hooker.sharedPref.getStringSet("phrases", emptySet())!! + Hooker.sharedPref.edit() + .putInt("id_counter", id) + .putStringSet("phrases", currentPhrases + id.toString()) + .putString("phrase_${id}_text", phrase) + .putInt("phrase_${id}_frequency", 0) + .putLong("phrase_${id}_timestamp", 0) + .apply() + val response = + constructor_AddSavedPhraseResponse.newInstance(id.toString()) + createSuccessResult.invoke(null, response) + } + GApp.api.ChatRestService_.deleteSavedPhrase -> { + val id = args[0] as String + val currentPhrases = + Hooker.sharedPref.getStringSet("phrases", emptySet())!! + Hooker.sharedPref.edit() + .putStringSet("phrases", currentPhrases - id) + .remove("phrase_${id}_text") + .remove("phrase_${id}_frequency") + .remove("phrase_${id}_timestamp") + .apply() + createSuccessResult.invoke(null, Unit) + } + GApp.api.ChatRestService_.increaseSavedPhraseClickCount -> { + val id = args[0] as String + val currentFrequency = + Hooker.sharedPref.getInt("phrase_${id}_text", 0) + Hooker.sharedPref.edit() + .putInt("phrase_${id}_text", currentFrequency + 1) + .apply() + createSuccessResult.invoke(null, Unit) + } + else -> invocationHandler.invoke(proxy, method, args) + } + } + } + + fun hookPhrasesRestService(service: Any): Any { + val invocationHandler = Proxy.getInvocationHandler(service) + return Proxy.newProxyInstance( + Hooker.pkgParam.classLoader, + arrayOf(class_PhrasesRestService) + ) { proxy, method, args -> + when (method.name) { + GApp.api.PhrasesRestService_.getSavedPhrases -> { + val phrases = + Hooker.sharedPref.getStringSet("phrases", emptySet())!! + .map { id -> + val text = Hooker.sharedPref.getString( + "phrase_${id}_text", + "" + ) + val timestamp = Hooker.sharedPref.getLong( + "phrase_${id}_timestamp", + 0 + ) + val frequency = Hooker.sharedPref.getInt( + "phrase_${id}_frequency", + 0 + ) + id to constructor_Phrase.newInstance( + id, + text, + timestamp, + frequency + ) + } + .toMap() + val phrasesResponse = + constructor_PhrasesResponse.newInstance(phrases) + createSuccessResult.invoke(null, phrasesResponse) + } + else -> invocationHandler.invoke(proxy, method, args) + } + } + } + findAndHookMethod( "retrofit2.Retrofit", Hooker.pkgParam.classLoader, @@ -741,109 +832,12 @@ object Hooks { object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { val service = param.result - val proxyClass = service::class.java - when { - class_ChatRestService.isInstance(service) -> { - findAndHookMethod( - proxyClass, - GApp.api.ChatRestService_.addSavedPhrase, - GApp.model.AddSavedPhraseResponse, - "kotlin.coroutines.Continuation", - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any? { - val phrase = - getObjectField(param.args[0], "phrase") as String - val id = Hooker.sharedPref.getInt("id_counter", 0) + 1 - val currentPhrases = - Hooker.sharedPref.getStringSet("phrases", emptySet())!! - Hooker.sharedPref.edit() - .putInt("id_counter", id) - .putStringSet("phrases", currentPhrases + id.toString()) - .putString("phrase_${id}_text", phrase) - .putInt("phrase_${id}_frequency", 0) - .putLong("phrase_${id}_timestamp", 0) - .apply() - val response = - constructor_AddSavedPhraseResponse.newInstance(id.toString()) - return createSuccessResult.invoke(null, response) - } - } - ) - findAndHookMethod( - proxyClass, - GApp.api.ChatRestService_.deleteSavedPhrase, - String::class.java, - "kotlin.coroutines.Continuation", - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any? { - val id = param.args[0] as String - val currentPhrases = - Hooker.sharedPref.getStringSet("phrases", emptySet())!! - Hooker.sharedPref.edit() - .putStringSet("phrases", currentPhrases - id) - .remove("phrase_${id}_text") - .remove("phrase_${id}_frequency") - .remove("phrase_${id}_timestamp") - .apply() - return createSuccessResult.invoke(null, Unit) - } - } - ) - findAndHookMethod( - proxyClass, - GApp.api.ChatRestService_.increaseSavedPhraseClickCount, - String::class.java, - "kotlin.coroutines.Continuation", - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any? { - val id = param.args[0] as String - val currentFrequency = - Hooker.sharedPref.getInt("phrase_${id}_text", 0) - Hooker.sharedPref.edit() - .putInt("phrase_${id}_text", currentFrequency + 1) - .apply() - return createSuccessResult.invoke(null, Unit) - } - } - ) - } - class_PhrasesRestService.isInstance(service) -> { - findAndHookMethod( - proxyClass, - GApp.api.PhrasesRestService_.getSavedPhrases, - "kotlin.coroutines.Continuation", - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any? { - val phrases = - Hooker.sharedPref.getStringSet("phrases", emptySet())!! - .map { id -> - val text = Hooker.sharedPref.getString( - "phrase_${id}_text", - "" - ) - val timestamp = Hooker.sharedPref.getLong( - "phrase_${id}_timestamp", - 0 - ) - val frequency = Hooker.sharedPref.getInt( - "phrase_${id}_frequency", - 0 - ) - id to constructor_Phrase.newInstance( - id, - text, - timestamp, - frequency - ) - } - .toMap() - val phrasesResponse = - constructor_PhrasesResponse.newInstance(phrases) - return createSuccessResult.invoke(null, phrasesResponse) - } - } - ) - } + param.result = when { + class_ChatRestService.isInstance(service) -> hookChatRestService(service) + class_PhrasesRestService.isInstance(service) -> hookPhrasesRestService( + service + ) + else -> service } } } From 003225204f51c6b4e84d8dff2ce6d354ff512721 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sun, 23 Apr 2023 21:12:13 +0200 Subject: [PATCH 09/22] Add disableAnalytics --- .../java/com/eljaviluki/grindrplus/Hooker.kt | 6 +++ .../java/com/eljaviluki/grindrplus/Hooks.kt | 39 ++++++++++++++++++- .../com/eljaviluki/grindrplus/Obfuscation.kt | 2 + 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 16d356cb..af51c088 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -132,6 +132,12 @@ class Hooker : IXposedHookLoadPackage { } catch (e: Exception) { e.message?.let { Logger.xLog(it) } } + + try { + Hooks.disableAnalytics() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } } } ) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 98776501..901ca34b 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -699,7 +699,6 @@ object Hooks { } fun localSavedPhrases() { - val class_ChatRestService = findClass(GApp.api.ChatRestService, Hooker.pkgParam.classLoader) @@ -843,4 +842,42 @@ object Hooks { } ) } + + fun disableAnalytics() { + val class_AnalyticsRestService = + findClass(GApp.api.AnalyticsRestService, Hooker.pkgParam.classLoader) + + val createSuccessResult = findMethodExact( + GApp.network.either.ResultHelper, + Hooker.pkgParam.classLoader, + GApp.network.either.ResultHelper_.createSuccess, + Any::class.java + ) + + findAndHookMethod( + "retrofit2.Retrofit", + Hooker.pkgParam.classLoader, + "create", + Class::class.java, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val service = param.result + param.result = when { + class_AnalyticsRestService.isInstance(service) -> { + Proxy.newProxyInstance( + Hooker.pkgParam.classLoader, + arrayOf(class_AnalyticsRestService) + ) { proxy, method, args -> + XposedBridge.log(args[0].toString()) + //Just block all methods for now, + //in the future we might need to if they change service interface. + createSuccessResult(Unit) + } + } + else -> service + } + } + } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 60609358..669e3cce 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -16,6 +16,8 @@ object Obfuscation { object PhrasesRestService_ { const val getSavedPhrases = "a" } + + const val AnalyticsRestService = "$_api.d" } object base { From b1f52a1744cb43f81f53c8163a74dcf598f0219f Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sun, 23 Apr 2023 21:22:57 +0200 Subject: [PATCH 10/22] Fix typo --- app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 901ca34b..5f9f7e78 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -870,7 +870,7 @@ object Hooks { ) { proxy, method, args -> XposedBridge.log(args[0].toString()) //Just block all methods for now, - //in the future we might need to if they change service interface. + //in the future we might need to differentiate if they change service interface. createSuccessResult(Unit) } } From 84d9a9f95897bc878c3a3650388ef8e87a56f76e Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sun, 23 Apr 2023 22:12:37 +0200 Subject: [PATCH 11/22] Remove log statement --- app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 5f9f7e78..cdc1eb8b 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -842,7 +842,6 @@ object Hooks { } ) } - fun disableAnalytics() { val class_AnalyticsRestService = findClass(GApp.api.AnalyticsRestService, Hooker.pkgParam.classLoader) @@ -868,9 +867,8 @@ object Hooks { Hooker.pkgParam.classLoader, arrayOf(class_AnalyticsRestService) ) { proxy, method, args -> - XposedBridge.log(args[0].toString()) //Just block all methods for now, - //in the future we might need to differentiate if they change service interface. + //in the future we might need to differentiate if they change the service interface. createSuccessResult(Unit) } } From 713d86427cb88527034db69d2ca9518b85f092de Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Mon, 24 Apr 2023 11:49:08 +0200 Subject: [PATCH 12/22] Fix phrases frequency --- app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt | 1 + app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index af51c088..32db0024 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -5,6 +5,7 @@ import android.content.Context import android.widget.Toast import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers.* import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam import kotlin.time.Duration.Companion.minutes diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index cdc1eb8b..8fd8abd2 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -772,9 +772,9 @@ object Hooks { GApp.api.ChatRestService_.increaseSavedPhraseClickCount -> { val id = args[0] as String val currentFrequency = - Hooker.sharedPref.getInt("phrase_${id}_text", 0) + Hooker.sharedPref.getInt("phrase_${id}_frequency", 0) Hooker.sharedPref.edit() - .putInt("phrase_${id}_text", currentFrequency + 1) + .putInt("phrase_${id}_frequency", currentFrequency + 1) .apply() createSuccessResult.invoke(null, Unit) } From a6a95065706bff63194d76e41e67a0300a795cf8 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Tue, 25 Apr 2023 15:39:14 +0200 Subject: [PATCH 13/22] Update to Grindr 9.7.0 --- app/build.gradle | 7 +- app/src/main/AndroidManifest.xml | 3 +- .../java/com/eljaviluki/grindrplus/Hooker.kt | 43 ++++----- .../java/com/eljaviluki/grindrplus/Hooks.kt | 24 +++-- .../com/eljaviluki/grindrplus/Obfuscation.kt | 94 +++++++++++-------- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 100 insertions(+), 77 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 96f5fa17..2f1d274c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,9 +4,9 @@ plugins { } ext.versionMajor = 1 -ext.versionMinor = 1 -ext.versionPatch = 6 -ext.grindrVersion = '9.6.0' +ext.versionMinor = 2 +ext.versionPatch = 0 +ext.grindrVersion = '9.7.0' private String genVersionName() { String versionName = "${ext.versionMajor}.${ext.versionMinor}.${ext.versionPatch}" @@ -42,6 +42,7 @@ android { kotlinOptions { jvmTarget = '1.8' } + namespace 'com.eljaviluki.grindrplus' } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d2a581f8..c7776e9e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + + //val profile = Profile(it) + addProfileFieldUi("Profile ID", profileId, 0).also { view -> view.setOnLongClickListener { val clipboard = Hooker.appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Profile ID", profile.profileId) + val clip = ClipData.newPlainText("Profile ID", profileId) clipboard.setPrimaryClip(clip) Toast.makeText( Hooker.appContext, @@ -142,7 +145,7 @@ object Hooks { } } - addProfileFieldUi( + /*addProfileFieldUi( "Last Seen", if (profile.seen != 0L) Utils.toReadableDate(profile.seen) else "N/A", 1 @@ -153,7 +156,7 @@ object Hooks { "Body Mass Index", Utils.getBmiDescription(profile.weight, profile.height), 2 - ) + )*/ } //.setVisibility() of param.thisObject to always VISIBLE (otherwise if the profile has no fields, the additional ones will not be shown) @@ -513,9 +516,9 @@ object Hooks { fun preventRecordProfileViews() { findAndHookMethod( - GApp.ui.profileV2.CruiseProfilesViewModel, + GApp.ui.profileV2.ProfilesViewModel, Hooker.pkgParam.classLoader, - GApp.ui.profileV2.CruiseProfilesViewModel_.recordProfileViews, + GApp.ui.profileV2.ProfilesViewModel_.recordProfileViewsForViewedMeService, List::class.java, XC_MethodReplacement.DO_NOTHING ) @@ -842,6 +845,7 @@ object Hooks { } ) } + fun disableAnalytics() { val class_AnalyticsRestService = findClass(GApp.api.AnalyticsRestService, Hooker.pkgParam.classLoader) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 669e3cce..96900641 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -6,18 +6,27 @@ object Obfuscation { private const val _api = Constants.GRINDR_PKG + ".api" const val ChatRestService = "$_api.ChatRestService" + object ChatRestService_ { + //Annotated with @POST("v3/me/prefs/phrases") const val addSavedPhrase = "a" + + //Annotated with @DELETE("v3/me/prefs/phrases/{id}") const val deleteSavedPhrase = "r" + + //Annotated with @POST("v4/phrases/frequency/{id}") const val increaseSavedPhraseClickCount = "G" } - const val PhrasesRestService = "$_api.o" + const val PhrasesRestService = "$_api.n" + object PhrasesRestService_ { + //Annotated with @GET("v3/me/prefs"), returns PhrasesResponse const val getSavedPhrases = "a" } - const val AnalyticsRestService = "$_api.d" + //Contains @POST("/v3/logging/mobile/logs") + const val AnalyticsRestService = "$_api.c" } object base { @@ -34,30 +43,21 @@ object Obfuscation { private const val _experiment = Constants.GRINDR_PKG + ".experiment" const val Experiments = "$_experiment.f" + object Experiments_ { const val uncheckedIsEnabled_expMgr = "c" } } - object interactor { - private const val _interactor = Constants.GRINDR_PKG + ".interactor" - - object phrase { - private const val _phrase = "$_interactor.phrase" - - const val PhraseInteractor = "$_phrase.a" - object PhraseInteractor_ { - const val addSavedPhrase = "c" - const val deleteSavedPhrase = "e" - } - } - } - object manager { private const val _manager = Constants.GRINDR_PKG + ".manager" const val BlockInteractor = "$_manager.o" + object BlockInteractor_ { + //Calls blockstream$2 const val blockstream = "n" + + //Calls removeProfilesFromDbTables$2$1 const val processAndRemoveBlockedProfiles = "D" } } @@ -66,28 +66,33 @@ object Obfuscation { private const val _model = Constants.GRINDR_PKG + ".model" const val ExpiringImageBody = "$_model.ExpiringImageBody" + object ExpiringImageBody_ { const val getDuration = "getDuration" } const val ExpiringPhotoStatusResponse = "$_model.ExpiringPhotoStatusResponse" + object ExpiringPhotoStatusResponse_ { const val getTotal = "getTotal" const val getAvailable = "getAvailable" } const val Feature = "$_model.Feature" + object Feature_ { const val isGranted = "isGranted" } const val UpsellsV8 = "$_model.UpsellsV8" + object UpsellsV8_ { const val getMpuFree = "getMpuFree" const val getMpuXtra = "getMpuXtra" } const val Inserts = "$_model.Inserts" + object Inserts_ { const val getMpuFree = "getMpuFree" const val getMpuXtra = "getMpuXtra" @@ -99,6 +104,7 @@ object Obfuscation { const val AddSavedPhraseResponse = "$_model.AddSavedPhraseResponse" const val PhrasesResponse = "$_model.PhrasesResponse" } + object network { private const val _network = Constants.GRINDR_PKG + ".network" @@ -106,6 +112,7 @@ object Obfuscation { private const val _either = "$_network.either" const val ResultHelper = "$_either.b" + object ResultHelper_ { const val createSuccess = "b" } @@ -115,21 +122,11 @@ object Obfuscation { object persistence { private const val _persistence = Constants.GRINDR_PKG + ".persistence" - object cache { - private const val _cache = "$_persistence.cache" - - const val BlockedByHelper = "$_cache.BlockedByHelper" - object BlockByHelper_ { - const val addBlockByProfile = "addBlockByProfile" - const val removeBlockByProfile = "removeBlockByProfile" - } - - } - object model { private const val _model = "$_persistence.model" const val ChatMessage = "$_model.ChatMessage" + object ChatMessage_ { const val TAP_TYPE_NONE = "TAP_TYPE_NONE" const val getType = "getType" @@ -145,11 +142,13 @@ object Obfuscation { private const val _repository = "$_persistence.repository" const val ChatRepo = "$_repository.ChatRepo" + object ChatRepo_ { const val checkMessageForVideoCall = "checkMessageForVideoCall" } const val ProfileRepo = "$_repository.ProfileRepo" + object ProfileRepo_ { const val delete = "delete" const val recordProfileView = "recordProfileView" @@ -161,18 +160,20 @@ object Obfuscation { private const val _R = Constants.GRINDR_PKG const val color = "$_R.m0" + object color_ { - const val grindr_gold_star_gay = "F" - const val grindr_pure_white = "V" + const val grindr_gold_star_gay = "G" + const val grindr_pure_white = "W" } } object storage { private const val _storage = Constants.GRINDR_PKG + ".storage" - const val UserSession = "$_storage.z0" + const val UserSession = "$_storage.x0" const val IUserSession = "$_storage.UserSession" + object IUserSession_ { const val hasFeature_feature = "a" const val isFree = "r" @@ -189,13 +190,25 @@ object Obfuscation { private const val _profileV2 = "$_ui.profileV2" const val ProfileFieldsView = "$_profileV2.ProfileFieldsView" + object ProfileFieldsView_ { - const val setProfile = "h" + const val setProfile = "setProfile" } - const val CruiseProfilesViewModel = "$_profileV2.CruiseProfilesViewModel" - object CruiseProfilesViewModel_ { - const val recordProfileViews = "a1" + const val ProfilesViewModel = "$_profileV2.ProfilesViewModel" + + object ProfilesViewModel_ { + const val recordProfileViewsForViewedMeService = "X1" + } + + object model { + private const val _model = "$_profileV2.model" + + const val Profile = "$_model.h" + + object Profile_ { + const val getProfileId = "Z" + } } } @@ -203,8 +216,9 @@ object Obfuscation { private const val _chat = "$_ui.chat" const val ChatBaseFragmentV2 = "$_chat.ChatBaseFragmentV2" + object ChatBaseFragmentV2_ { - const val _canBeUnsent = "Y1" + const val _canBeUnsent = "X1" } } } @@ -212,22 +226,26 @@ object Obfuscation { object utils { private const val _utils = Constants.GRINDR_PKG + ".utils" - const val ProfileUtils = "$_utils.n0" + const val ProfileUtils = "$_utils.ProfileUtilsV2" + object ProfileUtils_ { - const val onlineIndicatorDuration = "b" + //Look for value of 600000 + const val onlineIndicatorDuration = "g" } } object view { private const val _view = Constants.GRINDR_PKG + ".view" - const val ExtendedProfileFieldView = "$_view.z4" + const val ExtendedProfileFieldView = "$_view.y4" + object ExtendedProfileFieldView_ { const val setLabel = "l" const val setValue = "n" } const val TapsAnimLayout = "$_view.TapsAnimLayout" + object TapsAnimLayout_ { const val tapType = "i" diff --git a/build.gradle b/build.gradle index 081a28e8..10511fd0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.1.0' apply false - id 'com.android.library' version '7.1.0' apply false + id 'com.android.application' version '7.4.2' apply false + id 'com.android.library' version '7.4.2' apply false id 'org.jetbrains.kotlin.android' version '1.6.21' apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 25dfd632..2f0e7a34 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Sep 17 23:10:56 CEST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From d3c500307aca94934c920768fe6c96a7e4147173 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 02:04:17 +0200 Subject: [PATCH 14/22] Add useThreeColumnLayoutForFavorites --- app/build.gradle | 2 +- .../java/com/eljaviluki/grindrplus/Hooker.kt | 6 + .../java/com/eljaviluki/grindrplus/Hooks.kt | 146 +++++++++++++++++- .../com/eljaviluki/grindrplus/Obfuscation.kt | 17 ++ 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2f1d274c..d62d7966 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { ext.versionMajor = 1 ext.versionMinor = 2 -ext.versionPatch = 0 +ext.versionPatch = 1 ext.grindrVersion = '9.7.0' private String genVersionName() { diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 0e9bee5d..897a8fb9 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -140,6 +140,12 @@ class Hooker : IXposedHookLoadPackage { } catch (e: Exception) { e.message?.let { Logger.xLog(it) } } + + try { + Hooks.useThreeColumnLayoutForFavorites() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } } } ) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 7ccec11b..99fc7159 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -4,13 +4,17 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Build +import android.os.Bundle import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity import android.view.View +import android.view.ViewGroup.LayoutParams import android.view.Window import android.view.WindowManager -import android.widget.FrameLayout -import android.widget.Toast +import android.widget.* import androidx.core.content.ContextCompat +import androidx.core.view.children import com.eljaviluki.grindrplus.Constants.Returns.RETURN_FALSE import com.eljaviluki.grindrplus.Constants.Returns.RETURN_INTEGER_MAX_VALUE import com.eljaviluki.grindrplus.Constants.Returns.RETURN_LONG_MAX_VALUE @@ -23,6 +27,7 @@ import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers.* import java.lang.reflect.Proxy +import kotlin.math.roundToInt import kotlin.time.Duration object Hooks { @@ -882,4 +887,141 @@ object Hooks { } ) } + + fun useThreeColumnLayoutForFavorites() { + val R_id = findClass( + GApp.R.id, + Hooker.pkgParam.classLoader + ) + + val recyclerViewId = getStaticIntField( + R_id, + GApp.R.id_.fragment_favorite_recycler_view + ) + val profileDistanceId = getStaticIntField( + R_id, + GApp.R.id_.profile_distance + ) + val profileOnlineNowIconId = getStaticIntField( + R_id, + GApp.R.id_.profile_online_now_icon + ) + val profileLastSeenId = getStaticIntField( + R_id, + GApp.R.id_.profile_last_seen + ) + val profileNoteIconId = getStaticIntField( + R_id, + GApp.R.id_.profile_note_icon + ) + val profileDisplayNameId = getStaticIntField( + R_id, + GApp.R.id_.profile_display_name + ) + + val Constructor_LayoutParamsRecyclerView = findConstructorExact( + "androidx.recyclerview.widget.RecyclerView\$LayoutParams", + Hooker.pkgParam.classLoader, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType + ) + + findAndHookMethod( + GApp.favorites.FavoritesFragment, + Hooker.pkgParam.classLoader, + "onViewCreated", + View::class.java, + Bundle::class.java, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val view = param.args[0] as View + val recyclerView = view.findViewById(recyclerViewId) + val gridLayoutManager = callMethod(recyclerView, "getLayoutManager") + callMethod(gridLayoutManager, "setSpanCount", 3) + + val adapter = callMethod(recyclerView, "getAdapter") + + findAndHookMethod( + adapter::class.java, + "onBindViewHolder", + "androidx.recyclerview.widget.RecyclerView\$ViewHolder", + Int::class.javaPrimitiveType, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + //Adjust grid item size + val size = + Hooker.appContext.resources.displayMetrics.widthPixels / 3 + val rootLayoutParams = + Constructor_LayoutParamsRecyclerView.newInstance( + size, + size + ) as LayoutParams + + val viewHolder = param.args[0] + val itemView = getObjectField(viewHolder, "itemView") as View + + itemView.layoutParams = rootLayoutParams + val distanceTextView = + itemView.findViewById(profileDistanceId) + + //Make online status and distance appear below each other + //because theres not enough space anymore to show them in a single row + val linearLayout = distanceTextView.parent as LinearLayout + linearLayout.orientation = LinearLayout.VERTICAL + + //Adjust layout params because of different orientation of LinearLayout + linearLayout.children.forEach { child -> + child.layoutParams = LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT + ) + } + + //Align distance TextView left now that it's displayed in its own row + distanceTextView.gravity = Gravity.START + + //Remove ugly margin before last seen text when online indicator is invisible + + val profileOnlineNowIcon = + itemView.findViewById(profileOnlineNowIconId) + val profileLastSeen = + itemView.findViewById(profileLastSeenId) + val lastSeenLayoutParams = + profileLastSeen.layoutParams as LinearLayout.LayoutParams + if (profileOnlineNowIcon.visibility == View.GONE) { + lastSeenLayoutParams.marginStart = 0 + } else { + lastSeenLayoutParams.marginStart = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 5f, + profileLastSeen.resources.displayMetrics + ).roundToInt() + } + profileLastSeen.layoutParams = lastSeenLayoutParams + + //Remove ugly margin before display name when note icon is invisible + + val profileNoteIcon = + itemView.findViewById(profileNoteIconId) + val profileDisplayName = + itemView.findViewById(profileDisplayNameId) + val displayNameLayoutParams = + profileDisplayName.layoutParams as LinearLayout.LayoutParams + if (profileNoteIcon.visibility == View.GONE) { + displayNameLayoutParams.marginStart = 0 + } else { + displayNameLayoutParams.marginStart = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 4f, + profileLastSeen.resources.displayMetrics + ).roundToInt() + } + profileDisplayName.layoutParams = displayNameLayoutParams + } + } + ) + } + } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 96900641..11f4c1f6 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -49,6 +49,12 @@ object Obfuscation { } } + object favorites { + private const val _favorites = Constants.GRINDR_PKG + ".favorites" + + const val FavoritesFragment = "$_favorites.FavoritesFragment" + } + object manager { private const val _manager = Constants.GRINDR_PKG + ".manager" const val BlockInteractor = "$_manager.o" @@ -165,6 +171,17 @@ object Obfuscation { const val grindr_gold_star_gay = "G" const val grindr_pure_white = "W" } + + const val id = "$_R.q0" + + object id_ { + const val fragment_favorite_recycler_view = "Ib" + const val profile_distance = "rk" + const val profile_online_now_icon = "pl" + const val profile_last_seen = "Tk" + const val profile_note_icon = "nl" + const val profile_display_name = "nk" + } } object storage { From bf316b34ceca211fb201cfe3204cbc895649ae01 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 03:01:49 +0200 Subject: [PATCH 15/22] Add disableAutomaticMessageDeletion --- app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt | 6 ++++++ app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt | 11 +++++++++++ .../java/com/eljaviluki/grindrplus/Obfuscation.kt | 1 + 3 files changed, 18 insertions(+) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 897a8fb9..7599b776 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -146,6 +146,12 @@ class Hooker : IXposedHookLoadPackage { } catch (e: Exception) { e.message?.let { Logger.xLog(it) } } + + try { + Hooks.disableAutomaticMessageDeletion() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } } } ) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 99fc7159..ad500fc1 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -1024,4 +1024,15 @@ object Hooks { } ) } + + fun disableAutomaticMessageDeletion() { + findAndHookMethod( + GApp.favorites.FavoritesFragment, + Hooker.pkgParam.classLoader, + "onViewCreated", + Long::class.java, + "kotlin.coroutines.Continuation", + Constants.Returns.RETURN_UNIT + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 11f4c1f6..c643118a 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -151,6 +151,7 @@ object Obfuscation { object ChatRepo_ { const val checkMessageForVideoCall = "checkMessageForVideoCall" + const val deleteChatMessageFromLessThanOrEqualToTimestamp = "deleteChatMessageFromLessThanOrEqualToTimestamp" } const val ProfileRepo = "$_repository.ProfileRepo" From 8bbc853b40a2d29a3000161f05e984f108db7086 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 03:04:34 +0200 Subject: [PATCH 16/22] Fix undo error ;) --- app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index ad500fc1..8363d43c 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -1027,12 +1027,12 @@ object Hooks { fun disableAutomaticMessageDeletion() { findAndHookMethod( - GApp.favorites.FavoritesFragment, + GApp.persistence.repository.ChatRepo, Hooker.pkgParam.classLoader, - "onViewCreated", + GApp.persistence.repository.ChatRepo_.deleteChatMessageFromLessThanOrEqualToTimestamp, Long::class.java, "kotlin.coroutines.Continuation", - Constants.Returns.RETURN_UNIT + RETURN_UNIT ) } } \ No newline at end of file From b595e3ad740adc17ee0db71d551d933bd4dd776d Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 04:25:52 +0200 Subject: [PATCH 17/22] Make showBlocksInChat more reliable --- .../java/com/eljaviluki/grindrplus/Hooks.kt | 57 ++++++++++--------- .../com/eljaviluki/grindrplus/Obfuscation.kt | 14 ++++- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 8363d43c..61c98802 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -587,35 +587,40 @@ object Hooks { */ fun showBlocksInChat() { - val chatMessage = findClass(GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader) - - findAndHookMethod(GApp.model.ChatMessageParserCoroutine, + val receiveChatMessage = findMethodExact( + GApp.xmpp.ChatMessageManager, Hooker.pkgParam.classLoader, - "invokeSuspend", - Any::class.java, + GApp.xmpp.ChatMessageManager_.handleChatMessage, + GApp.persistence.model.ChatMessage, + Boolean::class.javaPrimitiveType, + Boolean::class.javaPrimitiveType, + ) + + XposedBridge.hookMethod(receiveChatMessage, object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { - val result = param.result - if (!chatMessage.isInstance(result)) return - val type = - callMethod(result, GApp.persistence.model.ChatMessage_.getType) as String - when (type) { - "block" -> { - callMethod(result, GApp.persistence.model.ChatMessage_.setType, "text") - callMethod( - result, - GApp.persistence.model.ChatMessage_.setBody, - "[You have been blocked.]" - ) - } - "unblock" -> { - callMethod(result, GApp.persistence.model.ChatMessage_.setType, "text") - callMethod( - result, - GApp.persistence.model.ChatMessage_.setBody, - "[You have been unblocked.]" - ) - } + val chatMessage = param.args[0] + XposedBridge.log(chatMessage.toString()) + val type = callMethod(chatMessage, GApp.persistence.model.ChatMessage_.getType) as String + val syntheticMessage = when (type) { + "block" -> "[You have been blocked.]" + "unblock" -> "[You have been unblocked.]" + else -> null + } + if (syntheticMessage != null) { + val clone = callMethod(chatMessage, "clone") + callMethod(clone, GApp.persistence.model.ChatMessage_.setType, "text") + callMethod( + clone, + GApp.persistence.model.ChatMessage_.setBody, + syntheticMessage + ) + receiveChatMessage.invoke( + param.thisObject, + clone, + param.args[1], + param.args[2] + ) } } }) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index c643118a..4073bbf1 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -104,8 +104,6 @@ object Obfuscation { const val getMpuXtra = "getMpuXtra" } - const val ChatMessageParserCoroutine = "$_model.ChatMessageParser\$parseXmppMessage$2" - const val AddSavedPhraseRequest = "$_model.AddSavedPhraseRequest" const val AddSavedPhraseResponse = "$_model.AddSavedPhraseResponse" const val PhrasesResponse = "$_model.PhrasesResponse" @@ -138,6 +136,8 @@ object Obfuscation { const val getType = "getType" const val setType = "setType" const val setBody = "setBody" + + const val clone = "clone" } const val Profile = "$_model.Profile" @@ -272,5 +272,15 @@ object Obfuscation { const val setTapType = "S" } } + + object xmpp { + private const val _xmpp = Constants.GRINDR_PKG + ".xmpp" + + const val ChatMessageManager = "$_xmpp.ChatMessageManager" + + object ChatMessageManager_ { + const val handleChatMessage = "p" + } + } } } \ No newline at end of file From 2dc75a8a6a20ce12760774fce83e67465784e74e Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 06:26:25 +0200 Subject: [PATCH 18/22] Log outgoing blocks in chat --- .../java/com/eljaviluki/grindrplus/Hooks.kt | 94 ++++++++++++++++++- .../com/eljaviluki/grindrplus/Obfuscation.kt | 13 ++- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 61c98802..1f36927e 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -27,6 +27,7 @@ import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers.* import java.lang.reflect.Proxy +import java.util.* import kotlin.math.roundToInt import kotlin.time.Duration @@ -590,7 +591,7 @@ object Hooks { val receiveChatMessage = findMethodExact( GApp.xmpp.ChatMessageManager, Hooker.pkgParam.classLoader, - GApp.xmpp.ChatMessageManager_.handleChatMessage, + GApp.xmpp.ChatMessageManager_.handleIncomingChatMessage, GApp.persistence.model.ChatMessage, Boolean::class.javaPrimitiveType, Boolean::class.javaPrimitiveType, @@ -600,15 +601,18 @@ object Hooks { object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { val chatMessage = param.args[0] - XposedBridge.log(chatMessage.toString()) - val type = callMethod(chatMessage, GApp.persistence.model.ChatMessage_.getType) as String + val type = callMethod( + chatMessage, + GApp.persistence.model.ChatMessage_.getType + ) as String val syntheticMessage = when (type) { "block" -> "[You have been blocked.]" "unblock" -> "[You have been unblocked.]" else -> null } if (syntheticMessage != null) { - val clone = callMethod(chatMessage, "clone") + val clone = + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.clone) callMethod(clone, GApp.persistence.model.ChatMessage_.setType, "text") callMethod( clone, @@ -632,6 +636,66 @@ object Hooks { Hooker.pkgParam.classLoader ) + val Constructor_ChatMessage = findConstructorExact( + GApp.persistence.model.ChatMessage, + Hooker.pkgParam.classLoader + ) + + var ownProfileId: String? = null + + findAndHookMethod( + GApp.storage.UserSession, + Hooker.pkgParam.classLoader, + GApp.storage.IUserSession_.getProfileId, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + ownProfileId = param.result as String + } + } + ) + + var chatMessageManager: Any? = null + + XposedBridge.hookAllConstructors( + findClass( + GApp.xmpp.ChatMessageManager, + Hooker.pkgParam.classLoader + ), + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + chatMessageManager = param.thisObject + } + } + ) + + fun logChatMessage(from: String, text: String) { + val chatMessage = Constructor_ChatMessage.newInstance() + callMethod( + chatMessage, + GApp.persistence.model.ChatMessage_.setMessageId, + UUID.randomUUID().toString() + ) + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.setSender, ownProfileId) + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.setRecipient, from) + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.setStanzaId, from) + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.setConversationId, from) + callMethod( + chatMessage, + GApp.persistence.model.ChatMessage_.setTimestamp, + System.currentTimeMillis() + ) + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.setType, "text") + callMethod(chatMessage, GApp.persistence.model.ChatMessage_.setBody, text) + callMethod( + chatMessageManager, + GApp.xmpp.ChatMessageManager_.handleIncomingChatMessage, + chatMessage, + false, + false + ) + } + + findAndHookMethod( GApp.manager.BlockInteractor, Hooker.pkgParam.classLoader, @@ -639,7 +703,27 @@ object Hooks { String::class.java, Boolean::class.javaPrimitiveType, class_Continuation, - RETURN_UNIT + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any { + val otherProfileId = param.args[0] as String + logChatMessage(otherProfileId, "[You have blocked this profile.]") + return Unit + } + } + ) + + findAndHookMethod( + GApp.manager.BlockInteractor, + Hooker.pkgParam.classLoader, + GApp.manager.BlockInteractor_.unblockProfile, + String::class.java, + class_Continuation, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val otherProfileId = param.args[0] as String + logChatMessage(otherProfileId, "[You have unblocked this profile.]") + } + } ) findAndHookMethod( diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 4073bbf1..33945d79 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -65,6 +65,9 @@ object Obfuscation { //Calls removeProfilesFromDbTables$2$1 const val processAndRemoveBlockedProfiles = "D" + + //Calls unblockProfile$2 + const val unblockProfile = "G" } } @@ -135,6 +138,12 @@ object Obfuscation { const val TAP_TYPE_NONE = "TAP_TYPE_NONE" const val getType = "getType" const val setType = "setType" + const val setMessageId = "setMessageId" + const val setSender = "setSender" + const val setRecipient = "setRecipient" + const val setStanzaId = "setStanzaId" + const val setConversationId = "setConversationId" + const val setTimestamp = "setTimestamp" const val setBody = "setBody" const val clone = "clone" @@ -198,6 +207,7 @@ object Obfuscation { const val isNoXtraUpsell = "g" const val isXtra = "o" const val isUnlimited = "x" + const val getProfileId = "e" } } @@ -279,7 +289,8 @@ object Obfuscation { const val ChatMessageManager = "$_xmpp.ChatMessageManager" object ChatMessageManager_ { - const val handleChatMessage = "p" + const val handleIncomingChatMessage = "p" + const val handleRetryChatMessage = "B" } } } From a8f67bf119f2316825c745bbf5ae46fdd735c3c0 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 08:26:38 +0200 Subject: [PATCH 19/22] Overhaul blocking hooks --- app/build.gradle | 2 +- .../java/com/eljaviluki/grindrplus/Hooks.kt | 123 +++++++++++++----- .../com/eljaviluki/grindrplus/Obfuscation.kt | 46 +++++-- 3 files changed, 130 insertions(+), 41 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d62d7966..9c6f0489 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { ext.versionMajor = 1 ext.versionMinor = 2 -ext.versionPatch = 1 +ext.versionPatch = 2 ext.grindrVersion = '9.7.0' private String genVersionName() { diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 1f36927e..8e7bcf82 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -628,13 +628,8 @@ object Hooks { } } }) - } - fun keepChatsOfBlockedProfiles() { - val class_Continuation = findClass( - "kotlin.coroutines.Continuation", - Hooker.pkgParam.classLoader - ) + val Constructor_ChatMessage = findConstructorExact( GApp.persistence.model.ChatMessage, @@ -695,45 +690,62 @@ object Hooks { ) } - findAndHookMethod( - GApp.manager.BlockInteractor, + GApp.persistence.repository.BlockRepo, Hooker.pkgParam.classLoader, - GApp.manager.BlockInteractor_.blockstream, - String::class.java, - Boolean::class.javaPrimitiveType, - class_Continuation, - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any { - val otherProfileId = param.args[0] as String + GApp.persistence.repository.BlockRepo_.add, + GApp.persistence.model.BlockedProfile, + "kotlin.coroutines.Continuation", + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val otherProfileId = callMethod(param.args[0], GApp.persistence.model.BlockedProfile_.getProfileId) as String logChatMessage(otherProfileId, "[You have blocked this profile.]") - return Unit } } ) findAndHookMethod( - GApp.manager.BlockInteractor, + GApp.persistence.repository.BlockRepo, Hooker.pkgParam.classLoader, - GApp.manager.BlockInteractor_.unblockProfile, + GApp.persistence.repository.BlockRepo_.delete, String::class.java, - class_Continuation, + "kotlin.coroutines.Continuation", object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { - val otherProfileId = param.args[0] as String - logChatMessage(otherProfileId, "[You have unblocked this profile.]") + val otherProfileId = param.args[0] as? String + if (otherProfileId != null) { + logChatMessage(otherProfileId, "[You have unblocked this profile.]") + } } } ) + } + + fun keepChatsOfBlockedProfiles() { + val ignoreIfBlockInteractor = object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any { + //We still want to allow deleting chats etc., + //so only ignore if BlockInteractor is calling + val isBlockInteractor = + Thread.currentThread().stackTrace.any { it.className.contains(GApp.manager.BlockInteractor) } + if (isBlockInteractor) { + return Unit + } + return XposedBridge.invokeOriginalMethod( + param.method, + param.thisObject, + param.args + ) + } + } findAndHookMethod( - GApp.manager.BlockInteractor, + GApp.persistence.repository.ProfileRepo, Hooker.pkgParam.classLoader, - GApp.manager.BlockInteractor_.processAndRemoveBlockedProfiles, - List::class.java, - Boolean::class.javaPrimitiveType, - class_Continuation, - RETURN_UNIT + GApp.persistence.repository.ProfileRepo_.delete, + String::class.java, + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor ) findAndHookMethod( @@ -741,8 +753,61 @@ object Hooks { Hooker.pkgParam.classLoader, GApp.persistence.repository.ProfileRepo_.delete, List::class.java, - class_Continuation, - RETURN_UNIT + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor + ) + + findAndHookMethod( + GApp.persistence.repository.ConversationRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.ConversationRepo_.deleteConversation, + String::class.java, + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor + ) + + findAndHookMethod( + GApp.persistence.repository.ConversationRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.ConversationRepo_.deleteConversations, + List::class.java, + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor + ) + + findAndHookMethod( + GApp.persistence.repository.ChatRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.ChatRepo_.deleteChatMessageFromConversationId, + String::class.java, + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor + ) + + findAndHookMethod( + GApp.persistence.repository.ChatRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.ChatRepo_.deleteChatMessageListFromConversationId, + List::class.java, + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor + ) + + findAndHookMethod( + GApp.persistence.repository.IncomingChatMarkerRepo, + Hooker.pkgParam.classLoader, + GApp.persistence.repository.IncomingChatMarkerRepo_.deleteIncomingChatMarker, + String::class.java, + "kotlin.coroutines.Continuation", + ignoreIfBlockInteractor + ) + + findAndHookMethod( + GApp.ui.chat.individual.ChatIndividualFragment, + Hooker.pkgParam.classLoader, + GApp.ui.chat.individual.ChatIndividualFragment_.showBlockDialog, + Boolean::class.javaPrimitiveType, + XC_MethodReplacement.DO_NOTHING ) val queries = mapOf( diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 33945d79..a3006324 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -58,17 +58,6 @@ object Obfuscation { object manager { private const val _manager = Constants.GRINDR_PKG + ".manager" const val BlockInteractor = "$_manager.o" - - object BlockInteractor_ { - //Calls blockstream$2 - const val blockstream = "n" - - //Calls removeProfilesFromDbTables$2$1 - const val processAndRemoveBlockedProfiles = "D" - - //Calls unblockProfile$2 - const val unblockProfile = "G" - } } object model { @@ -149,6 +138,12 @@ object Obfuscation { const val clone = "clone" } + const val BlockedProfile = "$_model.BlockedProfile" + + object BlockedProfile_ { + const val getProfileId = "getProfileId" + } + const val Profile = "$_model.Profile" const val Phrase = "$_model.Phrase" } @@ -161,6 +156,8 @@ object Obfuscation { object ChatRepo_ { const val checkMessageForVideoCall = "checkMessageForVideoCall" const val deleteChatMessageFromLessThanOrEqualToTimestamp = "deleteChatMessageFromLessThanOrEqualToTimestamp" + const val deleteChatMessageFromConversationId = "deleteChatMessageFromConversationId" + const val deleteChatMessageListFromConversationId = "deleteChatMessageListFromConversationId" } const val ProfileRepo = "$_repository.ProfileRepo" @@ -169,6 +166,23 @@ object Obfuscation { const val delete = "delete" const val recordProfileView = "recordProfileView" } + + const val ConversationRepo = "$_repository.ConversationRepo" + object ConversationRepo_ { + const val deleteConversation = "deleteConversation" + const val deleteConversations = "deleteConversations" + } + + const val IncomingChatMarkerRepo = "$_repository.IncomingChatMarkerRepo" + object IncomingChatMarkerRepo_ { + const val deleteIncomingChatMarker = "deleteIncomingChatMarker" + } + + const val BlockRepo = "$_repository.BlockRepo" + object BlockRepo_ { + const val add = "add" + const val delete = "delete" + } } } @@ -248,6 +262,16 @@ object Obfuscation { object ChatBaseFragmentV2_ { const val _canBeUnsent = "X1" } + + object individual { + private const val _individual = "$_chat.individual" + + const val ChatIndividualFragment = "$_individual.ChatIndividualFragment" + + object ChatIndividualFragment_ { + const val showBlockDialog = "A3" + } + } } } From 3dd1b29b48d7417f56404ca20e37c2a9d42842ce Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Fri, 28 Apr 2023 15:50:07 +0200 Subject: [PATCH 20/22] Add dontSendChatMarkers and dontSendTypingIndicator --- app/build.gradle | 2 +- .../java/com/eljaviluki/grindrplus/Hooker.kt | 12 +++++++ .../java/com/eljaviluki/grindrplus/Hooks.kt | 35 +++++++++++++++++-- .../com/eljaviluki/grindrplus/Obfuscation.kt | 1 - 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9c6f0489..cdbf8578 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { ext.versionMajor = 1 ext.versionMinor = 2 -ext.versionPatch = 2 +ext.versionPatch = 3 ext.grindrVersion = '9.7.0' private String genVersionName() { diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt index 7599b776..2574249d 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooker.kt @@ -152,6 +152,18 @@ class Hooker : IXposedHookLoadPackage { } catch (e: Exception) { e.message?.let { Logger.xLog(it) } } + + try { + Hooks.dontSendChatMarkers() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } + + try { + Hooks.dontSendTypingIndicator() + } catch (e: Exception) { + e.message?.let { Logger.xLog(it) } + } } } ) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 8e7bcf82..7f70f57d 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -630,7 +630,6 @@ object Hooks { }) - val Constructor_ChatMessage = findConstructorExact( GApp.persistence.model.ChatMessage, Hooker.pkgParam.classLoader @@ -698,7 +697,10 @@ object Hooks { "kotlin.coroutines.Continuation", object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { - val otherProfileId = callMethod(param.args[0], GApp.persistence.model.BlockedProfile_.getProfileId) as String + val otherProfileId = callMethod( + param.args[0], + GApp.persistence.model.BlockedProfile_.getProfileId + ) as String logChatMessage(otherProfileId, "[You have blocked this profile.]") } } @@ -1189,4 +1191,33 @@ object Hooks { RETURN_UNIT ) } + + fun dontSendChatMarkers() { + findAndHookMethod( + "org.jivesoftware.smack.packet.Stanza", + Hooker.pkgParam.classLoader, + "addExtension", + "org.jivesoftware.smack.packet.ExtensionElement", + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + if (param.args[0] == null) return + val elementName = callMethod(param.args[0], "getElementName") as String + if (elementName in arrayOf("received", "displayed")) { + param.args[0] = null + } + } + } + ) + } + + fun dontSendTypingIndicator() { + findAndHookMethod( + "org.jivesoftware.smackx.chatstates.ChatStateManager", + Hooker.pkgParam.classLoader, + "setCurrentState", + "org.jivesoftware.smackx.chatstates.ChatState", + "org.jivesoftware.smack.chat2.Chat", + XC_MethodReplacement.DO_NOTHING + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index a3006324..48ee98ad 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -314,7 +314,6 @@ object Obfuscation { object ChatMessageManager_ { const val handleIncomingChatMessage = "p" - const val handleRetryChatMessage = "B" } } } From 7f21e99e1c538827cc0753f91ae9dee4e385c6ce Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sat, 29 Apr 2023 04:28:26 +0200 Subject: [PATCH 21/22] Fix don't delete profile when blocking --- app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt | 5 ++++- app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index 7f70f57d..eb3d4bbc 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -729,7 +729,10 @@ object Hooks { //We still want to allow deleting chats etc., //so only ignore if BlockInteractor is calling val isBlockInteractor = - Thread.currentThread().stackTrace.any { it.className.contains(GApp.manager.BlockInteractor) } + Thread.currentThread().stackTrace.any { + it.className.contains(GApp.manager.BlockInteractor) || + it.className.contains(GApp.ui.chat.BlockViewModel) + } if (isBlockInteractor) { return Unit } diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 48ee98ad..81e4c712 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -257,6 +257,8 @@ object Obfuscation { object chat { private const val _chat = "$_ui.chat" + const val BlockViewModel = "$_chat.BlockViewModel" + const val ChatBaseFragmentV2 = "$_chat.ChatBaseFragmentV2" object ChatBaseFragmentV2_ { From 81ad2e778027368ad8f882cf3cc8119e7a552aaa Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Sat, 29 Apr 2023 08:54:12 +0200 Subject: [PATCH 22/22] Don't block incoming chat markers --- app/build.gradle | 2 +- .../java/com/eljaviluki/grindrplus/Hooks.kt | 25 ++++++++++--------- .../com/eljaviluki/grindrplus/Obfuscation.kt | 7 ++++++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cdbf8578..dc210961 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { ext.versionMajor = 1 ext.versionMinor = 2 -ext.versionPatch = 3 +ext.versionPatch = 4 ext.grindrVersion = '9.7.0' private String genVersionName() { diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt index eb3d4bbc..d4e5c199 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Hooks.kt @@ -1197,19 +1197,20 @@ object Hooks { fun dontSendChatMarkers() { findAndHookMethod( - "org.jivesoftware.smack.packet.Stanza", + GApp.xmpp.ChatMarkersManager, Hooker.pkgParam.classLoader, - "addExtension", - "org.jivesoftware.smack.packet.ExtensionElement", - object : XC_MethodHook() { - override fun beforeHookedMethod(param: MethodHookParam) { - if (param.args[0] == null) return - val elementName = callMethod(param.args[0], "getElementName") as String - if (elementName in arrayOf("received", "displayed")) { - param.args[0] = null - } - } - } + GApp.xmpp.ChatMarkersManager_.addDisplayedExtension, + "org.jivesoftware.smack.chat2.Chat", + "org.jivesoftware.smack.packet.Message", + XC_MethodReplacement.DO_NOTHING + ) + findAndHookMethod( + GApp.xmpp.ChatMarkersManager, + Hooker.pkgParam.classLoader, + GApp.xmpp.ChatMarkersManager_.addReceivedExtension, + "org.jivesoftware.smack.chat2.Chat", + "org.jivesoftware.smack.packet.Message", + XC_MethodReplacement.DO_NOTHING ) } diff --git a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt index 81e4c712..a959dc6e 100644 --- a/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt +++ b/app/src/main/java/com/eljaviluki/grindrplus/Obfuscation.kt @@ -317,6 +317,13 @@ object Obfuscation { object ChatMessageManager_ { const val handleIncomingChatMessage = "p" } + + const val ChatMarkersManager = "$_xmpp.i" + + object ChatMarkersManager_ { + const val addDisplayedExtension = "d" + const val addReceivedExtension = "f" + } } } } \ No newline at end of file