From 9b465d95887863f6b42baa6b710ed98c97383a82 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 2 May 2023 05:30:14 +0200 Subject: [PATCH] feat(youtube/theme): change seekbar color via preference --- .../CreateDarkThemeSeekbarFingerprint.kt | 25 ++++++++ .../bytecode/patch/ThemeBytecodePatch.kt | 51 +++++++++++++++ .../patch/ThemeLithoComponentsPatch.kt | 40 ++++++++++++ .../layout/theme/patch/LithoThemePatch.kt | 36 ----------- .../ThemeResourcePatch.kt} | 64 +++++++++++-------- 5 files changed, 153 insertions(+), 63 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/fingerprints/CreateDarkThemeSeekbarFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeLithoComponentsPatch.kt delete mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt rename src/main/kotlin/app/revanced/patches/youtube/layout/theme/{patch/ThemePatch.kt => resource/ThemeResourcePatch.kt} (54%) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/fingerprints/CreateDarkThemeSeekbarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/fingerprints/CreateDarkThemeSeekbarFingerprint.kt new file mode 100644 index 000000000..98baa9d69 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/fingerprints/CreateDarkThemeSeekbarFingerprint.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.youtube.layout.theme.bytecode.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.theme.bytecode.fingerprints.CreateDarkThemeSeekbarFingerprint.indexOfInstructionWithSeekbarId +import app.revanced.patches.youtube.layout.theme.resource.ThemeResourcePatch +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object CreateDarkThemeSeekbarFingerprint : MethodFingerprint( + access = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + customFingerprint = { method -> method.indexOfInstructionWithSeekbarId != -1 }, +) { + /** + * The index of the instruction that loads the resource id of the seekbar. + */ + internal val Method.indexOfInstructionWithSeekbarId + get() = implementation?.let { + it.instructions.indexOfFirst { instruction -> + instruction.opcode == Opcode.CONST && (instruction as WideLiteralInstruction).wideLiteral == ThemeResourcePatch.inlineTimeBarColorizedBarPlayedColorDarkId + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeBytecodePatch.kt new file mode 100644 index 000000000..b83c839f3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeBytecodePatch.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.youtube.layout.theme.bytecode.patch + +import app.revanced.extensions.toErrorResult +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.patch.BytecodePatch +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.patches.youtube.layout.theme.annotations.ThemeCompatibility +import app.revanced.patches.youtube.layout.theme.bytecode.fingerprints.CreateDarkThemeSeekbarFingerprint +import app.revanced.patches.youtube.layout.theme.bytecode.fingerprints.CreateDarkThemeSeekbarFingerprint.indexOfInstructionWithSeekbarId +import app.revanced.patches.youtube.layout.theme.resource.ThemeResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction + +@Patch +@Name("theme") +@Description("Applies a custom theme.") +@DependsOn([ThemeLithoComponentsPatch::class, ThemeResourcePatch::class, IntegrationsPatch::class]) +@ThemeCompatibility +@Version("0.0.1") +class ThemeBytecodePatch : BytecodePatch(listOf(CreateDarkThemeSeekbarFingerprint)) { + override fun execute(context: BytecodeContext): PatchResult { + CreateDarkThemeSeekbarFingerprint.result?.let { + val putColorValueIndex = it.method.indexOfInstructionWithSeekbarId!! + 3 + + it.mutableMethod.apply { + val overrideRegister = (instruction(putColorValueIndex) as TwoRegisterInstruction).registerA + + addInstructions( + putColorValueIndex, + """ + invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->getSeekbarColorValue()I + move-result v$overrideRegister + """ + ) + } + } ?: return CreateDarkThemeSeekbarFingerprint.toErrorResult() + return PatchResultSuccess() + } + + private companion object { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/theme/ThemePatch;" + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeLithoComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeLithoComponentsPatch.kt new file mode 100644 index 000000000..cfcf75ed0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/bytecode/patch/ThemeLithoComponentsPatch.kt @@ -0,0 +1,40 @@ +package app.revanced.patches.youtube.layout.theme.bytecode.patch + +import app.revanced.extensions.toErrorResult +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility +import app.revanced.patches.youtube.layout.theme.fingerprints.LithoThemeFingerprint + +@Name("theme-litho-components") +@Description("Applies a custom theme to Litho components.") +@ThemeCompatibility +@Version("0.0.1") +class ThemeLithoComponentsPatch : BytecodePatch(listOf(LithoThemeFingerprint)) { + override fun execute(context: BytecodeContext): PatchResult { + LithoThemeFingerprint.result?.let { + it.mutableMethod.apply { + val patchIndex = it.scanResult.patternScanResult!!.endIndex - 1 + + addInstructions( + patchIndex, + """ + invoke-static {p1}, $INTEGRATIONS_CLASS_DESCRIPTOR->getValue(I)I + move-result p1 + """ + ) + } + } ?: return LithoThemeFingerprint.toErrorResult() + return PatchResultSuccess() + } + + private companion object { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/theme/ThemeLithoComponentsPatch;" + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt deleted file mode 100644 index 6575ff4ec..000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt +++ /dev/null @@ -1,36 +0,0 @@ -package app.revanced.patches.youtube.layout.theme.patch - -import app.revanced.patcher.annotation.Description -import app.revanced.patcher.annotation.Name -import app.revanced.patcher.annotation.Version -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.addInstructions -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultSuccess -import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility -import app.revanced.patches.youtube.layout.theme.fingerprints.LithoThemeFingerprint - -@Name("litho-components-theme") -@Description("Applies a custom theme to litho components.") -@ThemeCompatibility -@Version("0.0.1") -class LithoThemePatch : BytecodePatch( - listOf( - LithoThemeFingerprint - ) -) { - override fun execute(context: BytecodeContext): PatchResult { - val result = LithoThemeFingerprint.result!! - val method = result.mutableMethod - val patchIndex = result.scanResult.patternScanResult!!.endIndex - 1 - - method.addInstructions( - patchIndex, """ - invoke-static {p1}, Lapp/revanced/integrations/patches/LithoThemePatch;->applyLithoTheme(I)I - move-result p1 - """ - ) - return PatchResultSuccess() - } -} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/resource/ThemeResourcePatch.kt similarity index 54% rename from src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/theme/resource/ThemeResourcePatch.kt index 8bc9c1c1c..12d396093 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/resource/ThemeResourcePatch.kt @@ -1,29 +1,45 @@ -package app.revanced.patches.youtube.layout.theme.patch +package app.revanced.patches.youtube.layout.theme.resource -import app.revanced.patcher.annotation.Description -import app.revanced.patcher.annotation.Name -import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.* import app.revanced.patcher.patch.annotations.DependsOn -import app.revanced.patcher.patch.annotations.Patch -import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.shared.settings.preference.impl.InputType +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.TextPreference +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch import app.revanced.util.resources.ResourceUtils import app.revanced.util.resources.ResourceUtils.copyResources import org.w3c.dom.Element -@Patch -@DependsOn([LithoThemePatch::class]) -@Name("theme") -@Description("Applies a custom theme.") -@ThemeCompatibility -@Version("0.0.1") -class ThemePatch : ResourcePatch { +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +class ThemeResourcePatch : ResourcePatch { override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + TextPreference( + "revanced_seekbar_color", + StringResource("revanced_seekbar_color_title", "Seekbar color"), + InputType.STRING, + "#ffff0000", + StringResource( + "revanced_seekbar_color_summary", + "The color of the seekbar for the dark theme." + ) + ), + ) + + // Edit theme colors via bytecode. + // For that the resource id is used in a bytecode patch to change the color. + + inlineTimeBarColorizedBarPlayedColorDarkId = ResourceMappingPatch.resourceMappings + .find { it.name == "inline_time_bar_colorized_bar_played_color_dark" }?.id + ?: return PatchResultError("Could not find seekbar resource") + + val darkThemeBackgroundColor = darkThemeBackgroundColor!! val lightThemeBackgroundColor = lightThemeBackgroundColor!! - val darkThemeSeekbarColor = darkThemeSeekbarColor!! + // Edit theme colors via resources. context.xmlEditor["res/values/colors.xml"].use { editor -> val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element @@ -31,18 +47,19 @@ class ThemePatch : ResourcePatch { val node = resourcesNode.childNodes.item(i) as? Element ?: continue node.textContent = when (node.getAttribute("name")) { - "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", "yt_black4", "yt_status_bar_background_dark", "material_grey_850" -> darkThemeBackgroundColor + "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", + "yt_black4", "yt_status_bar_background_dark", "material_grey_850" -> darkThemeBackgroundColor - "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", "yt_white2", "yt_white3", "yt_white4", + "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", + "yt_white2", "yt_white3", "yt_white4", -> lightThemeBackgroundColor - "inline_time_bar_colorized_bar_played_color_dark" -> darkThemeSeekbarColor else -> continue } } } - // copies the resource file to change the splash screen color + // Copy the resource file to change the splash screen color. context.copyResources( "theme", ResourceUtils.ResourceGroup("values-night-v31", "styles.xml") ) @@ -51,6 +68,8 @@ class ThemePatch : ResourcePatch { } companion object : OptionsContainer() { + internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L + var darkThemeBackgroundColor: String? by option( PatchOption.StringOption( key = "darkThemeBackgroundColor", @@ -68,14 +87,5 @@ class ThemePatch : ResourcePatch { description = "The background color of the light theme. Can be a hex color or a resource reference.", ) ) - - var darkThemeSeekbarColor: String? by option( - PatchOption.StringOption( - key = "darkThemeSeekbarColor", - default = "#ffff0000", - title = "Dark theme seekbar color", - description = "The background color of the seekbar of the dark theme. Leave empty for default color.", - ) - ) } }