refactor(youtube/settings): increase fingerprint robustness

This commit is contained in:
oSumAtrIX 2023-05-18 02:57:50 +02:00
parent 758b300591
commit a64f33e385
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
4 changed files with 46 additions and 93 deletions

View File

@ -5,12 +5,16 @@ import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourc
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
object ThemeSetterSystemFingerprint : MethodFingerprint(
"L",
object SetThemeFingerprint : MethodFingerprint(
returnType = "L",
opcodes = listOf(Opcode.RETURN_OBJECT),
customFingerprint = { methodDef, _ ->
methodDef.implementation?.instructions?.any {
it.opcode.ordinal == Opcode.CONST.ordinal && (it as WideLiteralInstruction).wideLiteral == SettingsResourcePatch.appearanceStringId
} == true
methodDef.implementation?.instructions?.any { instruction ->
if (instruction.opcode != Opcode.CONST) return@any false
val wideLiteral = (instruction as WideLiteralInstruction).wideLiteral
SettingsResourcePatch.appearanceStringId == wideLiteral
} ?: false
}
)

View File

@ -1,25 +0,0 @@
package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object ThemeSetterAppFingerprint : MethodFingerprint(
"L",
AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L", "L", "L", "L"),
opcodes = listOf(
Opcode.CONST, // target reference
Opcode.GOTO,
Opcode.CONST, // target reference
Opcode.INVOKE_DIRECT,
Opcode.RETURN_OBJECT,
Opcode.NEW_INSTANCE,
null, // changed from invoke interface to invoke virtual
Opcode.MOVE_RESULT_OBJECT,
Opcode.SGET_OBJECT,
Opcode.IF_NE,
Opcode.CONST, // target reference
)
)

View File

@ -7,6 +7,8 @@ import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.instruction
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
@ -15,69 +17,48 @@ import app.revanced.patches.shared.settings.preference.impl.Preference
import app.revanced.patches.shared.settings.util.AbstractPreferenceScreen
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.LicenseActivityFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterAppFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterSystemFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.SetThemeFingerprint
import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
import org.jf.dexlib2.util.MethodUtil
@DependsOn(
[
IntegrationsPatch::class,
SettingsResourcePatch::class,
]
)
@DependsOn([IntegrationsPatch::class, SettingsResourcePatch::class, ])
@Name("settings")
@Description("Adds settings for ReVanced to YouTube.")
@Version("0.0.1")
class SettingsPatch : BytecodePatch(
listOf(LicenseActivityFingerprint, ThemeSetterSystemFingerprint, ThemeSetterAppFingerprint)
listOf(LicenseActivityFingerprint, SetThemeFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
fun buildInvokeInstructionsString(
// TODO: Remove this when it is only required at one place.
fun getSetThemeInstructionString(
registers: String = "v0",
classDescriptor: String = THEME_HELPER_DESCRIPTOR,
methodName: String = SET_THEME_METHOD_NAME,
parameters: String = "Ljava/lang/Object;"
) = "invoke-static {$registers}, $classDescriptor->$methodName($parameters)V"
) = "invoke-static { $registers }, $classDescriptor->$methodName($parameters)V"
// apply the current theme of the settings page
ThemeSetterSystemFingerprint.result!!.let { result ->
val call = buildInvokeInstructionsString()
result.mutableMethod.apply {
// TODO: The label is pointing to the instruction below, but should instead point to the new instruction.
addInstruction(
result.scanResult.patternScanResult!!.startIndex, call
)
addInstructions(
implementation!!.instructions.size - 1, call
)
}
}
SetThemeFingerprint.result?.mutableMethod?.let { setThemeMethod ->
setThemeMethod.implementation!!.instructions.mapIndexedNotNull { i, instruction ->
if (instruction.opcode == Opcode.RETURN_OBJECT) i else null
}
.asReversed() // Prevent index shifting.
.forEach { returnIndex ->
// The following strategy is to replace the return instruction with the setTheme instruction,
// then add a return instruction after the setTheme instruction.
// This is done because the return instruction is a target of another instruction.
// set the theme based on the preference of the app
ThemeSetterAppFingerprint.result?.apply {
fun buildInstructionsString(theme: Int) = """
const/4 v0, 0x$theme
${buildInvokeInstructionsString(parameters = "I")}
"""
setThemeMethod.apply {
// This register is returned by the setTheme method.
val register = instruction<OneRegisterInstruction>(returnIndex).registerA
val patternScanResult = scanResult.patternScanResult!!
mutableMethod.apply {
addInstructions(
patternScanResult.endIndex + 1, buildInstructionsString(1)
)
addInstructions(
patternScanResult.endIndex - 7, buildInstructionsString(0)
)
addInstructions(
patternScanResult.endIndex - 9, buildInstructionsString(1)
)
addInstructions(
implementation!!.instructions.size - 2, buildInstructionsString(0)
)
}
} ?: return ThemeSetterAppFingerprint.toErrorResult()
val setThemeInstruction = getSetThemeInstructionString("v$register")
replaceInstruction(returnIndex, setThemeInstruction)
addInstruction(returnIndex + 1, "return-object v0")
}
}
} ?: return SetThemeFingerprint.toErrorResult()
// set the theme based on the preference of the device
LicenseActivityFingerprint.result!!.apply licenseActivity@{
@ -87,7 +68,7 @@ class SettingsPatch : BytecodePatch(
classDescriptor: String = SETTINGS_ACTIVITY_DESCRIPTOR,
methodName: String = "initializeSettings",
parameters: String = this@licenseActivity.mutableClass.type
) = buildInvokeInstructionsString(registers, classDescriptor, methodName, parameters)
) = getSetThemeInstructionString(registers, classDescriptor, methodName, parameters)
// initialize the settings
addInstructions(

View File

@ -29,21 +29,17 @@ class SettingsResourcePatch : AbstractSettingsResourcePatch(
override fun execute(context: ResourceContext): PatchResult {
super.execute(context)
/*
* used by a fingerprint of SettingsPatch
*/
// Used for a fingerprint from SettingsPatch.
appearanceStringId = ResourceMappingPatch.resourceMappings.find {
it.type == "string" && it.name == "app_theme_appearance_dark"
}!!.id
/*
* create missing directory for the resources
*/
// Create missing directory for the resources.
context["res/drawable-ldrtl-xxxhdpi"].mkdirs()
/*
* copy layout resources
*/
// Copy layout resources.
arrayOf(
ResourceUtils.ResourceGroup(
"layout",
@ -63,7 +59,7 @@ class SettingsResourcePatch : AbstractSettingsResourcePatch(
preferencesEditor = context.xmlEditor["res/xml/settings_fragment.xml"]
// Add the ReVanced settings to the YouTube settings
// Add the ReVanced settings to the YouTube settings.
val youtubePackage = "com.google.android.youtube"
SettingsPatch.addPreference(
Preference(
@ -92,11 +88,8 @@ class SettingsResourcePatch : AbstractSettingsResourcePatch(
internal companion object {
// Used by a fingerprint of SettingsPatch
// this field is located in the SettingsResourcePatch
// because if it were to be defined in the SettingsPatch companion object,
// the companion object could be initialized before ResourceMappingResourcePatch has executed.
internal var appearanceStringId: Long = -1
// Used for a fingerprint from SettingsPatch.
internal var appearanceStringId = -1L
// if this is not null, all intents will be renamed to this
var overrideIntentsTargetPackage: String? = null