revanced-patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt

172 lines
7.4 KiB
Kotlin

package app.revanced.patches.youtube.misc.settings
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.packagename.ChangePackageNamePatch
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.fingerprints.LicenseActivityOnCreateFingerprint
import app.revanced.patches.youtube.misc.settings.fingerprints.SetThemeFingerprint
import app.revanced.util.exception
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.util.MethodUtil
import java.io.Closeable
@Patch(
description = "Adds settings for ReVanced to YouTube.",
dependencies = [
IntegrationsPatch::class,
SettingsResourcePatch::class,
AddResourcesPatch::class,
],
)
object SettingsPatch :
BytecodePatch(
setOf(LicenseActivityOnCreateFingerprint, SetThemeFingerprint),
),
Closeable {
private const val INTEGRATIONS_PACKAGE = "app/revanced/integrations/youtube"
private const val ACTIVITY_HOOK_CLASS_DESCRIPTOR = "L$INTEGRATIONS_PACKAGE/settings/LicenseActivityHook;"
private const val THEME_HELPER_DESCRIPTOR = "L$INTEGRATIONS_PACKAGE/ThemeHelper;"
private const val SET_THEME_METHOD_NAME: String = "setTheme"
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
PreferenceScreen.MISC.addPreferences(
TextPreference(
key = null,
titleKey = "revanced_pref_import_export_title",
summaryKey = "revanced_pref_import_export_summary",
inputType = InputType.TEXT_MULTI_LINE,
tag = "app.revanced.integrations.shared.settings.preference.ImportExportPreference",
),
)
SetThemeFingerprint.result?.mutableMethod?.let { setThemeMethod ->
setThemeMethod.implementation!!.instructions.mapIndexedNotNull { i, instruction ->
if (instruction.opcode == Opcode.RETURN_OBJECT) i else null
}.asReversed().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.
setThemeMethod.apply {
// This register is returned by the setTheme method.
val register = getInstruction<OneRegisterInstruction>(returnIndex).registerA
replaceInstruction(
returnIndex,
"invoke-static { v$register }, " +
"$THEME_HELPER_DESCRIPTOR->$SET_THEME_METHOD_NAME(Ljava/lang/Object;)V",
)
addInstruction(returnIndex + 1, "return-object v$register")
}
}
} ?: throw SetThemeFingerprint.exception
// Modify the license activity and remove all existing layout code.
// Must modify an existing activity and cannot add a new activity to the manifest,
// as that fails for root installations.
LicenseActivityOnCreateFingerprint.result?.let { result ->
result.mutableMethod.addInstructions(
1,
"""
invoke-static { p0 }, $ACTIVITY_HOOK_CLASS_DESCRIPTOR->initialize(Landroid/app/Activity;)V
return-void
""",
)
// Remove other methods as they will break as the onCreate method is modified above.
result.mutableClass.apply {
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
}
} ?: throw LicenseActivityOnCreateFingerprint.exception
}
/**
* Creates an intent to open ReVanced settings.
*/
fun newIntent(settingsName: String) = IntentPreference.Intent(
data = settingsName,
targetClass = "com.google.android.libraries.social.licenses.LicenseActivity",
) {
// The package name change has to be reflected in the intent.
ChangePackageNamePatch.setOrGetFallbackPackageName("com.google.android.youtube")
}
object PreferenceScreen : BasePreferenceScreen() {
// Sort screens in the root menu by key, to not scatter related items apart
// (sorting key is set in revanced_prefs.xml).
// If no preferences are added to a screen, the screen will not be added to the settings.
val ADS = Screen(
key = "revanced_settings_screen_01_ads",
summaryKey = null,
)
val ALTERNATIVE_THUMBNAILS = Screen(
key = "revanced_settings_screen_02_alt_thumbnails",
summaryKey = null,
sorting = Sorting.UNSORTED,
)
val FEED = Screen(
key = "revanced_settings_screen_03_feed",
summaryKey = null,
)
val PLAYER = Screen(
key = "revanced_settings_screen_04_player",
summaryKey = null,
)
val GENERAL_LAYOUT = Screen(
key = "revanced_settings_screen_05_general",
summaryKey = null,
)
// Don't sort, as related preferences are scattered apart.
// Can use title sorting after PreferenceCategory support is added.
val SHORTS = Screen(
key = "revanced_settings_screen_06_shorts",
summaryKey = null,
sorting = Sorting.UNSORTED,
)
// Don't sort, because title sorting scatters the custom color preferences.
val SEEKBAR = Screen(
key = "revanced_settings_screen_07_seekbar",
summaryKey = null,
sorting = Sorting.UNSORTED,
)
val SWIPE_CONTROLS = Screen(
key = "revanced_settings_screen_08_swipe_controls",
summaryKey = null,
sorting = Sorting.UNSORTED,
)
// RYD and SB are items 9 and 10.
// Menus are added in their own patch because they use an Intent and not a Screen.
val MISC = Screen(
key = "revanced_settings_screen_11_misc",
summaryKey = null,
)
val VIDEO = Screen(
key = "revanced_settings_screen_12_video",
summaryKey = null,
)
override fun commit(screen: app.revanced.patches.shared.misc.settings.preference.PreferenceScreen) {
SettingsResourcePatch += screen
}
}
override fun close() = PreferenceScreen.close()
}