mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 18:03:55 +01:00
Compare commits
8 Commits
v2.203.0-d
...
v3.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
536b354fd6 | ||
|
|
e6a1573c59 | ||
|
|
d63288cc3e | ||
|
|
aaad91333f | ||
|
|
a98c178ca7 | ||
|
|
4672118e88 | ||
|
|
5587a5c869 | ||
|
|
55bc7ecbae |
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,3 +1,36 @@
|
||||
# [3.0.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v3.0.0-dev.3...v3.0.0-dev.4) (2023-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#3364](https://github.com/ReVanced/revanced-patches/issues/3364)) ([84607ff](https://github.com/ReVanced/revanced-patches/commit/84607ff5f4bd30d328cdc4e1d46070a86d6c56bf))
|
||||
|
||||
# [3.0.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v3.0.0-dev.2...v3.0.0-dev.3) (2023-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Return YouTube Dislike:** Prevent the first Short opened from freezing the UI ([#3359](https://github.com/ReVanced/revanced-patches/issues/3359)) ([e024409](https://github.com/ReVanced/revanced-patches/commit/e024409219bfbccc32c337d95da24b7146b6c7b7))
|
||||
|
||||
# [3.0.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v3.0.0-dev.1...v3.0.0-dev.2) (2023-12-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Clarify patch descriptions ([#3350](https://github.com/ReVanced/revanced-patches/issues/3350)) ([f2b9df4](https://github.com/ReVanced/revanced-patches/commit/f2b9df4e22a1c537cbd383087a3d724c3cdc1784))
|
||||
|
||||
# [3.0.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v2.203.0-dev.2...v3.0.0-dev.1) (2023-12-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow choosing the vendor of GmsCore via patch options ([#3347](https://github.com/ReVanced/revanced-patches/issues/3347)) ([161261c](https://github.com/ReVanced/revanced-patches/commit/161261cfeab4616764d20efe46fcfd483299741c))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Various APIs have been moved to `GmsCoreSupportAbstractPatch`
|
||||
|
||||
# [2.203.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v2.203.0-dev.1...v2.203.0-dev.2) (2023-12-02)
|
||||
|
||||
|
||||
|
||||
@@ -309,20 +309,16 @@ public final class app/revanced/patches/music/misc/androidauto/BypassCertificate
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/misc/microg/MicroGBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/music/misc/microg/MicroGBytecodePatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public final class app/revanced/patches/music/misc/gms/Constants {
|
||||
public static final field INSTANCE Lapp/revanced/patches/music/misc/gms/Constants;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/misc/microg/MicroGResourcePatch : app/revanced/patcher/patch/ResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/music/misc/microg/MicroGResourcePatch;
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatch : app/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/music/misc/gms/GmsCoreSupportPatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/misc/microg/shared/Constants {
|
||||
public static final field INSTANCE Lapp/revanced/patches/music/misc/microg/shared/Constants;
|
||||
public final class app/revanced/patches/music/misc/gms/GmsCoreSupportResourcePatch : app/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/music/misc/gms/GmsCoreSupportResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/premium/backgroundplay/BackgroundPlayPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
@@ -573,6 +569,26 @@ public final class app/revanced/patches/shared/misc/fix/verticalscroll/VerticalS
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patcher/fingerprint/MethodFingerprint;Ljava/util/Set;Lapp/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportResourcePatch;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patcher/fingerprint/MethodFingerprint;Ljava/util/Set;Lapp/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportResourcePatch;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportResourcePatch : app/revanced/patcher/patch/ResourcePatch {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
protected final fun getGmsCoreVendor ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/shared/misc/gms/fingerprints/GmsCoreSupportFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
|
||||
public static final field GET_GMS_CORE_VENDOR_METHOD_NAME Ljava/lang/String;
|
||||
public static final field INSTANCE Lapp/revanced/patches/shared/misc/gms/fingerprints/GmsCoreSupportFingerprint;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patches/shared/settings/AbstractSettingsResourcePatch : app/revanced/patcher/patch/ResourcePatch, java/io/Closeable {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public fun close ()V
|
||||
@@ -1394,6 +1410,18 @@ public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignature
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch : app/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportResourcePatch : app/revanced/patches/shared/misc/gms/AbstractGmsCoreSupportResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/gms/GmsCoreSupportResourcePatch;
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/integrations/IntegrationsPatch : app/revanced/patches/shared/integrations/AbstractIntegrationsPatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/integrations/IntegrationsPatch;
|
||||
}
|
||||
@@ -1421,18 +1449,6 @@ public final class app/revanced/patches/youtube/misc/litho/filter/LithoFilterPat
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/microg/MicroGBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/microg/MicroGBytecodePatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/microg/MicroGResourcePatch : app/revanced/patcher/patch/ResourcePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/microg/MicroGResourcePatch;
|
||||
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/minimizedplayback/MinimizedPlaybackPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/minimizedplayback/MinimizedPlaybackPatch;
|
||||
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 2.203.0-dev.2
|
||||
version = 3.0.0-dev.4
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
||||
package app.revanced.patches.music.misc.gms
|
||||
|
||||
object Constants {
|
||||
internal const val REVANCED_MUSIC_PACKAGE_NAME = "app.revanced.android.apps.youtube.music"
|
||||
internal const val MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package app.revanced.patches.music.misc.gms
|
||||
|
||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorOption
|
||||
import app.revanced.patches.music.misc.gms.fingerprints.*
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportPatch
|
||||
import app.revanced.patches.youtube.misc.gms.fingerprints.CastContextFetchFingerprint
|
||||
|
||||
@Suppress("unused")
|
||||
object GmsCoreSupportPatch : AbstractGmsCoreSupportPatch(
|
||||
fromPackageName = REVANCED_MUSIC_PACKAGE_NAME,
|
||||
toPackageName = MUSIC_PACKAGE_NAME,
|
||||
primeMethodFingerprint = PrimeMethodFingerprint,
|
||||
earlyReturnFingerprints = setOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint,
|
||||
),
|
||||
abstractGmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
|
||||
compatiblePackages = setOf(CompatiblePackage("com.google.android.apps.youtube.music")),
|
||||
fingerprints = setOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint,
|
||||
PrimeMethodFingerprint,
|
||||
)
|
||||
) {
|
||||
override val gmsCoreVendor by gmsCoreVendorOption
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package app.revanced.patches.music.misc.gms
|
||||
|
||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportResourcePatch
|
||||
|
||||
object GmsCoreSupportResourcePatch : AbstractGmsCoreSupportResourcePatch(
|
||||
fromPackageName = MUSIC_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_MUSIC_PACKAGE_NAME,
|
||||
spoofedPackageSignature = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875"
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.music.misc.microg.fingerprints
|
||||
package app.revanced.patches.music.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.music.misc.microg.fingerprints
|
||||
package app.revanced.patches.music.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,18 +1,18 @@
|
||||
package app.revanced.patches.music.misc.microg.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object GooglePlayUtilityFingerprint : MethodFingerprint(
|
||||
"I",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
listOf("L", "I"),
|
||||
strings = listOf(
|
||||
"This should never happen.",
|
||||
"MetadataValueReader",
|
||||
"GooglePlayServicesUtil",
|
||||
"com.android.vending",
|
||||
"android.hardware.type.embedded"
|
||||
)
|
||||
package app.revanced.patches.music.misc.gms.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object GooglePlayUtilityFingerprint : MethodFingerprint(
|
||||
"I",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
listOf("L", "I"),
|
||||
strings = listOf(
|
||||
"This should never happen.",
|
||||
"MetadataValueReader",
|
||||
"GooglePlayServicesUtil",
|
||||
"com.android.vending",
|
||||
"android.hardware.type.embedded"
|
||||
)
|
||||
)
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patches.youtube.misc.microg.fingerprints
|
||||
package app.revanced.patches.music.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object PrimeFingerprint : MethodFingerprint(
|
||||
internal object PrimeMethodFingerprint : MethodFingerprint(
|
||||
strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
|
||||
)
|
||||
@@ -1,15 +1,14 @@
|
||||
package app.revanced.patches.music.misc.microg.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
|
||||
@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
|
||||
internal object ServiceCheckFingerprint : MethodFingerprint(
|
||||
"V",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
listOf("L", "I"),
|
||||
strings = listOf("Google Play Services not available")
|
||||
)
|
||||
package app.revanced.patches.music.misc.gms.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
|
||||
internal object ServiceCheckFingerprint : MethodFingerprint(
|
||||
"V",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
listOf("L", "I"),
|
||||
strings = listOf("Google Play Services not available")
|
||||
)
|
||||
@@ -1,70 +0,0 @@
|
||||
package app.revanced.patches.music.misc.microg
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.patches.music.misc.microg.fingerprints.*
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||
import app.revanced.util.microg.MicroGBytecodeHelper
|
||||
|
||||
|
||||
@Patch(
|
||||
name = "Vanced MicroG support",
|
||||
description = "Allows YouTube Music to run without root and under a different package name.",
|
||||
dependencies = [
|
||||
ChangePackageNamePatch::class,
|
||||
MicroGResourcePatch::class,
|
||||
],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music"
|
||||
)
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
object MicroGBytecodePatch : BytecodePatch(
|
||||
setOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint,
|
||||
PrimeFingerprint,
|
||||
)
|
||||
) {
|
||||
// NOTE: the previous patch also replaced the following strings, but it seems like they are not needed:
|
||||
// - "com.google.android.gms.chimera.GmsIntentOperationService",
|
||||
// - "com.google.android.gms.phenotype.internal.IPhenotypeCallbacks",
|
||||
// - "com.google.android.gms.phenotype.internal.IPhenotypeService",
|
||||
// - "com.google.android.gms.phenotype.PACKAGE_NAME",
|
||||
// - "com.google.android.gms.phenotype.UPDATE",
|
||||
// - "com.google.android.gms.phenotype",
|
||||
override fun execute(context: BytecodeContext) {
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(REVANCED_MUSIC_PACKAGE_NAME)
|
||||
|
||||
MicroGBytecodeHelper.patchBytecode(
|
||||
context,
|
||||
arrayOf(
|
||||
MicroGBytecodeHelper.packageNameTransform(
|
||||
MUSIC_PACKAGE_NAME,
|
||||
packageName
|
||||
)
|
||||
),
|
||||
MicroGBytecodeHelper.PrimeMethodTransformationData(
|
||||
PrimeFingerprint,
|
||||
MUSIC_PACKAGE_NAME,
|
||||
packageName
|
||||
),
|
||||
listOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package app.revanced.patches.music.misc.microg
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_APP_NAME
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.SPOOFED_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.microg.shared.Constants.SPOOFED_PACKAGE_SIGNATURE
|
||||
import app.revanced.util.microg.MicroGManifestHelper
|
||||
import app.revanced.util.microg.MicroGResourceHelper
|
||||
|
||||
|
||||
@Patch(
|
||||
description = "Resource patch to allow YouTube Music ReVanced to run without root " +
|
||||
"and under a different package name.",
|
||||
dependencies = [ChangePackageNamePatch::class]
|
||||
)
|
||||
object MicroGResourcePatch : ResourcePatch() {
|
||||
override fun execute(context: ResourceContext) {
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(REVANCED_MUSIC_PACKAGE_NAME)
|
||||
|
||||
// update manifest
|
||||
MicroGResourceHelper.patchManifest(
|
||||
context,
|
||||
MUSIC_PACKAGE_NAME,
|
||||
packageName,
|
||||
REVANCED_MUSIC_APP_NAME
|
||||
)
|
||||
|
||||
// add metadata to the manifest
|
||||
MicroGManifestHelper.addSpoofingMetadata(
|
||||
context,
|
||||
SPOOFED_PACKAGE_NAME,
|
||||
SPOOFED_PACKAGE_SIGNATURE
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patches.music.misc.microg.shared
|
||||
|
||||
object Constants {
|
||||
internal const val REVANCED_MUSIC_APP_NAME = "YT Music ReVanced"
|
||||
internal const val REVANCED_MUSIC_PACKAGE_NAME = "app.revanced.android.apps.youtube.music"
|
||||
internal const val MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"
|
||||
internal const val SPOOFED_PACKAGE_NAME = MUSIC_PACKAGE_NAME
|
||||
internal const val SPOOFED_PACKAGE_SIGNATURE = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875"
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package app.revanced.patches.shared.misc.gms
|
||||
|
||||
import app.revanced.patcher.PatchClass
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportPatch.Constants.ACTIONS
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportPatch.Constants.AUTHORITIES
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportPatch.Constants.PERMISSIONS
|
||||
import app.revanced.patches.shared.misc.gms.fingerprints.GmsCoreSupportFingerprint
|
||||
import app.revanced.patches.shared.misc.gms.fingerprints.GmsCoreSupportFingerprint.GET_GMS_CORE_VENDOR_METHOD_NAME
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
/**
|
||||
* A patch that allows Google apps to run without root and under a different package name
|
||||
* by using GmsCore instead of Google Play Services.
|
||||
*
|
||||
* @param fromPackageName The package name of the original app.
|
||||
* @param toPackageName The package name to fall back to if no custom package name is specified in patch options.
|
||||
* @param primeMethodFingerprint The fingerprint of the "prime" method that needs to be patched.
|
||||
* @param earlyReturnFingerprints The fingerprints of methods that need to be returned early.
|
||||
* @param abstractGmsCoreSupportResourcePatch The corresponding resource patch that is used to patch the resources.
|
||||
* @param dependencies Additional dependencies of this patch.
|
||||
* @param compatiblePackages The compatible packages of this patch.
|
||||
* @param fingerprints The fingerprints of this patch.
|
||||
*/
|
||||
abstract class AbstractGmsCoreSupportPatch(
|
||||
private val fromPackageName: String,
|
||||
private val toPackageName: String,
|
||||
private val primeMethodFingerprint: MethodFingerprint,
|
||||
private val earlyReturnFingerprints: Set<MethodFingerprint>,
|
||||
abstractGmsCoreSupportResourcePatch: AbstractGmsCoreSupportResourcePatch,
|
||||
dependencies: Set<PatchClass> = setOf(),
|
||||
compatiblePackages: Set<CompatiblePackage>? = null,
|
||||
fingerprints: Set<MethodFingerprint> = emptySet(),
|
||||
) : BytecodePatch(
|
||||
name = "GmsCore support",
|
||||
description = "Allows Google apps to run without root and under a different package name " +
|
||||
"by using GmsCore instead of Google Play Services.",
|
||||
dependencies = setOf(ChangePackageNamePatch::class, abstractGmsCoreSupportResourcePatch::class) + dependencies,
|
||||
compatiblePackages = compatiblePackages,
|
||||
fingerprints = setOf(GmsCoreSupportFingerprint) + fingerprints,
|
||||
requiresIntegrations = true
|
||||
) {
|
||||
init {
|
||||
// Manually register all options of the resource patch so that they are visible in the patch API.
|
||||
abstractGmsCoreSupportResourcePatch.options.values.forEach(options::register)
|
||||
}
|
||||
|
||||
internal abstract val gmsCoreVendor: String?
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName)
|
||||
|
||||
// Transform all strings using all provided transforms, first match wins.
|
||||
val transformations = arrayOf(
|
||||
::commonTransform,
|
||||
::contentUrisTransform,
|
||||
packageNameTransform(fromPackageName, packageName)
|
||||
)
|
||||
context.transformStringReferences transform@{ string ->
|
||||
transformations.forEach { transform ->
|
||||
transform(string)?.let { transformedString -> return@transform transformedString }
|
||||
}
|
||||
|
||||
return@transform null
|
||||
}
|
||||
|
||||
// Specific method that needs to be patched.
|
||||
transformPrimeMethod(packageName)
|
||||
|
||||
// Return these methods early to prevent the app from crashing.
|
||||
earlyReturnFingerprints.toList().returnEarly()
|
||||
|
||||
// Change the vendor of GmsCore in ReVanced Integrations.
|
||||
GmsCoreSupportFingerprint.result?.mutableClass?.methods
|
||||
?.single { it.name == GET_GMS_CORE_VENDOR_METHOD_NAME }
|
||||
?.replaceInstruction(0, "const-string v0, \"$gmsCoreVendor\"")
|
||||
?: throw GmsCoreSupportFingerprint.exception
|
||||
}
|
||||
|
||||
private fun BytecodeContext.transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
|
||||
val mutableClass by lazy {
|
||||
proxy(it).mutableClass
|
||||
}
|
||||
|
||||
it.methods.forEach classLoop@{ methodDef ->
|
||||
val implementation = methodDef.implementation ?: return@classLoop
|
||||
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first { MethodUtil.methodSignaturesMatch(it, methodDef) }
|
||||
}
|
||||
|
||||
implementation.instructions.forEachIndexed insnLoop@{ index, instruction ->
|
||||
val string = ((instruction as? Instruction21c)?.reference as? StringReference)?.string
|
||||
?: return@insnLoop
|
||||
|
||||
// Apply transformation.
|
||||
val transformedString = transform(string) ?: return@insnLoop
|
||||
|
||||
mutableMethod.replaceInstruction(
|
||||
index,
|
||||
BuilderInstruction21c(
|
||||
Opcode.CONST_STRING,
|
||||
instruction.registerA,
|
||||
ImmutableStringReference(transformedString)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Collection of transformations that are applied to all strings.
|
||||
|
||||
private fun commonTransform(referencedString: String): String? =
|
||||
when (referencedString) {
|
||||
"com.google",
|
||||
"com.google.android.gms",
|
||||
in PERMISSIONS,
|
||||
in ACTIONS,
|
||||
in AUTHORITIES -> referencedString.replace("com.google", gmsCoreVendor!!)
|
||||
|
||||
// No vendor prefix for whatever reason...
|
||||
"subscribedfeeds" -> "$gmsCoreVendor.subscribedfeeds"
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun contentUrisTransform(str: String): String? {
|
||||
// only when content:// uri
|
||||
if (str.startsWith("content://")) {
|
||||
// check if matches any authority
|
||||
for (authority in AUTHORITIES) {
|
||||
val uriPrefix = "content://$authority"
|
||||
if (str.startsWith(uriPrefix)) {
|
||||
return str.replace(
|
||||
uriPrefix,
|
||||
"content://${authority.replace("com.google", gmsCoreVendor!!)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// gms also has a 'subscribedfeeds' authority, check for that one too
|
||||
val subFeedsUriPrefix = "content://subscribedfeeds"
|
||||
if (str.startsWith(subFeedsUriPrefix)) {
|
||||
return str.replace(subFeedsUriPrefix, "content://$gmsCoreVendor.subscribedfeeds")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
private fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? = { string ->
|
||||
when (string) {
|
||||
"$fromPackageName.SuggestionsProvider",
|
||||
"$fromPackageName.fileprovider" -> string.replace(fromPackageName, toPackageName)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformPrimeMethod(packageName: String) = primeMethodFingerprint.result?.mutableMethod?.apply {
|
||||
var register = 2
|
||||
val index = getInstructions().indexOfFirst {
|
||||
if (it.opcode != Opcode.CONST_STRING) return@indexOfFirst false
|
||||
|
||||
val instructionString = ((it as Instruction21c).reference as StringReference).string
|
||||
if (instructionString != fromPackageName) return@indexOfFirst false
|
||||
|
||||
register = it.registerA
|
||||
return@indexOfFirst true
|
||||
}
|
||||
|
||||
replaceInstruction(
|
||||
index, "const-string v$register, \"$packageName\""
|
||||
)
|
||||
}
|
||||
?: throw primeMethodFingerprint.exception
|
||||
|
||||
/**
|
||||
* A collection of permissions, intents and content provider authorities
|
||||
* that are present in GmsCore which need to be transformed.
|
||||
*
|
||||
* NOTE: The following were present, but it seems like they are not needed to be transformed:
|
||||
* - com.google.android.gms.chimera.GmsIntentOperationService
|
||||
* - com.google.android.gms.phenotype.internal.IPhenotypeCallbacks
|
||||
* - com.google.android.gms.phenotype.internal.IPhenotypeService
|
||||
* - com.google.android.gms.phenotype.PACKAGE_NAME
|
||||
* - com.google.android.gms.phenotype.UPDATE
|
||||
* - com.google.android.gms.phenotype
|
||||
*/
|
||||
private object Constants {
|
||||
/**
|
||||
* A list of all permissions.
|
||||
*/
|
||||
val PERMISSIONS = listOf(
|
||||
// C2DM / GCM
|
||||
"com.google.android.c2dm.permission.RECEIVE",
|
||||
"com.google.android.c2dm.permission.SEND",
|
||||
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
|
||||
|
||||
// GAuth
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
|
||||
)
|
||||
|
||||
/**
|
||||
* All intent actions.
|
||||
*/
|
||||
val ACTIONS = listOf(
|
||||
// location
|
||||
"com.google.android.gms.location.places.ui.PICK_PLACE",
|
||||
"com.google.android.gms.location.places.GeoDataApi",
|
||||
"com.google.android.gms.location.places.PlacesApi",
|
||||
"com.google.android.gms.location.places.PlaceDetectionApi",
|
||||
"com.google.android.gms.wearable.MESSAGE_RECEIVED",
|
||||
|
||||
// C2DM / GCM
|
||||
"com.google.android.c2dm.intent.REGISTER",
|
||||
"com.google.android.c2dm.intent.REGISTRATION",
|
||||
"com.google.android.c2dm.intent.UNREGISTER",
|
||||
"com.google.android.c2dm.intent.RECEIVE",
|
||||
"com.google.iid.TOKEN_REQUEST",
|
||||
"com.google.android.gcm.intent.SEND",
|
||||
|
||||
// car
|
||||
"com.google.android.gms.car.service.START",
|
||||
|
||||
// people
|
||||
"com.google.android.gms.people.service.START",
|
||||
|
||||
// wearable
|
||||
"com.google.android.gms.wearable.BIND",
|
||||
|
||||
// auth
|
||||
"com.google.android.gsf.login",
|
||||
"com.google.android.gsf.action.GET_GLS",
|
||||
"com.google.android.gms.common.account.CHOOSE_ACCOUNT",
|
||||
"com.google.android.gms.auth.login.LOGIN",
|
||||
"com.google.android.gms.auth.api.credentials.PICKER",
|
||||
"com.google.android.gms.auth.api.credentials.service.START",
|
||||
"com.google.android.gms.auth.service.START",
|
||||
"com.google.firebase.auth.api.gms.service.START",
|
||||
"com.google.android.gms.auth.be.appcert.AppCertService",
|
||||
|
||||
// fido
|
||||
"com.google.android.gms.fido.fido2.privileged.START",
|
||||
|
||||
// games
|
||||
"com.google.android.gms.games.service.START",
|
||||
"com.google.android.gms.games.PLAY_GAMES_UPGRADE",
|
||||
|
||||
// chimera
|
||||
"com.google.android.gms.chimera",
|
||||
|
||||
// fonts
|
||||
"com.google.android.gms.fonts",
|
||||
|
||||
// phenotype
|
||||
"com.google.android.gms.phenotype.service.START",
|
||||
|
||||
// location
|
||||
"com.google.android.gms.location.reporting.service.START",
|
||||
|
||||
// misc
|
||||
"com.google.android.gms.gmscompliance.service.START",
|
||||
"com.google.android.gms.oss.licenses.service.START",
|
||||
"com.google.android.gms.safetynet.service.START",
|
||||
"com.google.android.gms.tapandpay.service.BIND"
|
||||
)
|
||||
|
||||
/**
|
||||
* All content provider authorities.
|
||||
*/
|
||||
val AUTHORITIES = listOf(
|
||||
// gsf
|
||||
"com.google.android.gsf.gservices",
|
||||
"com.google.settings",
|
||||
|
||||
// auth
|
||||
"com.google.android.gms.auth.accounts",
|
||||
|
||||
// chimera
|
||||
"com.google.android.gms.chimera",
|
||||
|
||||
// fonts
|
||||
"com.google.android.gms.fonts",
|
||||
|
||||
// phenotype
|
||||
"com.google.android.gms.phenotype"
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package app.revanced.patches.shared.misc.gms
|
||||
|
||||
import app.revanced.patcher.PatchClass
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.util.mergeStrings
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
|
||||
/**
|
||||
* Abstract resource patch that allows Google apps to run without root and under a different package name
|
||||
* by using GmsCore instead of Google Play Services.
|
||||
*
|
||||
* @param fromPackageName The package name of the original app.
|
||||
* @param toPackageName The package name to fall back to if no custom package name is specified in patch options.
|
||||
* @param spoofedPackageSignature The signature of the package to spoof to.
|
||||
* @param dependencies Additional dependencies of this patch.
|
||||
*/
|
||||
abstract class AbstractGmsCoreSupportResourcePatch(
|
||||
private val fromPackageName: String,
|
||||
private val toPackageName: String,
|
||||
private val spoofedPackageSignature: String,
|
||||
dependencies: Set<PatchClass> = setOf()
|
||||
) : ResourcePatch(dependencies = setOf(ChangePackageNamePatch::class) + dependencies) {
|
||||
internal val gmsCoreVendorOption = stringPatchOption(
|
||||
key = "gmsCoreVendor",
|
||||
default = "com.mgoogle",
|
||||
values = mapOf(
|
||||
"Vanced" to "com.mgoogle",
|
||||
"ReVanced" to "app.revanced"
|
||||
),
|
||||
title = "GmsCore Vendor",
|
||||
description = "The group id of the GmsCore vendor.",
|
||||
required = true
|
||||
) { it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) }
|
||||
|
||||
protected val gmsCoreVendor by gmsCoreVendorOption
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
context.mergeStrings("gms/host/values/strings.xml")
|
||||
context.patchManifest()
|
||||
context.addSpoofingMetadata()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add metadata to manifest to support spoofing the package name and signature of GmsCore.
|
||||
*/
|
||||
private fun ResourceContext.addSpoofingMetadata() {
|
||||
fun Node.adoptChild(tagName: String, block: Element.() -> Unit) {
|
||||
val child = ownerDocument.createElement(tagName)
|
||||
child.block()
|
||||
appendChild(child)
|
||||
}
|
||||
|
||||
xmlEditor["AndroidManifest.xml"].use {
|
||||
val applicationNode = it
|
||||
.file
|
||||
.getElementsByTagName("application")
|
||||
.item(0)
|
||||
|
||||
// Spoof package name and signature.
|
||||
applicationNode.adoptChild("meta-data") {
|
||||
setAttribute("android:name", "$gmsCoreVendor.android.gms.SPOOFED_PACKAGE_NAME")
|
||||
setAttribute("android:value", fromPackageName)
|
||||
}
|
||||
|
||||
applicationNode.adoptChild("meta-data") {
|
||||
setAttribute("android:name", "$gmsCoreVendor.android.gms.SPOOFED_PACKAGE_SIGNATURE")
|
||||
setAttribute("android:value", spoofedPackageSignature)
|
||||
}
|
||||
|
||||
// GmsCore presence detection in ReVanced Integrations.
|
||||
applicationNode.adoptChild("meta-data") {
|
||||
// TODO: The name of this metadata should be dynamic.
|
||||
setAttribute("android:name", "app.revanced.MICROG_PACKAGE_NAME")
|
||||
setAttribute("android:value", "$gmsCoreVendor.android.gms")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the manifest to support GmsCore.
|
||||
*/
|
||||
private fun ResourceContext.patchManifest() {
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName)
|
||||
|
||||
val manifest = this["AndroidManifest.xml"].readText()
|
||||
this["AndroidManifest.xml"].writeText(
|
||||
manifest.replace(
|
||||
"package=\"$fromPackageName",
|
||||
"package=\"$packageName"
|
||||
).replace(
|
||||
"android:authorities=\"$fromPackageName",
|
||||
"android:authorities=\"$packageName"
|
||||
).replace(
|
||||
"$fromPackageName.permission.C2D_MESSAGE",
|
||||
"$packageName.permission.C2D_MESSAGE"
|
||||
).replace(
|
||||
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
|
||||
"$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
).replace(
|
||||
"com.google.android.c2dm",
|
||||
"$gmsCoreVendor.android.c2dm"
|
||||
).replace(
|
||||
"</queries>",
|
||||
"<package android:name=\"$gmsCoreVendor.android.gms\"/></queries>"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package app.revanced.patches.shared.misc.gms.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
object GmsCoreSupportFingerprint : MethodFingerprint(
|
||||
customFingerprint = { _, classDef ->
|
||||
classDef.type.endsWith("GmsCoreSupport;")
|
||||
}
|
||||
) {
|
||||
const val GET_GMS_CORE_VENDOR_METHOD_NAME = "getGmsCoreVendor"
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Enable slide to seek",
|
||||
description = "Enable slide to seek instead of playing at 2x speed.",
|
||||
description = "Enable slide to seek instead of playing at 2x speed when pressing and holding in the video player.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.nio.file.Files
|
||||
|
||||
@Patch(
|
||||
name = "Custom branding",
|
||||
description = "Changes the app icon and name to your choice (defaults to YouTube ReVanced and the ReVanced logo).",
|
||||
description = "Changes the app name and icon to your choice (defaults to \"YouTube ReVanced\" and the ReVanced logo).",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.google.android.youtube")
|
||||
],
|
||||
|
||||
@@ -13,7 +13,7 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
|
||||
@Patch(
|
||||
name = "Hide video action buttons",
|
||||
description = "Adds the options to hide action buttons under a video.",
|
||||
description = "Adds options to hide action buttons under a video.",
|
||||
dependencies = [
|
||||
ResourceMappingPatch::class,
|
||||
LithoFilterPatch::class
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Patch(
|
||||
name = "Hide captions button",
|
||||
description = "Hides the captions button on video player.",
|
||||
description = "Hides the captions button in the video player.",
|
||||
dependencies = [
|
||||
IntegrationsPatch::class,
|
||||
SettingsPatch::class
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction3rc
|
||||
|
||||
@Patch(
|
||||
name = "Hide player buttons",
|
||||
description = "Adds the option to hide video player previous and next buttons.",
|
||||
description = "Hides previous and next buttons in the video player.",
|
||||
dependencies = [
|
||||
IntegrationsPatch::class,
|
||||
SettingsPatch::class
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
|
||||
|
||||
@Patch(
|
||||
name = "Hide endscreen cards",
|
||||
description = "Hides the suggested video cards at the end of a video in fullscreen.",
|
||||
description = "Hides the suggested video cards at the end of videos.",
|
||||
dependencies = [
|
||||
IntegrationsPatch::class,
|
||||
HideEndscreenCardsResourcePatch::class
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Disable rolling number animations",
|
||||
description = "Disables rolling number animations of video view count, upload time, and user likes",
|
||||
description = "Disables rolling number animations of video view count, user likes, and upload time.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -14,7 +14,7 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
|
||||
@Patch(
|
||||
name = "Disable player popup panels",
|
||||
description = "Disables panels from appearing automatically when going into fullscreen (playlist or live chat).",
|
||||
description = "Disables panels (such as live chat) from opening automatically.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Custom player overlay opacity",
|
||||
description = "Change the opacity of the player background, when player controls are visible.",
|
||||
description = "Change the opacity of the player background when player controls are visible.",
|
||||
dependencies = [CustomPlayerOverlayOpacityResourcePatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage("com.google.android.youtube")
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
@Patch(
|
||||
name = "SponsorBlock",
|
||||
description = "Integrates SponsorBlock which allows skipping video segments such as sponsored content.",
|
||||
description = "Integrates SponsorBlock, which can skip undesired video segments such as sponsored content.",
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube", [
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
@Patch(
|
||||
name = "Spoof app version",
|
||||
description = "Tricks YouTube into thinking you are running an older version of the app. " +
|
||||
"One of the side effects also includes restoring the old UI.",
|
||||
"This can be used to restore old UI elements and features.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -112,7 +112,7 @@ object SpoofSignaturePatch : BytecodePatch(
|
||||
|
||||
// Hook the player parameters.
|
||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;)Ljava/lang/String;"
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Z)Ljava/lang/String;"
|
||||
)
|
||||
|
||||
// Force the seekbar time and chapters to always show up.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package app.revanced.patches.youtube.misc.gms
|
||||
|
||||
internal object Constants {
|
||||
const val YOUTUBE_PACKAGE_NAME = "com.google.android.youtube"
|
||||
const val REVANCED_YOUTUBE_PACKAGE_NAME = "app.revanced.android.youtube"
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package app.revanced.patches.youtube.misc.gms
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportPatch
|
||||
import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch
|
||||
import app.revanced.patches.youtube.misc.fix.playback.ClientSpoofPatch
|
||||
import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorOption
|
||||
import app.revanced.patches.youtube.misc.gms.fingerprints.*
|
||||
import app.revanced.patches.youtube.shared.fingerprints.WatchWhileActivityFingerprint
|
||||
import app.revanced.util.exception
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
object GmsCoreSupportPatch : AbstractGmsCoreSupportPatch(
|
||||
fromPackageName = YOUTUBE_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_YOUTUBE_PACKAGE_NAME,
|
||||
primeMethodFingerprint = PrimeMethodFingerprint,
|
||||
earlyReturnFingerprints = setOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint
|
||||
),
|
||||
dependencies = setOf(
|
||||
HideCastButtonPatch::class,
|
||||
ClientSpoofPatch::class
|
||||
),
|
||||
abstractGmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
|
||||
compatiblePackages = setOf(
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube", setOf(
|
||||
"18.32.39",
|
||||
"18.37.36",
|
||||
"18.38.44",
|
||||
"18.43.45",
|
||||
"18.44.41",
|
||||
"18.45.41",
|
||||
"18.45.43"
|
||||
)
|
||||
)
|
||||
),
|
||||
fingerprints = setOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint,
|
||||
PrimeMethodFingerprint,
|
||||
WatchWhileActivityFingerprint
|
||||
)
|
||||
) {
|
||||
override val gmsCoreVendor by gmsCoreVendorOption
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// Check the availability of GmsCore.
|
||||
WatchWhileActivityFingerprint.result?.mutableMethod?.addInstruction(
|
||||
0,
|
||||
"invoke-static {}, Lapp/revanced/integrations/patches/GmsCoreSupport;->checkAvailability()V"
|
||||
) ?: throw WatchWhileActivityFingerprint.exception
|
||||
|
||||
super.execute(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.revanced.patches.youtube.misc.gms
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.patches.shared.misc.gms.AbstractGmsCoreSupportResourcePatch
|
||||
import app.revanced.patches.shared.settings.preference.impl.Preference
|
||||
import app.revanced.patches.shared.settings.preference.impl.StringResource
|
||||
import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
|
||||
|
||||
object GmsCoreSupportResourcePatch : AbstractGmsCoreSupportResourcePatch(
|
||||
fromPackageName = YOUTUBE_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_YOUTUBE_PACKAGE_NAME,
|
||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
|
||||
dependencies = setOf(SettingsPatch::class)
|
||||
) {
|
||||
override fun execute(context: ResourceContext) {
|
||||
SettingsPatch.addPreference(
|
||||
Preference(
|
||||
StringResource("microg_settings", "GmsCore Settings"),
|
||||
StringResource("microg_settings_summary", "Settings for GmsCore"),
|
||||
Preference.Intent("$gmsCoreVendor.android.gms", "", "org.microg.gms.ui.SettingsActivity")
|
||||
)
|
||||
)
|
||||
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(REVANCED_YOUTUBE_PACKAGE_NAME)
|
||||
SettingsPatch.renameIntentsTargetPackage(packageName)
|
||||
|
||||
super.execute(context)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.music.misc.microg.fingerprints
|
||||
package app.revanced.patches.youtube.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.misc.microg.fingerprints
|
||||
package app.revanced.patches.youtube.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.misc.microg.fingerprints
|
||||
package app.revanced.patches.youtube.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.misc.microg.fingerprints
|
||||
package app.revanced.patches.youtube.misc.gms.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patches.music.misc.microg.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object PrimeFingerprint : MethodFingerprint(
|
||||
strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
|
||||
package app.revanced.patches.youtube.misc.gms.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object PrimeMethodFingerprint : MethodFingerprint(
|
||||
strings = listOf("com.google.android.GoogleCamera", "com.android.vending")
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.youtube.misc.microg.fingerprints
|
||||
package app.revanced.patches.youtube.misc.gms.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
@@ -1,79 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.microg
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.patches.youtube.shared.fingerprints.WatchWhileActivityFingerprint
|
||||
import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch
|
||||
import app.revanced.patches.youtube.misc.fix.playback.ClientSpoofPatch
|
||||
import app.revanced.patches.youtube.misc.microg.fingerprints.*
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME
|
||||
import app.revanced.util.microg.MicroGBytecodeHelper
|
||||
|
||||
@Patch(
|
||||
name = "Vanced MicroG support",
|
||||
description = "Allows YouTube to run without root and under a different package name with Vanced MicroG.",
|
||||
dependencies = [
|
||||
ChangePackageNamePatch::class,
|
||||
MicroGResourcePatch::class,
|
||||
HideCastButtonPatch::class,
|
||||
ClientSpoofPatch::class
|
||||
],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube", [
|
||||
"18.32.39",
|
||||
"18.37.36",
|
||||
"18.38.44",
|
||||
"18.43.45",
|
||||
"18.44.41",
|
||||
"18.45.41",
|
||||
"18.45.43"
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
object MicroGBytecodePatch : BytecodePatch(
|
||||
setOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint,
|
||||
PrimeFingerprint,
|
||||
WatchWhileActivityFingerprint
|
||||
)
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(REVANCED_PACKAGE_NAME)
|
||||
|
||||
// apply common microG patch
|
||||
MicroGBytecodeHelper.patchBytecode(
|
||||
context, arrayOf(
|
||||
MicroGBytecodeHelper.packageNameTransform(
|
||||
PACKAGE_NAME,
|
||||
packageName
|
||||
)
|
||||
),
|
||||
MicroGBytecodeHelper.PrimeMethodTransformationData(
|
||||
PrimeFingerprint,
|
||||
PACKAGE_NAME,
|
||||
packageName
|
||||
),
|
||||
listOf(
|
||||
ServiceCheckFingerprint,
|
||||
GooglePlayUtilityFingerprint,
|
||||
CastDynamiteModuleFingerprint,
|
||||
CastDynamiteModuleV2Fingerprint,
|
||||
CastContextFetchFingerprint
|
||||
)
|
||||
)
|
||||
|
||||
// inject the notice for MicroG
|
||||
MicroGBytecodeHelper.injectNotice(WatchWhileActivityFingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.microg
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
|
||||
import app.revanced.patches.shared.settings.preference.impl.Preference
|
||||
import app.revanced.patches.shared.settings.preference.impl.StringResource
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_APP_NAME
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.SPOOFED_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.microg.shared.Constants.SPOOFED_PACKAGE_SIGNATURE
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.util.microg.Constants.MICROG_VENDOR
|
||||
import app.revanced.util.microg.MicroGManifestHelper
|
||||
import app.revanced.util.microg.MicroGResourceHelper
|
||||
|
||||
@Patch(
|
||||
dependencies = [
|
||||
SettingsPatch::class,
|
||||
ChangePackageNamePatch::class
|
||||
]
|
||||
)
|
||||
object MicroGResourcePatch : ResourcePatch() {
|
||||
override fun execute(context: ResourceContext) {
|
||||
SettingsPatch.addPreference(
|
||||
Preference(
|
||||
StringResource("microg_settings", "MicroG Settings"),
|
||||
StringResource("microg_settings_summary", "Settings for MicroG"),
|
||||
Preference.Intent("$MICROG_VENDOR.android.gms", "", "org.microg.gms.ui.SettingsActivity")
|
||||
)
|
||||
)
|
||||
|
||||
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(REVANCED_PACKAGE_NAME)
|
||||
|
||||
SettingsPatch.renameIntentsTargetPackage(packageName)
|
||||
|
||||
// update manifest
|
||||
MicroGResourceHelper.patchManifest(
|
||||
context,
|
||||
PACKAGE_NAME,
|
||||
packageName,
|
||||
REVANCED_APP_NAME
|
||||
)
|
||||
|
||||
// add metadata to manifest
|
||||
MicroGManifestHelper.addSpoofingMetadata(
|
||||
context,
|
||||
SPOOFED_PACKAGE_NAME,
|
||||
SPOOFED_PACKAGE_SIGNATURE
|
||||
)
|
||||
|
||||
// add strings
|
||||
MicroGResourceHelper.addStrings(context)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.microg.fingerprints
|
||||
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
|
||||
internal object CastContextFetchFingerprint : MethodFingerprint(
|
||||
strings = listOf("Error fetching CastContext.")
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patches.youtube.misc.microg.shared
|
||||
|
||||
internal object Constants {
|
||||
const val REVANCED_APP_NAME = "YouTube ReVanced"
|
||||
const val REVANCED_PACKAGE_NAME = "app.revanced.android.youtube"
|
||||
const val PACKAGE_NAME = "com.google.android.youtube"
|
||||
const val SPOOFED_PACKAGE_NAME = PACKAGE_NAME
|
||||
const val SPOOFED_PACKAGE_SIGNATURE = "24bb24c05e47e0aefa68a58a766179d9b613a600"
|
||||
}
|
||||
@@ -17,13 +17,19 @@ import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.Minimize
|
||||
import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackSettingsParentFingerprint
|
||||
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Patch(
|
||||
name = "Minimized playback",
|
||||
description = "Enables minimized and background playback.",
|
||||
dependencies = [IntegrationsPatch::class, PlayerTypeHookPatch::class, SettingsPatch::class],
|
||||
dependencies = [
|
||||
IntegrationsPatch::class,
|
||||
PlayerTypeHookPatch::class,
|
||||
VideoInformationPatch::class,
|
||||
SettingsPatch::class
|
||||
],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.youtube",
|
||||
@@ -65,7 +71,7 @@ object MinimizedPlaybackPatch : BytecodePatch(
|
||||
mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->isPlaybackNotShort()Z
|
||||
invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->playbackIsNotShort()Z
|
||||
move-result v0
|
||||
return v0
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Remove tracking query parameter",
|
||||
description = "Remove the tracking query parameter from links.",
|
||||
description = "Remove the tracking query parameter from links you share.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -12,6 +12,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
|
||||
import app.revanced.patches.youtube.video.information.fingerprints.*
|
||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
||||
import app.revanced.patches.youtube.video.videoid.VideoIdPatch
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
@@ -26,7 +27,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@Patch(
|
||||
description = "Hooks YouTube to get information about the current playing video.",
|
||||
dependencies = [IntegrationsPatch::class, VideoIdPatch::class]
|
||||
dependencies = [IntegrationsPatch::class, VideoIdPatch::class, PlayerResponseMethodHookPatch::class]
|
||||
)
|
||||
object VideoInformationPatch : BytecodePatch(
|
||||
setOf(
|
||||
@@ -115,6 +116,10 @@ object VideoInformationPatch : BytecodePatch(
|
||||
VideoIdPatch.hookBackgroundPlayVideoId(videoIdMethodDescriptor)
|
||||
VideoIdPatch.hookPlayerResponseVideoId(
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(Ljava/lang/String;Z)V")
|
||||
// Call before any other video id hooks,
|
||||
// so they can use VideoInformation and check if the video id is for a Short.
|
||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameterBeforeVideoId(
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->newPlayerResponseSignature(Ljava/lang/String;Z)Ljava/lang/String;")
|
||||
|
||||
/*
|
||||
* Set the video time method
|
||||
|
||||
@@ -19,7 +19,7 @@ object PlayerResponseMethodHookPatch :
|
||||
Closeable,
|
||||
MutableSet<PlayerResponseMethodHookPatch.Hook> by mutableSetOf() {
|
||||
private const val VIDEO_ID_PARAMETER = 1
|
||||
private const val VIDEO_IS_OPENING_OR_PLAYING_PARAMETER = 11
|
||||
private const val IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER = 11
|
||||
private const val PROTO_BUFFER_PARAMETER_PARAMETER = 3
|
||||
|
||||
private lateinit var playerResponseMethod: MutableMethod
|
||||
@@ -31,13 +31,13 @@ object PlayerResponseMethodHookPatch :
|
||||
|
||||
override fun close() {
|
||||
fun hookVideoId(hook: Hook) = playerResponseMethod.addInstruction(
|
||||
0, "invoke-static {p$VIDEO_ID_PARAMETER, p$VIDEO_IS_OPENING_OR_PLAYING_PARAMETER}, $hook"
|
||||
0, "invoke-static {p$VIDEO_ID_PARAMETER, p$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook"
|
||||
)
|
||||
|
||||
fun hookProtoBufferParameter(hook: Hook) = playerResponseMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static {p$PROTO_BUFFER_PARAMETER_PARAMETER}, $hook
|
||||
invoke-static {p$PROTO_BUFFER_PARAMETER_PARAMETER, p$IS_SHORT_AND_OPENING_OR_PLAYING_PARAMETER}, $hook
|
||||
move-result-object p$PROTO_BUFFER_PARAMETER_PARAMETER
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
@Patch(
|
||||
name = "Remember video quality",
|
||||
description = "Adds the ability to remember the video quality you chose in the video quality flyout.",
|
||||
description = "Adds the ability to remember the last video quality selected.",
|
||||
dependencies = [IntegrationsPatch::class, VideoInformationPatch::class, SettingsPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -9,8 +9,7 @@ import app.revanced.patches.youtube.video.speed.remember.RememberPlaybackSpeedPa
|
||||
|
||||
@Patch(
|
||||
name = "Playback speed",
|
||||
description = "Adds custom playback speeds and ability " +
|
||||
"to remember the playback speed you chose in the video playback speed flyout.",
|
||||
description = "Adds custom playback speeds and ability to remember the last playback speed selected.",
|
||||
dependencies = [CustomPlaybackSpeedPatch::class, RememberPlaybackSpeedPatch::class],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
|
||||
@@ -19,7 +19,6 @@ import app.revanced.patches.youtube.video.speed.remember.fingerprint.InitializeP
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
|
||||
@Patch(
|
||||
description = "Adds the ability to remember the playback speed you chose in the playback speed flyout.",
|
||||
dependencies = [IntegrationsPatch::class, SettingsPatch::class, VideoInformationPatch::class, CustomPlaybackSpeedPatch::class]
|
||||
)
|
||||
object RememberPlaybackSpeedPatch : BytecodePatch(
|
||||
|
||||
@@ -104,7 +104,7 @@ object VideoIdPatch : BytecodePatch(
|
||||
* Supports all videos and functions in all situations.
|
||||
*
|
||||
* First parameter is the video id.
|
||||
* Second parameter is if the video is being opened or is currently playing.
|
||||
* Second parameter is if the video is a Short AND it is being opened or is currently playing.
|
||||
*
|
||||
* Hook is always called off the main thread.
|
||||
*
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
package app.revanced.util.microg
|
||||
|
||||
/**
|
||||
* constants for microG builds with signature spoofing
|
||||
*/
|
||||
internal object Constants {
|
||||
/**
|
||||
* microG vendor name
|
||||
* aka. package prefix / package base
|
||||
*/
|
||||
const val MICROG_VENDOR = "com.mgoogle"
|
||||
|
||||
/**
|
||||
* meta-data for microG package name spoofing on patched builds
|
||||
*/
|
||||
const val META_SPOOFED_PACKAGE_NAME = "$MICROG_VENDOR.android.gms.SPOOFED_PACKAGE_NAME"
|
||||
|
||||
/**
|
||||
* meta-data for microG package signature spoofing on patched builds
|
||||
*/
|
||||
const val META_SPOOFED_PACKAGE_SIGNATURE = "$MICROG_VENDOR.android.gms.SPOOFED_PACKAGE_SIGNATURE"
|
||||
|
||||
/**
|
||||
* meta-data for microG package detection
|
||||
*/
|
||||
const val META_GMS_PACKAGE_NAME = "app.revanced.MICROG_PACKAGE_NAME"
|
||||
|
||||
/**
|
||||
* a list of all permissions in microG
|
||||
*/
|
||||
val PERMISSIONS = listOf(
|
||||
// C2DM / GCM
|
||||
"com.google.android.c2dm.permission.RECEIVE",
|
||||
"com.google.android.c2dm.permission.SEND",
|
||||
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
|
||||
|
||||
// GAuth
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
|
||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
|
||||
)
|
||||
|
||||
/**
|
||||
* a list of all (intent) actions in microG
|
||||
*/
|
||||
val ACTIONS = listOf(
|
||||
// location
|
||||
"com.google.android.gms.location.places.ui.PICK_PLACE",
|
||||
"com.google.android.gms.location.places.GeoDataApi",
|
||||
"com.google.android.gms.location.places.PlacesApi",
|
||||
"com.google.android.gms.location.places.PlaceDetectionApi",
|
||||
"com.google.android.gms.wearable.MESSAGE_RECEIVED",
|
||||
|
||||
// C2DM / GCM
|
||||
"com.google.android.c2dm.intent.REGISTER",
|
||||
"com.google.android.c2dm.intent.REGISTRATION",
|
||||
"com.google.android.c2dm.intent.UNREGISTER",
|
||||
"com.google.android.c2dm.intent.RECEIVE",
|
||||
"com.google.iid.TOKEN_REQUEST",
|
||||
"com.google.android.gcm.intent.SEND",
|
||||
|
||||
// car
|
||||
"com.google.android.gms.car.service.START",
|
||||
|
||||
// people
|
||||
"com.google.android.gms.people.service.START",
|
||||
|
||||
// wearable
|
||||
"com.google.android.gms.wearable.BIND",
|
||||
|
||||
// auth
|
||||
"com.google.android.gsf.login",
|
||||
"com.google.android.gsf.action.GET_GLS",
|
||||
"com.google.android.gms.common.account.CHOOSE_ACCOUNT",
|
||||
"com.google.android.gms.auth.login.LOGIN",
|
||||
"com.google.android.gms.auth.api.credentials.PICKER",
|
||||
"com.google.android.gms.auth.api.credentials.service.START",
|
||||
"com.google.android.gms.auth.service.START",
|
||||
"com.google.firebase.auth.api.gms.service.START",
|
||||
"com.google.android.gms.auth.be.appcert.AppCertService",
|
||||
|
||||
// fido
|
||||
"com.google.android.gms.fido.fido2.privileged.START",
|
||||
|
||||
// games
|
||||
"com.google.android.gms.games.service.START",
|
||||
"com.google.android.gms.games.PLAY_GAMES_UPGRADE",
|
||||
|
||||
// chimera
|
||||
"com.google.android.gms.chimera",
|
||||
|
||||
// fonts
|
||||
"com.google.android.gms.fonts",
|
||||
|
||||
// phenotype
|
||||
"com.google.android.gms.phenotype.service.START",
|
||||
|
||||
// location
|
||||
"com.google.android.gms.location.reporting.service.START",
|
||||
|
||||
// misc
|
||||
"com.google.android.gms.gmscompliance.service.START",
|
||||
"com.google.android.gms.oss.licenses.service.START",
|
||||
"com.google.android.gms.safetynet.service.START",
|
||||
"com.google.android.gms.tapandpay.service.BIND"
|
||||
)
|
||||
|
||||
/**
|
||||
* a list of all content provider authorities in microG
|
||||
*/
|
||||
val AUTHORITIES = listOf(
|
||||
// gsf
|
||||
"com.google.android.gsf.gservices",
|
||||
"com.google.settings",
|
||||
|
||||
// auth
|
||||
"com.google.android.gms.auth.accounts",
|
||||
|
||||
// chimera
|
||||
"com.google.android.gms.chimera",
|
||||
|
||||
// fonts
|
||||
"com.google.android.gms.fonts",
|
||||
|
||||
// phenotype
|
||||
"com.google.android.gms.phenotype"
|
||||
)
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package app.revanced.util.microg
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.util.returnEarly
|
||||
import app.revanced.util.microg.Constants.ACTIONS
|
||||
import app.revanced.util.microg.Constants.AUTHORITIES
|
||||
import app.revanced.util.microg.Constants.MICROG_VENDOR
|
||||
import app.revanced.util.microg.Constants.PERMISSIONS
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
|
||||
|
||||
/**
|
||||
* Helper class for applying bytecode patches needed for the microg-support patches.
|
||||
*/
|
||||
internal object MicroGBytecodeHelper {
|
||||
/**
|
||||
* Hook a method to check the availability of MicroG.
|
||||
*
|
||||
* @param fingerprint The fingerprint of the method to add the call for the notice in.
|
||||
*/
|
||||
fun injectNotice(fingerprint: MethodFingerprint) {
|
||||
fingerprint.result!!.mutableMethod.addInstruction(
|
||||
0,
|
||||
"invoke-static {}, Lapp/revanced/integrations/patches/MicroGSupport;->checkAvailability()V"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform strings with package name out of [fromPackageName] and [toPackageName].
|
||||
*
|
||||
* @param fromPackageName Original package name.
|
||||
* @param toPackageName The package name to accept.
|
||||
**/
|
||||
fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? {
|
||||
return { referencedString ->
|
||||
when (referencedString) {
|
||||
"$fromPackageName.SuggestionsProvider",
|
||||
"$fromPackageName.fileprovider" -> referencedString.replace(fromPackageName, toPackageName)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime method data class for the [MicroGBytecodeHelper] class.
|
||||
*
|
||||
* @param primeMethodFingerprint The prime methods [MethodFingerprint].
|
||||
* @param fromPackageName Original package name.
|
||||
* @param toPackageName The package name to accept.
|
||||
**/
|
||||
data class PrimeMethodTransformationData(
|
||||
val primeMethodFingerprint: MethodFingerprint,
|
||||
val fromPackageName: String,
|
||||
val toPackageName: String
|
||||
) {
|
||||
/**
|
||||
* Patch the prime method to accept the new package name.
|
||||
*/
|
||||
fun transformPrimeMethodPackageName() {
|
||||
val primeMethod = primeMethodFingerprint.result!!.mutableMethod
|
||||
val implementation = primeMethod.implementation!!
|
||||
|
||||
var register = 2
|
||||
val index = implementation.instructions.indexOfFirst {
|
||||
if (it.opcode != Opcode.CONST_STRING) return@indexOfFirst false
|
||||
|
||||
val instructionString = ((it as Instruction21c).reference as StringReference).string
|
||||
if (instructionString != fromPackageName) return@indexOfFirst false
|
||||
|
||||
register = it.registerA
|
||||
return@indexOfFirst true
|
||||
}
|
||||
|
||||
primeMethod.replaceInstruction(
|
||||
index, "const-string v$register, \"$toPackageName\""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the bytecode to work with MicroG.
|
||||
* Note: this only handles string constants to gms (intent actions, authorities, ...).
|
||||
* If the app employs additional checks to validate the installed gms package, you'll have to handle those in the app- specific patch
|
||||
*
|
||||
* @param context The context.
|
||||
* @param additionalStringTransforms Additional transformations applied to all const-string references.
|
||||
* @param primeMethodTransformationData Data to patch the prime method.
|
||||
* @param earlyReturns List of [MethodFingerprint] to return the resolved methods early.
|
||||
*/
|
||||
fun patchBytecode(
|
||||
context: BytecodeContext,
|
||||
additionalStringTransforms: Array<(str: String) -> String?>,
|
||||
primeMethodTransformationData: PrimeMethodTransformationData,
|
||||
earlyReturns: List<MethodFingerprint>
|
||||
) {
|
||||
earlyReturns.returnEarly()
|
||||
primeMethodTransformationData.transformPrimeMethodPackageName()
|
||||
|
||||
val allTransforms = arrayOf(
|
||||
MicroGBytecodeHelper::commonTransform,
|
||||
MicroGBytecodeHelper::contentUrisTransform,
|
||||
*additionalStringTransforms
|
||||
)
|
||||
|
||||
// transform all strings using all provided transforms, first match wins
|
||||
context.transformStringReferences transform@{
|
||||
for (transformFn in allTransforms) {
|
||||
val s = transformFn(it)
|
||||
if (s != null) return@transform s
|
||||
}
|
||||
|
||||
return@transform null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* const-string transform function for common gms string references.
|
||||
*
|
||||
* @param referencedString The string to transform.
|
||||
*/
|
||||
private fun commonTransform(referencedString: String): String? =
|
||||
when (referencedString) {
|
||||
"com.google",
|
||||
"com.google.android.gms",
|
||||
in PERMISSIONS,
|
||||
in ACTIONS,
|
||||
in AUTHORITIES -> referencedString.replace("com.google", MICROG_VENDOR)
|
||||
|
||||
// subscribedfeeds has no vendor prefix for whatever reason...
|
||||
"subscribedfeeds" -> "${MICROG_VENDOR}.subscribedfeeds"
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* const-string transform function for strings containing gms content uris / authorities.
|
||||
*/
|
||||
private fun contentUrisTransform(str: String): String? {
|
||||
// only when content:// uri
|
||||
if (str.startsWith("content://")) {
|
||||
// check if matches any authority
|
||||
for (authority in AUTHORITIES) {
|
||||
val uriPrefix = "content://$authority"
|
||||
if (str.startsWith(uriPrefix)) {
|
||||
return str.replace(
|
||||
uriPrefix,
|
||||
"content://${authority.replace("com.google", MICROG_VENDOR)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// gms also has a 'subscribedfeeds' authority, check for that one too
|
||||
val subFeedsUriPrefix = "content://subscribedfeeds"
|
||||
if (str.startsWith(subFeedsUriPrefix)) {
|
||||
return str.replace(subFeedsUriPrefix, "content://${MICROG_VENDOR}.subscribedfeeds")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all constant string references using a transformation function.
|
||||
*
|
||||
* @param transformFn string transformation function. if null, string is not changed.
|
||||
*/
|
||||
private fun BytecodeContext.transformStringReferences(transformFn: (str: String) -> String?) {
|
||||
classes.forEach { classDef ->
|
||||
var mutableClass: MutableClass? = null
|
||||
|
||||
// enumerate all methods
|
||||
classDef.methods.forEach classLoop@{ methodDef ->
|
||||
var mutableMethod: MutableMethod? = null
|
||||
val implementation = methodDef.implementation ?: return@classLoop
|
||||
|
||||
// enumerate all instructions and find const-string
|
||||
implementation.instructions.forEachIndexed implLoop@{ index, instruction ->
|
||||
// skip all that are not const-string
|
||||
if (instruction.opcode != Opcode.CONST_STRING) return@implLoop
|
||||
val str = ((instruction as Instruction21c).reference as StringReference).string
|
||||
|
||||
// call transform function
|
||||
val transformedStr = transformFn(str)
|
||||
if (transformedStr != null) {
|
||||
// make class and method mutable, if not already
|
||||
mutableClass = mutableClass ?: proxy(classDef).mutableClass
|
||||
mutableMethod = mutableMethod ?: mutableClass!!.methods.first {
|
||||
it.name == methodDef.name && it.parameterTypes.containsAll(methodDef.parameterTypes)
|
||||
}
|
||||
|
||||
// replace instruction with updated string
|
||||
mutableMethod!!.implementation!!.replaceInstruction(
|
||||
index,
|
||||
BuilderInstruction21c(
|
||||
Opcode.CONST_STRING,
|
||||
instruction.registerA,
|
||||
ImmutableStringReference(
|
||||
transformedStr
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package app.revanced.util.microg
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.util.microg.Constants.META_GMS_PACKAGE_NAME
|
||||
import app.revanced.util.microg.Constants.META_SPOOFED_PACKAGE_NAME
|
||||
import app.revanced.util.microg.Constants.META_SPOOFED_PACKAGE_SIGNATURE
|
||||
import app.revanced.util.microg.Constants.MICROG_VENDOR
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
|
||||
/**
|
||||
* helper class for adding manifest metadata needed for microG builds with signature spoofing
|
||||
*/
|
||||
internal object MicroGManifestHelper {
|
||||
|
||||
/**
|
||||
* Add manifest entries needed for package and signature spoofing when using MicroG.
|
||||
* Note: this only adds metadata entries for signature spoofing, other changes may still be required to make a microG patch work.
|
||||
*
|
||||
* @param context Resource context.
|
||||
* @param spoofedPackage The package to spoof.
|
||||
* @param spoofedSignature The signature to spoof.
|
||||
*/
|
||||
fun addSpoofingMetadata(
|
||||
context: ResourceContext,
|
||||
spoofedPackage: String,
|
||||
spoofedSignature: String
|
||||
) {
|
||||
context.xmlEditor["AndroidManifest.xml"].use {
|
||||
val applicationNode = it
|
||||
.file
|
||||
.getElementsByTagName("application")
|
||||
.item(0)
|
||||
|
||||
// package spoofing
|
||||
applicationNode.adoptChild("meta-data") {
|
||||
setAttribute("android:name", META_SPOOFED_PACKAGE_NAME)
|
||||
setAttribute("android:value", spoofedPackage)
|
||||
}
|
||||
applicationNode.adoptChild("meta-data") {
|
||||
setAttribute("android:name", META_SPOOFED_PACKAGE_SIGNATURE)
|
||||
setAttribute("android:value", spoofedSignature)
|
||||
}
|
||||
|
||||
// microG presence detection in integrations
|
||||
applicationNode.adoptChild("meta-data") {
|
||||
setAttribute("android:name", META_GMS_PACKAGE_NAME)
|
||||
setAttribute("android:value", "${MICROG_VENDOR}.android.gms")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.adoptChild(tagName: String, block: Element.() -> Unit) {
|
||||
val child = ownerDocument.createElement(tagName)
|
||||
child.block()
|
||||
appendChild(child)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package app.revanced.util.microg
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.util.mergeStrings
|
||||
|
||||
/**
|
||||
* Helper class for applying resource patches needed for the microg-support patches.
|
||||
*/
|
||||
internal object MicroGResourceHelper {
|
||||
|
||||
/**
|
||||
* Add necessary strings to the strings.xml file.
|
||||
*
|
||||
* @param context The resource context.
|
||||
* @param stringsHost The file which hosts the strings.
|
||||
*/
|
||||
fun addStrings(context: ResourceContext, stringsHost: String = "microg/host/values/strings.xml") =
|
||||
context.mergeStrings(stringsHost)
|
||||
|
||||
/**
|
||||
* Patch the manifest to work with MicroG.
|
||||
*
|
||||
* @param context The resource context.
|
||||
* @param fromPackageName The original package name.
|
||||
* @param toPackageName The package name to accept.
|
||||
* @param toName The new name of the app.
|
||||
*/
|
||||
fun patchManifest(
|
||||
context: ResourceContext,
|
||||
fromPackageName: String,
|
||||
toPackageName: String,
|
||||
toName: String
|
||||
) {
|
||||
val manifest = context["AndroidManifest.xml"].readText()
|
||||
context["AndroidManifest.xml"].writeText(
|
||||
manifest.replace(
|
||||
"package=\"$fromPackageName",
|
||||
"package=\"$toPackageName"
|
||||
).replace(
|
||||
"android:label=\"@string/app_name",
|
||||
"android:label=\"$toName"
|
||||
).replace(
|
||||
"android:label=\"@string/app_launcher_name",
|
||||
"android:label=\"$toName"
|
||||
).replace(
|
||||
"android:authorities=\"$fromPackageName",
|
||||
"android:authorities=\"$toPackageName"
|
||||
).replace(
|
||||
"$fromPackageName.permission.C2D_MESSAGE",
|
||||
"$toPackageName.permission.C2D_MESSAGE"
|
||||
).replace(
|
||||
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
|
||||
"$toPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
).replace(
|
||||
"com.google.android.c2dm",
|
||||
"${Constants.MICROG_VENDOR}.android.c2dm"
|
||||
).replace(
|
||||
"</queries>",
|
||||
"<package android:name=\"${Constants.MICROG_VENDOR}.android.gms\"/></queries>"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
5
src/main/resources/gms/host/values/strings.xml
Normal file
5
src/main/resources/gms/host/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="gms_core_not_installed_warning">GmsCore is not installed. Please install it.</string>
|
||||
<string name="gms_core_not_running_warning">GmsCore is failing to run. Please follow the "Don't kill my app" guide for GmsCore.</string>
|
||||
</resources>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="microg_not_installed_warning">Vanced MicroG is not installed. Please install it.</string>
|
||||
<string name="microg_not_running_warning">Vanced MicroG is failing to run. Please follow the "Don't kill my app" guide for Vanced MicroG.</string>
|
||||
</resources>
|
||||
@@ -26,7 +26,7 @@
|
||||
<string name="revanced_ryd_compact_layout_summary_on">Like button styled for minimum width</string>
|
||||
<string name="revanced_ryd_compact_layout_summary_off">Like button styled for best appearance</string>
|
||||
|
||||
<string name="ryd_toast_on_connection_error_title">Show toast if API not available</string>
|
||||
<string name="ryd_toast_on_connection_error_title">Show toast if API is not available</string>
|
||||
<string name="ryd_toast_on_connection_error_summary_on">Toast shown if ReturnYouTubeDislike API is not available</string>
|
||||
<string name="ryd_toast_on_connection_error_summary_off">Toast not shown if ReturnYouTubeDislike API is not available</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user