From 0db4f4079b59437e6109b6bec6c4baeb820c65ba Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:08:59 +0200 Subject: [PATCH] refactor(microg-support): share code between `music-microg-support` patch --- .../bytecode/MusicMicroGBytecodePatch.kt | 178 +++----------- .../resource/MusicMicroGResourcePatch.kt | 83 +++---- .../music/misc/microg/shared/Constants.kt | 4 +- .../patch/bytecode/MicroGBytecodePatch.kt | 153 ++---------- .../patch/resource/MicroGResourcePatch.kt | 43 ++-- .../patch/resource/enum/StringReplaceMode.kt | 5 - .../youtube/misc/microg/shared/Constants.kt | 5 +- .../app/revanced/util/microg/Constants.kt | 130 ++++++++++ .../util/microg/MicroGBytecodeHelper.kt | 229 ++++++++++++++++++ .../util/microg/MicroGManifestHelper.kt | 58 +++++ .../util/microg/MicroGResourceHelper.kt | 49 ++++ 11 files changed, 595 insertions(+), 342 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/enum/StringReplaceMode.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/Constants.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt index edf67862..be04b566 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt @@ -1,37 +1,27 @@ package app.revanced.patches.music.misc.microg.patch.bytecode -import app.revanced.extensions.equalsAny import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.extensions.addInstructions -import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.impl.BytecodePatch -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultSuccess -import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patches.music.misc.microg.annotations.MusicMicroGPatchCompatibility -import app.revanced.patches.music.misc.microg.patch.resource.MusicMicroGResourcePatch -import app.revanced.patches.youtube.misc.microg.patch.resource.enum.StringReplaceMode -import app.revanced.patches.music.misc.microg.shared.Constants.BASE_MICROG_PACKAGE_NAME -import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME import app.revanced.patches.music.misc.microg.fingerprints.* -import org.jf.dexlib2.Opcode -import org.jf.dexlib2.builder.MutableMethodImplementation -import org.jf.dexlib2.builder.instruction.BuilderInstruction21c -import org.jf.dexlib2.iface.instruction.formats.Instruction21c -import org.jf.dexlib2.iface.reference.StringReference -import org.jf.dexlib2.immutable.reference.ImmutableStringReference +import app.revanced.patches.music.misc.microg.patch.resource.MusicMicroGResourcePatch +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.patches.youtube.misc.microg.shared.Constants +import app.revanced.util.microg.MicroGBytecodeHelper @Patch @DependsOn([MusicMicroGResourcePatch::class]) @Name("music-microg-support") @Description("Allows YouTube Music ReVanced to run without root and under a different package name.") @MusicMicroGPatchCompatibility -@Version("0.0.1") +@Version("0.0.2") class MusicMicroGBytecodePatch : BytecodePatch( listOf( ServiceCheckFingerprint, @@ -42,130 +32,34 @@ class MusicMicroGBytecodePatch : BytecodePatch( PrimeFingerprint, ) ) { - override fun execute(data: BytecodeData): PatchResult { - disablePlayServiceChecks() - data.classes.forEach { classDef -> - var proxiedClass: MutableClass? = null - - classDef.methods.forEach methodLoop@{ method -> - val implementation = method.implementation ?: return@methodLoop - - var proxiedImplementation: MutableMethodImplementation? = null - - implementation.instructions.forEachIndexed { i, instruction -> - if (instruction.opcode != Opcode.CONST_STRING) return@forEachIndexed - - val stringValue = ((instruction as Instruction21c).reference as StringReference).string - - val replaceMode = if (stringValue.equalsAny( - "com.google.android.gms", - "com.google.android.gms.chimera", - "com.google.android.c2dm.intent.REGISTER", - "com.google.android.c2dm.permission.SEND", - "com.google.iid.TOKEN_REQUEST", - "com.google", - "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.service.START", - "com.google.android.gms.phenotype.PACKAGE_NAME", - "com.google.android.gms.phenotype.UPDATE", - "com.google.android.gms.phenotype", - "com.google.android.gms.auth.accounts", - "com.google.android.c2dm.intent.REGISTRATION", - "com.google.android.gsf.action.GET_GLS", - "com.google.android.gsf.login", - "content://com.google.settings/partner", - "content://com.google.android.gms.phenotype/", - "content://com.google.android.gsf.gservices", - "content://com.google.android.gsf.gservices/prefix", - "com.google.android.c2dm.intent.RECEIVE" - ) - ) { - StringReplaceMode.REPLACE_WITH_MICROG - } else if (stringValue.equalsAny( - "com.google.android.apps.youtube.music.SuggestionsProvider", "com.google.android.apps.youtube.music.fileprovider" - ) - ) { - StringReplaceMode.REPLACE_WITH_REVANCED - } else { - StringReplaceMode.DO_NOT_REPLACE - } - - if (replaceMode != StringReplaceMode.DO_NOT_REPLACE) { - if (proxiedClass == null) { - proxiedClass = data.proxy(classDef).resolve() - } - - if (proxiedImplementation == null) { - proxiedImplementation = proxiedClass!!.methods.first { - it.name == method.name && it.parameterTypes.containsAll(method.parameterTypes) - }.implementation!! - } - - val newString = if (replaceMode == StringReplaceMode.REPLACE_WITH_REVANCED) stringValue.replace( - "com.google.android.apps.youtube.music", REVANCED_MUSIC_PACKAGE_NAME - ) - else stringValue.replace("com.google", BASE_MICROG_PACKAGE_NAME) - - proxiedImplementation!!.replaceInstruction( - i, BuilderInstruction21c( - Opcode.CONST_STRING, instruction.registerA, ImmutableStringReference(newString) - ) - ) - } - } - } - } - - return PatchResultSuccess() - } - - private fun disablePlayServiceChecks() { - listOf( - ServiceCheckFingerprint, - GooglePlayUtilityFingerprint, - CastDynamiteModuleFingerprint, - CastDynamiteModuleV2Fingerprint, - CastContextFetchFingerprint, - ).forEach { fingerprint -> - val result = fingerprint.result!! - val stringInstructions = when (result.method.returnType.first()) { - 'L' -> """ - const/4 v0, 0x0 - return-object v0 - """ - - 'V' -> "return-void" - 'I' -> """ - const/4 v0, 0x0 - return v0 - """ - - else -> throw Exception("This case should never happen.") - } - result.mutableMethod.addInstructions( - 0, stringInstructions + // 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(data: BytecodeData) = + // apply common microG patch + MicroGBytecodeHelper.patchBytecode( + data, + arrayOf( + MicroGBytecodeHelper.packageNameTransform( + Constants.PACKAGE_NAME, + Constants.REVANCED_PACKAGE_NAME + ) + ), + MicroGBytecodeHelper.PrimeMethodTransformationData( + PrimeFingerprint, + MUSIC_PACKAGE_NAME, + REVANCED_MUSIC_PACKAGE_NAME + ), + listOf( + ServiceCheckFingerprint, + GooglePlayUtilityFingerprint, + CastDynamiteModuleFingerprint, + CastDynamiteModuleV2Fingerprint, + CastContextFetchFingerprint ) - } - - val primeMethod = PrimeFingerprint.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 != "com.google.android.apps.youtube.music") return@indexOfFirst false - - register = it.registerA - return@indexOfFirst true - } - - primeMethod.replaceInstruction( - index, "const-string v$register, \"$REVANCED_MUSIC_PACKAGE_NAME\"" - ) - - } + ).let { PatchResultSuccess() } } diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt index 824f570a..341e1e78 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt @@ -1,44 +1,41 @@ -package app.revanced.patches.music.misc.microg.patch.resource - -import app.revanced.patcher.annotation.Description -import app.revanced.patcher.annotation.Name -import app.revanced.patcher.annotation.Version -import app.revanced.patcher.data.impl.ResourceData -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultSuccess -import app.revanced.patcher.patch.impl.ResourcePatch -import app.revanced.patches.music.misc.microg.annotations.MusicMicroGPatchCompatibility -import app.revanced.patches.music.misc.microg.shared.Constants.BASE_MICROG_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 - -@Name("music-microg-resource-patch") -@Description("Resource patch to allow YouTube Music ReVanced to run without root and under a different package name.") -@MusicMicroGPatchCompatibility -@Version("0.0.1") -class MusicMicroGResourcePatch : ResourcePatch() { - override fun execute(data: ResourceData): PatchResult { - - val manifest = data["AndroidManifest.xml"].readText() - - data["AndroidManifest.xml"].writeText( - manifest.replace( - "package=\"com.google.android.apps.youtube.music", "package=\"$REVANCED_MUSIC_PACKAGE_NAME" - ).replace( - "android:label=\"@string/app_name", "android:label=\"$REVANCED_MUSIC_APP_NAME" - ).replace( - "android:label=\"@string/app_launcher_name", "android:label=\"$REVANCED_MUSIC_APP_NAME" - ).replace( - "android:authorities=\"com.google.android.apps.youtube.music", "android:authorities=\"$REVANCED_MUSIC_PACKAGE_NAME" - ).replace( - "com.google.android.apps.youtube.music.permission.C2D_MESSAGE", "$REVANCED_MUSIC_PACKAGE_NAME.permission.C2D_MESSAGE" - ).replace( - "com.google.android.c2dm", "$BASE_MICROG_PACKAGE_NAME.android.c2dm" - ).replace( - "", "" - ) - ) - - return PatchResultSuccess() - } +package app.revanced.patches.music.misc.microg.patch.resource + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.ResourceData +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.impl.ResourcePatch +import app.revanced.patches.music.misc.microg.annotations.MusicMicroGPatchCompatibility +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 + +@Name("music-microg-resource-patch") +@Description("Resource patch to allow YouTube Music ReVanced to run without root and under a different package name.") +@MusicMicroGPatchCompatibility +@Version("0.0.2") +class MusicMicroGResourcePatch : ResourcePatch() { + override fun execute(data: ResourceData): PatchResult { + // update manifest + MicroGResourceHelper.patchManifest( + data, + MUSIC_PACKAGE_NAME, + REVANCED_MUSIC_PACKAGE_NAME, + REVANCED_MUSIC_APP_NAME + ) + + // add metadata to the manifest + MicroGManifestHelper.addSpoofingMetadata( + data, + SPOOFED_PACKAGE_NAME, + SPOOFED_PACKAGE_SIGNATURE + ) + return PatchResultSuccess() + } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt index 54a3e285..a5f452d9 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt @@ -1,7 +1,9 @@ package app.revanced.patches.music.misc.microg.shared object Constants { - internal const val BASE_MICROG_PACKAGE_NAME = "com.mgoogle" internal const val REVANCED_MUSIC_APP_NAME = "YouTube 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" } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt index d531f748..db91e9b8 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt @@ -1,32 +1,21 @@ package app.revanced.patches.youtube.misc.microg.patch.bytecode -import app.revanced.extensions.equalsAny import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.extensions.addInstructions -import app.revanced.patcher.extensions.replaceInstruction -import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.impl.BytecodePatch -import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patches.youtube.layout.castbutton.patch.HideCastButtonPatch import app.revanced.patches.youtube.misc.clientspoof.patch.ClientSpoofPatch import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibility import app.revanced.patches.youtube.misc.microg.fingerprints.* import app.revanced.patches.youtube.misc.microg.patch.resource.MicroGResourcePatch -import app.revanced.patches.youtube.misc.microg.patch.resource.enum.StringReplaceMode -import app.revanced.patches.youtube.misc.microg.shared.Constants.BASE_MICROG_PACKAGE_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME -import org.jf.dexlib2.Opcode -import org.jf.dexlib2.builder.MutableMethodImplementation -import org.jf.dexlib2.builder.instruction.BuilderInstruction21c -import org.jf.dexlib2.iface.instruction.formats.Instruction21c -import org.jf.dexlib2.iface.reference.StringReference -import org.jf.dexlib2.immutable.reference.ImmutableStringReference +import app.revanced.util.microg.MicroGBytecodeHelper @Patch @DependsOn( @@ -51,121 +40,27 @@ class MicroGBytecodePatch : BytecodePatch( PrimeFingerprint, ) ) { - override fun execute(data: BytecodeData): PatchResult { - disablePlayServiceChecksAndFixCastIssues() - data.classes.forEach { classDef -> - var proxiedClass: MutableClass? = null - - classDef.methods.forEach methodLoop@{ method -> - val implementation = method.implementation ?: return@methodLoop - - var proxiedImplementation: MutableMethodImplementation? = null - - implementation.instructions.forEachIndexed { i, instruction -> - if (instruction.opcode != Opcode.CONST_STRING) return@forEachIndexed - - val stringValue = ((instruction as Instruction21c).reference as StringReference).string - - val replaceMode = if (stringValue.equalsAny( - "com.google.android.gms", - "com.google.android.c2dm.intent.REGISTER", - "com.google.android.c2dm.permission.SEND", - "com.google.iid.TOKEN_REQUEST", - "com.google", - "com.google.android.gms.auth.accounts", - "com.google.android.c2dm.intent.REGISTRATION", - "com.google.android.gsf.action.GET_GLS", - "com.google.android.gsf.login", - "content://com.google.settings/partner", - "content://com.google.android.gsf.gservices", - "content://com.google.android.gsf.gservices/prefix", - "com.google.android.c2dm.intent.RECEIVE" - ) - ) { - StringReplaceMode.REPLACE_WITH_MICROG - } else if (stringValue.equalsAny( - "com.google.android.youtube.SuggestionsProvider", "com.google.android.youtube.fileprovider" - ) - ) { - StringReplaceMode.REPLACE_WITH_REVANCED - } else { - StringReplaceMode.DO_NOT_REPLACE - } - - if (replaceMode != StringReplaceMode.DO_NOT_REPLACE) { - if (proxiedClass == null) { - proxiedClass = data.proxy(classDef).resolve() - } - - if (proxiedImplementation == null) { - proxiedImplementation = proxiedClass!!.methods.first { - it.name == method.name && it.parameterTypes.containsAll(method.parameterTypes) - }.implementation!! - } - - val newString = if (replaceMode == StringReplaceMode.REPLACE_WITH_REVANCED) stringValue.replace( - "com.google.android.youtube", REVANCED_PACKAGE_NAME - ) - else stringValue.replace("com.google", BASE_MICROG_PACKAGE_NAME) - - proxiedImplementation!!.replaceInstruction( - i, BuilderInstruction21c( - Opcode.CONST_STRING, instruction.registerA, ImmutableStringReference(newString) - ) - ) - } - } - } - } - - return PatchResultSuccess() - } - - private fun disablePlayServiceChecksAndFixCastIssues() { - listOf( - IntegrityCheckFingerprint, - ServiceCheckFingerprint, - GooglePlayUtilityFingerprint, - CastDynamiteModuleFingerprint, - CastDynamiteModuleV2Fingerprint, - CastContextFetchFingerprint - ).forEach { fingerprint -> - val result = fingerprint.result!! - val stringInstructions = when (result.method.returnType.first()) { - 'L' -> """ - const/4 v0, 0x0 - return-object v0 - """ - - 'V' -> "return-void" - 'I' -> """ - const/4 v0, 0x0 - return v0 - """ - - else -> throw Exception("This case should never happen.") - } - result.mutableMethod.addInstructions( - 0, stringInstructions + override fun execute(data: BytecodeData) = + // apply common microG patch + MicroGBytecodeHelper.patchBytecode( + data, arrayOf( + MicroGBytecodeHelper.packageNameTransform( + PACKAGE_NAME, + REVANCED_PACKAGE_NAME + ) + ), + MicroGBytecodeHelper.PrimeMethodTransformationData( + PrimeFingerprint, + PACKAGE_NAME, + REVANCED_PACKAGE_NAME + ), + listOf( + IntegrityCheckFingerprint, + ServiceCheckFingerprint, + GooglePlayUtilityFingerprint, + CastDynamiteModuleFingerprint, + CastDynamiteModuleV2Fingerprint, + CastContextFetchFingerprint ) - } - - val primeMethod = PrimeFingerprint.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 != "com.google.android.youtube") return@indexOfFirst false - - register = it.registerA - return@indexOfFirst true - } - - primeMethod.replaceInstruction( - index, "const-string v$register, \"$REVANCED_PACKAGE_NAME\"" - ) - } + ).let { PatchResultSuccess() } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt index 20720f77..ea4a1e68 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt @@ -8,14 +8,21 @@ import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.impl.ResourcePatch +import app.revanced.patches.music.misc.microg.shared.Constants import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibility -import app.revanced.patches.youtube.misc.microg.shared.Constants.BASE_MICROG_PACKAGE_NAME +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.settings.resource.patch.SettingsResourcePatch +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.bytecode.patch.SettingsPatch import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource +import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch +import app.revanced.util.microg.Constants.MICROG_VENDOR +import app.revanced.util.microg.MicroGManifestHelper +import app.revanced.util.microg.MicroGResourceHelper @Name("microg-resource-patch") @DependsOn([FixLocaleConfigErrorPatch::class, SettingsResourcePatch::class]) @@ -27,32 +34,26 @@ class MicroGResourcePatch : ResourcePatch() { SettingsPatch.addPreference( Preference( StringResource("microg_settings", "MicroG Settings"), - Preference.Intent("$BASE_MICROG_PACKAGE_NAME.android.gms", "", "org.microg.gms.ui.SettingsActivity"), + Preference.Intent("$MICROG_VENDOR.android.gms", "", "org.microg.gms.ui.SettingsActivity"), StringResource("microg_settings_summary", "Settings for MicroG"), ) ) SettingsPatch.renameIntentsTargetPackage(REVANCED_PACKAGE_NAME) - val manifest = data["AndroidManifest.xml"] - manifest.writeText( - manifest.readText() - .replace( - "package=\"com.google.android.youtube", "package=\"$REVANCED_PACKAGE_NAME" - ).replace( - "android:authorities=\"com.google.android.youtube", "android:authorities=\"$REVANCED_PACKAGE_NAME" - ).replace( - "com.google.android.youtube.permission.C2D_MESSAGE", "$REVANCED_PACKAGE_NAME.permission.C2D_MESSAGE" - ).replace( // might not be needed - "com.google.android.youtube.lifecycle-trojan", "$REVANCED_PACKAGE_NAME.lifecycle-trojan" - ).replace( // might not be needed - "com.google.android.youtube.photopicker_images", "$REVANCED_PACKAGE_NAME.photopicker_images" - ).replace( - "com.google.android.c2dm", "$BASE_MICROG_PACKAGE_NAME.android.c2dm" - ).replace( - "", "" - ) + // update manifest + MicroGResourceHelper.patchManifest( + data, + PACKAGE_NAME, + REVANCED_PACKAGE_NAME, + REVANCED_APP_NAME ) + // add metadata to manifest + MicroGManifestHelper.addSpoofingMetadata( + data, + SPOOFED_PACKAGE_NAME, + SPOOFED_PACKAGE_SIGNATURE + ) return PatchResultSuccess() } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/enum/StringReplaceMode.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/enum/StringReplaceMode.kt deleted file mode 100644 index 63604edd..00000000 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/enum/StringReplaceMode.kt +++ /dev/null @@ -1,5 +0,0 @@ -package app.revanced.patches.youtube.misc.microg.patch.resource.enum - -enum class StringReplaceMode { - REPLACE_WITH_MICROG, REPLACE_WITH_REVANCED, DO_NOT_REPLACE -} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt index 768101e0..84b48de2 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt @@ -1,6 +1,9 @@ package app.revanced.patches.youtube.misc.microg.shared object Constants { - internal const val BASE_MICROG_PACKAGE_NAME = "com.mgoogle" + internal const val REVANCED_APP_NAME = "YouTube ReVanced" internal const val REVANCED_PACKAGE_NAME = "app.revanced.android.youtube" + internal const val PACKAGE_NAME = "com.google.android.youtube" + internal const val SPOOFED_PACKAGE_NAME = PACKAGE_NAME + internal const val SPOOFED_PACKAGE_SIGNATURE = "24bb24c05e47e0aefa68a58a766179d9b613a600" } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/microg/Constants.kt b/src/main/kotlin/app/revanced/util/microg/Constants.kt new file mode 100644 index 00000000..912b9756 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/Constants.kt @@ -0,0 +1,130 @@ +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" + ) +} diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt new file mode 100644 index 00000000..60ab5b42 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt @@ -0,0 +1,229 @@ +package app.revanced.util.microg + +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.util.proxy.mutableTypes.MutableClass +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +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 org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction21c +import org.jf.dexlib2.iface.instruction.formats.Instruction21c +import org.jf.dexlib2.iface.reference.StringReference +import org.jf.dexlib2.immutable.reference.ImmutableStringReference + +/** + * Helper class for applying bytecode patches needed for the microg-support patches. + */ +internal object MicroGBytecodeHelper { + + /** + * 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 data Bytecode data instance. + * @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( + data: BytecodeData, + additionalStringTransforms: Array<(str: String) -> String?>, + primeMethodTransformationData: PrimeMethodTransformationData, + earlyReturns: List + ) { + earlyReturns.returnEarly() + primeMethodTransformationData.transformPrimeMethodPackageName() + + val allTransforms = arrayOf( + MicroGBytecodeHelper::commonTransform, + MicroGBytecodeHelper::contentUrisTransform, + *additionalStringTransforms + ) + + // transform all strings using all provided transforms, first match wins + data.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 BytecodeData.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).resolve() + 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 + ) + ) + ) + } + } + } + } + } + + /** + * Return the resolved methods of a list of [MethodFingerprint] early. + */ + private fun List.returnEarly() { + this.forEach { fingerprint -> + val result = fingerprint.result!! + val stringInstructions = when (result.method.returnType.first()) { + 'L' -> """ + const/4 v0, 0x0 + return-object v0 + """ + + 'V' -> "return-void" + 'I' -> """ + const/4 v0, 0x0 + return v0 + """ + + else -> throw Exception("This case should never happen.") + } + result.mutableMethod.addInstructions( + 0, stringInstructions + ) + } + } +} diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt new file mode 100644 index 00000000..c2e87889 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt @@ -0,0 +1,58 @@ +package app.revanced.util.microg + +import app.revanced.patcher.data.impl.ResourceData +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 data Resource data. + * @param spoofedPackage The package to spoof. + * @param spoofedSignature The signature to spoof. + */ + fun addSpoofingMetadata( + data: ResourceData, + spoofedPackage: String, + spoofedSignature: String + ) { + data.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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt new file mode 100644 index 00000000..4d218686 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt @@ -0,0 +1,49 @@ +package app.revanced.util.microg + +import app.revanced.patcher.data.impl.ResourceData + +/** + * Helper class for applying resource patches needed for the microg-support patches. + */ +internal object MicroGResourceHelper { + /** + * Patch the manifest to work with MicroG. + * + * @param data Bytecode data instance. + * @param fromPackageName Original package name. + * @param toPackageName The package name to accept. + * @param toName The new name of the app. + */ + fun patchManifest( + data: ResourceData, + fromPackageName: String, + toPackageName: String, + toName: String + ) { + val manifest = data["AndroidManifest.xml"].readText() + data["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( + "com.google.android.c2dm", + "${Constants.MICROG_VENDOR}.android.c2dm" + ).replace( + "", + "" + ) + ) + } +} \ No newline at end of file