mirror of
https://github.com/revanced/revanced-patches
synced 2024-11-08 14:36:58 +01:00
refactor(microg-support): share code between music-microg-support
patch
This commit is contained in:
parent
a1c1be7cd0
commit
642c332dfa
@ -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"
|
||||
// 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
|
||||
)
|
||||
) {
|
||||
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() {
|
||||
),
|
||||
MicroGBytecodeHelper.PrimeMethodTransformationData(
|
||||
PrimeFingerprint,
|
||||
MUSIC_PACKAGE_NAME,
|
||||
REVANCED_MUSIC_PACKAGE_NAME
|
||||
),
|
||||
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
|
||||
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() }
|
||||
}
|
||||
|
@ -8,37 +8,34 @@ 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.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.1")
|
||||
@Version("0.0.2")
|
||||
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(
|
||||
"</queries>", "<package android:name=\"$BASE_MICROG_PACKAGE_NAME.android.gms\"/></queries>"
|
||||
)
|
||||
// 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()
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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,77 +40,20 @@ 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"
|
||||
override fun execute(data: BytecodeData) =
|
||||
// apply common microG patch
|
||||
MicroGBytecodeHelper.patchBytecode(
|
||||
data, arrayOf(
|
||||
MicroGBytecodeHelper.packageNameTransform(
|
||||
PACKAGE_NAME,
|
||||
REVANCED_PACKAGE_NAME
|
||||
)
|
||||
) {
|
||||
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() {
|
||||
),
|
||||
MicroGBytecodeHelper.PrimeMethodTransformationData(
|
||||
PrimeFingerprint,
|
||||
PACKAGE_NAME,
|
||||
REVANCED_PACKAGE_NAME
|
||||
),
|
||||
listOf(
|
||||
IntegrityCheckFingerprint,
|
||||
ServiceCheckFingerprint,
|
||||
@ -129,43 +61,6 @@ class MicroGBytecodePatch : BytecodePatch(
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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() }
|
||||
}
|
||||
|
@ -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(
|
||||
"</queries>", "<package android:name=\"$BASE_MICROG_PACKAGE_NAME.android.gms\"/></queries>"
|
||||
)
|
||||
// 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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
130
src/main/kotlin/app/revanced/util/microg/Constants.kt
Normal file
130
src/main/kotlin/app/revanced/util/microg/Constants.kt
Normal file
@ -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"
|
||||
)
|
||||
}
|
229
src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt
Normal file
229
src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt
Normal file
@ -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<MethodFingerprint>
|
||||
) {
|
||||
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<MethodFingerprint>.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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(
|
||||
"</queries>",
|
||||
"<package android:name=\"${Constants.MICROG_VENDOR}.android.gms\"/></queries>"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user