From 523f0a342b0552e1af456b54ef47a042398c7ff9 Mon Sep 17 00:00:00 2001 From: Se7en-dev Date: Thu, 2 Jul 2026 15:49:18 +0200 Subject: [PATCH] feat(patch): waze integration patch --- .../patcher/steps/patch/PatchManifestStep.kt | 5 + .../rlmobile/patcher/util/ManifestPatcher.kt | 97 ++++++++ .../ui/screens/patchopts/KnownPatch.kt | 11 + Manager/app/src/main/res/values/strings.xml | 2 + patches/data.json | 2 +- .../extension/radiant/WazeInitReceiver.smali | 87 +++++++ .../radiant/WazeServiceConnection.smali | 222 ++++++++++++++++++ patches/manifest.json | 9 + patches/waze-media-browser.patch | 27 +++ 9 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 patches/extension/radiant/WazeInitReceiver.smali create mode 100644 patches/extension/radiant/WazeServiceConnection.smali create mode 100644 patches/waze-media-browser.patch diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt index 37d2334..e360f2d 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt @@ -6,6 +6,7 @@ import com.meowarex.rlmobile.patcher.steps.StepGroup import com.meowarex.rlmobile.patcher.steps.base.Step import com.meowarex.rlmobile.patcher.steps.download.CopyDependenciesStep import com.meowarex.rlmobile.patcher.util.ManifestPatcher +import com.meowarex.rlmobile.ui.screens.patchopts.KnownPatch import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions import com.github.diamondminer88.zip.ZipReader import com.github.diamondminer88.zip.ZipWriter @@ -26,11 +27,15 @@ class PatchManifestStep(private val options: PatchOptions) : Step() { ?: throw IllegalArgumentException("No manifest found in APK") container.log("Patching manifest") + val enableWazeIntegration = + options.patchStates[KnownPatch.WazeIntegration.name] ?: KnownPatch.WazeIntegration.default.isEnabled + val patchedManifest = ManifestPatcher.patchManifest( manifestBytes = manifest, packageName = options.packageName, appName = options.appName, debuggable = options.debuggable, + enableWazeIntegration = enableWazeIntegration, ) container.log("Repacking apk with patched manifest") diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt index bc53f2f..81a625e 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt @@ -18,12 +18,16 @@ object ManifestPatcher { private const val IS_SPLIT_REQUIRED = "isSplitRequired" private const val REQUIRED_SPLIT_TYPES = "requiredSplitTypes" private const val SPLIT_TYPES = "splitTypes" + private const val WAZE_PACKAGE = "com.waze" + private const val WAZE_INIT_RECEIVER = "radiant.WazeInitReceiver" + private const val WAZE_ACTION_INIT = "com.waze.sdk.audio.ACTION_INIT" fun patchManifest( manifestBytes: ByteArray, packageName: String, appName: String, debuggable: Boolean, + enableWazeIntegration: Boolean, ): ByteArray { // Extract original package name to rewrite every reference to it // (permissions, provider authorities) to the new packageName. @@ -84,6 +88,49 @@ object ManifestPatcher { } return when (name) { + "queries" -> object : NodeVisitor(nv) { + private var hasWazePackage = false + + override fun child(ns: String?, name: String): NodeVisitor { + val visitor = super.child(ns, name) + + return if (enableWazeIntegration && name == "package") { + object : NodeVisitor(visitor) { + override fun attr( + ns: String?, + name: String?, + resourceId: Int, + type: Int, + value: Any?, + ) { + if (name == "name" && value == WAZE_PACKAGE) { + hasWazePackage = true + } + super.attr(ns, name, resourceId, type, value) + } + } + } else { + visitor + } + } + + override fun end() { + if (enableWazeIntegration && !hasWazePackage) { + super.child(null, "package").apply { + attr( + ANDROID_NAMESPACE, + "name", + android.R.attr.name, + TYPE_STRING, + WAZE_PACKAGE, + ) + end() + } + } + super.end() + } + } + "uses-permission" -> object : NodeVisitor(nv) { override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) { if (name != "maxSdkVersion") { @@ -132,6 +179,7 @@ object ManifestPatcher { private var addUseEmbeddedDex = true private var addExtractNativeLibs = true private var addMetadata = true + private var hasWazeInitReceiver = false override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false @@ -161,6 +209,22 @@ object ManifestPatcher { return when (name) { "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName)) + + "receiver" -> object : NodeVisitor(visitor) { + override fun attr( + ns: String?, + name: String, + resourceId: Int, + type: Int, + value: Any? + ) { + if (name == "name" && value == WAZE_INIT_RECEIVER) { + hasWazeInitReceiver = true + } + super.attr(ns, name, resourceId, type, value) + } + } + "provider" -> object : NodeVisitor(visitor) { override fun attr( ns: String?, @@ -219,6 +283,39 @@ object ManifestPatcher { 0 ) + if (enableWazeIntegration && !hasWazeInitReceiver) { + super.child(null, "receiver").apply { + attr( + ANDROID_NAMESPACE, + "name", + android.R.attr.name, + TYPE_STRING, + WAZE_INIT_RECEIVER, + ) + attr( + ANDROID_NAMESPACE, + "exported", + android.R.attr.exported, + TYPE_INT_BOOLEAN, + 1, + ) + child(null, "intent-filter").apply { + child(null, "action").apply { + attr( + ANDROID_NAMESPACE, + "name", + android.R.attr.name, + TYPE_STRING, + WAZE_ACTION_INIT, + ) + end() + } + end() + } + end() + } + } + super.end() } } diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt index 212fe07..3f1c27e 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt @@ -130,6 +130,17 @@ enum class KnownPatch( descRes = R.string.patch_debug_menu_unlock_desc, default = Disabled, ), + WazeIntegration( + order = 90, + fileNames = listOf("waze-media-browser.patch"), + extensionFiles = listOf( + "radiant/WazeInitReceiver.smali", + "radiant/WazeServiceConnection.smali", + ), + titleRes = R.string.patch_waze_integration_title, + descRes = R.string.patch_waze_integration_desc, + default = Disabled, + ), LyricsProgressPill( order = 40, fileNames = listOf( diff --git a/Manager/app/src/main/res/values/strings.xml b/Manager/app/src/main/res/values/strings.xml index 2aaa020..a6db93e 100644 --- a/Manager/app/src/main/res/values/strings.xml +++ b/Manager/app/src/main/res/values/strings.xml @@ -286,6 +286,8 @@ Reveals TIDAL\'s hidden Debug Menu in Settings. (Unlocks Feature Flags & the legacy Debug Options) + Waze Integration + Control TIDAL playback from the Waze app Enable Legacy UI ()V + .locals 0 + + invoke-direct {p0}, Landroid/content/BroadcastReceiver;->()V + + return-void +.end method + + +# virtual methods +.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V + .locals 5 + + if-eqz p1, :return + + if-eqz p2, :return + + const-string v0, "com.waze.sdk.audio.ACTION_INIT" + + invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String; + + move-result-object v1 + + invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + + move-result v0 + + if-eqz v0, :return + + const-string v0, "token" + + invoke-virtual {p2, v0}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String; + + move-result-object v0 + + if-eqz v0, :return + + invoke-virtual {v0}, Ljava/lang/String;->length()I + + move-result v1 + + if-lez v1, :return + + invoke-virtual {p1}, Landroid/content/Context;->getApplicationContext()Landroid/content/Context; + + move-result-object v1 + + if-eqz v1, :context_ready + + move-object p1, v1 + + :context_ready + new-instance v1, Landroid/content/Intent; + + invoke-direct {v1}, Landroid/content/Intent;->()V + + const-string v2, "com.waze" + + const-string v3, "com.waze.sdk.SdkService" + + invoke-virtual {v1, v2, v3}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent; + + new-instance v2, Lradiant/WazeServiceConnection; + + invoke-direct {v2, p1, v0}, Lradiant/WazeServiceConnection;->(Landroid/content/Context;Ljava/lang/String;)V + + :try_start_0 + const/4 v3, 0x1 + + invoke-virtual {p1, v1, v2, v3}, Landroid/content/Context;->bindService(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z + :try_end_0 + .catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0 + + goto :return + + :catch_0 + move-exception v4 + + :return + return-void +.end method diff --git a/patches/extension/radiant/WazeServiceConnection.smali b/patches/extension/radiant/WazeServiceConnection.smali new file mode 100644 index 0000000..74ca8c1 --- /dev/null +++ b/patches/extension/radiant/WazeServiceConnection.smali @@ -0,0 +1,222 @@ +.class public final Lradiant/WazeServiceConnection; +.super Ljava/lang/Object; +.source "WazeServiceConnection.smali" + +# interfaces +.implements Landroid/content/ServiceConnection; + + +# instance fields +.field private final context:Landroid/content/Context; + +.field private final token:Ljava/lang/String; + + +# direct methods +.method public constructor (Landroid/content/Context;Ljava/lang/String;)V + .locals 0 + + invoke-direct {p0}, Ljava/lang/Object;->()V + + iput-object p1, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context; + + iput-object p2, p0, Lradiant/WazeServiceConnection;->token:Ljava/lang/String; + + return-void +.end method + +.method private addOpenMeIntent(Landroid/os/Bundle;)V + .locals 4 + + iget-object v0, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context; + + invoke-virtual {v0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager; + + move-result-object v1 + + invoke-virtual {v0}, Landroid/content/Context;->getPackageName()Ljava/lang/String; + + move-result-object v2 + + invoke-virtual {v1, v2}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent; + + move-result-object v1 + + if-eqz v1, :return + + const/high16 v2, 0x10000000 + + invoke-virtual {v1, v2}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent; + + const v2, 0xc000000 + + const/4 v3, 0x0 + + invoke-static {v0, v3, v1, v2}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent; + + move-result-object v1 + + const-string v2, "openMeIntent" + + invoke-virtual {p1, v2, v1}, Landroid/os/Bundle;->putParcelable(Ljava/lang/String;Landroid/os/Parcelable;)V + + :return + return-void +.end method + +.method private createClientMessenger()Landroid/os/Messenger; + .locals 3 + + new-instance v0, Landroid/os/Handler; + + invoke-static {}, Landroid/os/Looper;->getMainLooper()Landroid/os/Looper; + + move-result-object v1 + + invoke-direct {v0, v1}, Landroid/os/Handler;->(Landroid/os/Looper;)V + + new-instance v2, Landroid/os/Messenger; + + invoke-direct {v2, v0}, Landroid/os/Messenger;->(Landroid/os/Handler;)V + + return-object v2 +.end method + +.method private createConfigBundle()Landroid/os/Bundle; + .locals 3 + + new-instance v0, Landroid/os/Bundle; + + invoke-direct {v0}, Landroid/os/Bundle;->()V + + const-string v1, "versionCode" + + const/16 v2, 0xa + + invoke-virtual {v0, v1, v2}, Landroid/os/Bundle;->putInt(Ljava/lang/String;I)V + + invoke-direct {p0, v0}, Lradiant/WazeServiceConnection;->addOpenMeIntent(Landroid/os/Bundle;)V + + return-object v0 +.end method + +.method private register(Landroid/os/IBinder;)V + .locals 5 + + if-eqz p1, :return + + invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel; + + move-result-object v0 + + invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel; + + move-result-object v1 + + const-string v2, "com.waze.sdk.ISdkService" + + invoke-virtual {v0, v2}, Landroid/os/Parcel;->writeInterfaceToken(Ljava/lang/String;)V + + iget-object v2, p0, Lradiant/WazeServiceConnection;->token:Ljava/lang/String; + + invoke-virtual {v0, v2}, Landroid/os/Parcel;->writeString(Ljava/lang/String;)V + + invoke-direct {p0}, Lradiant/WazeServiceConnection;->createConfigBundle()Landroid/os/Bundle; + + move-result-object v2 + + invoke-static {v0, v2}, Lradiant/WazeServiceConnection;->writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V + + invoke-direct {p0}, Lradiant/WazeServiceConnection;->createClientMessenger()Landroid/os/Messenger; + + move-result-object v2 + + invoke-static {v0, v2}, Lradiant/WazeServiceConnection;->writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V + + const/4 v2, 0x1 + + const/4 v3, 0x0 + + invoke-interface {p1, v2, v0, v1, v3}, Landroid/os/IBinder;->transact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z + + move-result v4 + + if-eqz v4, :cleanup + + invoke-virtual {v1}, Landroid/os/Parcel;->readException()V + + :cleanup + invoke-virtual {v1}, Landroid/os/Parcel;->recycle()V + + invoke-virtual {v0}, Landroid/os/Parcel;->recycle()V + + :return + return-void +.end method + +.method private safeUnbind()V + .locals 1 + + :try_start_0 + iget-object v0, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context; + + invoke-virtual {v0, p0}, Landroid/content/Context;->unbindService(Landroid/content/ServiceConnection;)V + :try_end_0 + .catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0 + + goto :return + + :catch_0 + move-exception v0 + + :return + return-void +.end method + +.method private static writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V + .locals 2 + + const/4 v0, 0x0 + + if-eqz p1, :null_value + + const/4 v1, 0x1 + + invoke-virtual {p0, v1}, Landroid/os/Parcel;->writeInt(I)V + + invoke-interface {p1, p0, v0}, Landroid/os/Parcelable;->writeToParcel(Landroid/os/Parcel;I)V + + return-void + + :null_value + invoke-virtual {p0, v0}, Landroid/os/Parcel;->writeInt(I)V + + return-void +.end method + + +# virtual methods +.method public onServiceConnected(Landroid/content/ComponentName;Landroid/os/IBinder;)V + .locals 1 + + :try_start_0 + invoke-direct {p0, p2}, Lradiant/WazeServiceConnection;->register(Landroid/os/IBinder;)V + :try_end_0 + .catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0 + + goto :unbind + + :catch_0 + move-exception v0 + + :unbind + invoke-direct {p0}, Lradiant/WazeServiceConnection;->safeUnbind()V + + return-void +.end method + +.method public onServiceDisconnected(Landroid/content/ComponentName;)V + .locals 0 + + return-void +.end method diff --git a/patches/manifest.json b/patches/manifest.json index d2d880e..757ca27 100644 --- a/patches/manifest.json +++ b/patches/manifest.json @@ -212,6 +212,15 @@ "description": "Inverts the auto-hide behavior on the lyrics screen, playback controls stay visible by default and only hide when you tap them off.", "default": true }, + { + "id": "WazeIntegration", + "order": 90, + "fileNames": ["waze-media-browser.patch"], + "extensionFiles": ["radiant/WazeInitReceiver.smali", "radiant/WazeServiceConnection.smali"], + "title": "Waze Integration", + "description": "Control TIDAL playback from the Waze app", + "default": true + }, { "id": "DebugMenuUnlock", "order": 100, diff --git a/patches/waze-media-browser.patch b/patches/waze-media-browser.patch new file mode 100644 index 0000000..1f2c5a4 --- /dev/null +++ b/patches/waze-media-browser.patch @@ -0,0 +1,27 @@ +--- a/pb/c.smali ++++ b/pb/c.smali +@@ -700,0 +701,24 @@ ++ iget-object v15, v1, Lcom/aspiro/wamp/util/y$a;->b:Ljava/lang/String; ++ ++ const-string/jumbo v14, "com.waze" ++ ++ invoke-static {v15, v14}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # check caller ++ ++ move-result v12 ++ ++ if-eqz v12, :rl_waze_done ++ ++ iget-object v15, v1, Lcom/aspiro/wamp/util/y$a;->d:Ljava/lang/String; # caller cert ++ ++ const-string v14, "03:63:7f:6c:5d:8f:60:4e:6f:db:79:a6:ff:bf:a5:78:de:4e:31:8f:8d:a2:2f:c6:10:66:65:24:7f:88:07:d7" # Waze cert ++ ++ invoke-static {v15, v14}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # check cert ++ ++ move-result v12 # cert match ++ ++ if-eqz v12, :rl_waze_done # reject mismatch ++ ++ goto :goto_9 # trust Waze ++ ++ :rl_waze_done ++