From 5abf89444a3e6a211ec03c242eb9a7847542b08c Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Wed, 17 Apr 2024 20:01:17 +0400
Subject: [PATCH] feat(YouTube): Add 'About' preference to settings menu
 (#2981)

Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
---
 api/revanced-patches.api                      |  2 +
 .../integrations/BaseIntegrationsPatch.kt     | 62 ++++++++++++++++---
 .../ReVancedUtilsPatchesVersionFingerprint.kt | 16 +++++
 .../preference/NonInteractivePreference.kt    | 11 +++-
 .../layout/theme/ThemeBytecodePatch.kt        | 24 ++++++-
 .../ThemeHelperDarkColorFingerprint.kt        | 16 +++++
 .../ThemeHelperLightColorFingerprint.kt       | 16 +++++
 .../youtube/misc/settings/SettingsPatch.kt    | 15 ++++-
 .../resources/addresources/values/strings.xml |  5 ++
 9 files changed, 154 insertions(+), 13 deletions(-)
 create mode 100644 src/main/kotlin/app/revanced/patches/shared/misc/integrations/fingerprints/ReVancedUtilsPatchesVersionFingerprint.kt
 create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperDarkColorFingerprint.kt
 create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperLightColorFingerprint.kt

diff --git a/api/revanced-patches.api b/api/revanced-patches.api
index aa3ce12d..c17ab404 100644
--- a/api/revanced-patches.api
+++ b/api/revanced-patches.api
@@ -844,6 +844,8 @@ public final class app/revanced/patches/shared/misc/settings/preference/ListPref
 }
 
 public final class app/revanced/patches/shared/misc/settings/preference/NonInteractivePreference : app/revanced/patches/shared/misc/settings/preference/BasePreference {
+	public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
+	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
 	public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
 	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
 	public final fun getSelectable ()Z
diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch.kt
index c1c849c0..7c461f6e 100644
--- a/src/main/kotlin/app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch.kt
@@ -2,17 +2,21 @@ package app.revanced.patches.shared.misc.integrations
 
 import app.revanced.patcher.data.BytecodeContext
 import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
+import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
 import app.revanced.patcher.fingerprint.MethodFingerprint
 import app.revanced.patcher.patch.BytecodePatch
 import app.revanced.patcher.patch.PatchException
 import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint.IRegisterResolver
+import app.revanced.patches.shared.misc.integrations.fingerprints.ReVancedUtilsPatchesVersionFingerprint
+import app.revanced.util.resultOrThrow
 import com.android.tools.smali.dexlib2.Opcode
 import com.android.tools.smali.dexlib2.iface.ClassDef
 import com.android.tools.smali.dexlib2.iface.Method
+import java.util.jar.JarFile
 
 abstract class BaseIntegrationsPatch(
     private val hooks: Set<IntegrationsFingerprint>,
-) : BytecodePatch(hooks) {
+) : BytecodePatch(hooks + setOf(ReVancedUtilsPatchesVersionFingerprint)) {
 
     @Deprecated(
         "Use the constructor without the integrationsDescriptor parameter",
@@ -34,6 +38,46 @@ abstract class BaseIntegrationsPatch(
         hooks.forEach { hook ->
             hook.invoke(INTEGRATIONS_CLASS_DESCRIPTOR)
         }
+
+        // Modify Utils method to include the patches release version version.
+        ReVancedUtilsPatchesVersionFingerprint.resultOrThrow().mutableMethod.apply {
+            val manifestValue = getPatchesManifestEntry("Version")
+
+            addInstructions(
+                0,
+                """
+                    const-string v0, "$manifestValue"
+                    return-object v0
+                """,
+            )
+        }
+    }
+
+    /**
+     * @return The value for the manifest entry,
+     *         or "Unknown" if the entry does not exist or is blank.
+     */
+    @Suppress("SameParameterValue")
+    private fun getPatchesManifestEntry(attributeKey: String) = JarFile(getCurrentJarFilePath()).use { jarFile ->
+        jarFile.manifest.mainAttributes.entries.firstOrNull { it.key.toString() == attributeKey }?.value?.toString()
+            ?: "Unknown"
+    }
+
+    /**
+     * @return The file path for the jar this classfile is contained inside.
+     */
+    private fun getCurrentJarFilePath(): String {
+        val className = object {}::class.java.enclosingClass.name.replace('.', '/') + ".class"
+        val classUrl = object {}::class.java.classLoader.getResource(className)
+        if (classUrl != null) {
+            val urlString = classUrl.toString()
+
+            if (urlString.startsWith("jar:file:")) {
+                val end = urlString.indexOf('!')
+                return urlString.substring("jar:file:".length, end)
+            }
+        }
+        throw IllegalStateException("Not running from inside a JAR file.")
     }
 
     /**
@@ -50,7 +94,7 @@ abstract class BaseIntegrationsPatch(
         strings: Iterable<String>? = null,
         customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
         private val insertIndexResolver: ((Method) -> Int) = object : IHookInsertIndexResolver {},
-        private val contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {}
+        private val contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {},
     ) : MethodFingerprint(
         returnType,
         accessFlags,
@@ -59,9 +103,11 @@ abstract class BaseIntegrationsPatch(
         strings,
         customFingerprint,
     ) {
-        @Deprecated("Previous constructor that is missing the insert index." +
+        @Deprecated(
+            "Previous constructor that is missing the insert index." +
                 "Here only for binary compatibility, " +
-                "and this can be removed after the next major version update.")
+                "and this can be removed after the next major version update.",
+        )
         constructor(
             returnType: String? = null,
             accessFlags: Int? = null,
@@ -69,7 +115,7 @@ abstract class BaseIntegrationsPatch(
             opcodes: Iterable<Opcode?>? = null,
             strings: Iterable<String>? = null,
             customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
-            contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {}
+            contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {},
         ) : this(
             returnType,
             accessFlags,
@@ -78,7 +124,7 @@ abstract class BaseIntegrationsPatch(
             strings,
             customFingerprint,
             object : IHookInsertIndexResolver {},
-            contextRegisterResolver
+            contextRegisterResolver,
         )
 
         fun invoke(integrationsDescriptor: String) {
@@ -103,7 +149,7 @@ abstract class BaseIntegrationsPatch(
         }
     }
 
-    private companion object {
-        private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/shared/Utils;"
+    internal companion object {
+        internal const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/shared/Utils;"
     }
 }
diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/integrations/fingerprints/ReVancedUtilsPatchesVersionFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/misc/integrations/fingerprints/ReVancedUtilsPatchesVersionFingerprint.kt
new file mode 100644
index 00000000..2f577087
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/shared/misc/integrations/fingerprints/ReVancedUtilsPatchesVersionFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.shared.misc.integrations.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal object ReVancedUtilsPatchesVersionFingerprint : MethodFingerprint(
+    accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
+    returnType = "Ljava/lang/String;",
+    parameters = listOf(),
+    customFingerprint = { methodDef, classDef ->
+        methodDef.name == "getPatchesReleaseVersion" &&
+        classDef.type == BaseIntegrationsPatch.INTEGRATIONS_CLASS_DESCRIPTOR
+    }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/NonInteractivePreference.kt b/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/NonInteractivePreference.kt
index f9525f92..787115db 100644
--- a/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/NonInteractivePreference.kt
+++ b/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/NonInteractivePreference.kt
@@ -16,10 +16,19 @@ import org.w3c.dom.Document
 @Suppress("MemberVisibilityCanBePrivate")
 class NonInteractivePreference(
     key: String,
+    titleKey: String = "${key}_title",
     summaryKey: String? = "${key}_summary",
     tag: String = "Preference",
     val selectable: Boolean = false
-) : BasePreference(null, "${key}_title", summaryKey, tag) {
+) : BasePreference(key, titleKey, summaryKey, tag) {
+
+    @Deprecated("Here only for binary compatibility, and should be removed after the next major version update.")
+    constructor(
+        key: String,
+        summaryKey: String? = "${key}_summary",
+        tag: String = "Preference",
+        selectable: Boolean = false
+    ) : this(key, "${key}_title", summaryKey, tag, selectable)
 
     override fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit) =
         super.serialize(ownerDocument, resourceCallback).apply {
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt
index ac69564d..9859dac0 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch.kt
@@ -10,11 +10,14 @@ import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatc
 import app.revanced.patches.all.misc.resources.AddResourcesPatch
 import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
 import app.revanced.patches.youtube.layout.seekbar.SeekbarColorBytecodePatch
+import app.revanced.patches.youtube.layout.theme.fingerprints.ThemeHelperDarkColorFingerprint
+import app.revanced.patches.youtube.layout.theme.fingerprints.ThemeHelperLightColorFingerprint
 import app.revanced.patches.youtube.layout.theme.fingerprints.UseGradientLoadingScreenFingerprint
 import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch
 import app.revanced.patches.youtube.misc.settings.SettingsPatch
 import app.revanced.util.exception
 import app.revanced.util.indexOfFirstWideLiteralInstructionValue
+import app.revanced.util.resultOrThrow
 import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
 
 @Patch(
@@ -54,7 +57,11 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
 )
 @Suppress("unused")
 object ThemeBytecodePatch : BytecodePatch(
-    setOf(UseGradientLoadingScreenFingerprint)
+    setOf(
+        UseGradientLoadingScreenFingerprint,
+        ThemeHelperLightColorFingerprint,
+        ThemeHelperDarkColorFingerprint
+    )
 ) {
     private const val INTEGRATIONS_CLASS_DESCRIPTOR =
         "Lapp/revanced/integrations/youtube/patches/theme/ThemePatch;"
@@ -121,6 +128,21 @@ object ThemeBytecodePatch : BytecodePatch(
             )
         } ?: throw UseGradientLoadingScreenFingerprint.exception
 
+
+        mapOf(
+            ThemeHelperLightColorFingerprint to lightThemeBackgroundColor,
+            ThemeHelperDarkColorFingerprint to darkThemeBackgroundColor
+        ).forEach { (fingerprint, color) ->
+            fingerprint.resultOrThrow().mutableMethod.apply {
+                addInstructions(
+                    0, """
+                        const-string v0, "$color"
+                        return-object v0
+                    """
+                )
+            }
+        }
+
         LithoColorHookPatch.lithoColorOverrideHook(INTEGRATIONS_CLASS_DESCRIPTOR, "getValue")
     }
 }
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperDarkColorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperDarkColorFingerprint.kt
new file mode 100644
index 00000000..1f04e17e
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperDarkColorFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.youtube.layout.theme.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.youtube.misc.settings.SettingsPatch
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal object ThemeHelperDarkColorFingerprint : MethodFingerprint(
+    accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,
+    returnType = "Ljava/lang/String;",
+    parameters = listOf(),
+    customFingerprint = { methodDef, classDef ->
+        methodDef.name == "darkThemeResourceName" &&
+                classDef.type == SettingsPatch.THEME_HELPER_DESCRIPTOR
+    }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperLightColorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperLightColorFingerprint.kt
new file mode 100644
index 00000000..0defdd14
--- /dev/null
+++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/ThemeHelperLightColorFingerprint.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.youtube.layout.theme.fingerprints
+
+import app.revanced.patcher.extensions.or
+import app.revanced.patcher.fingerprint.MethodFingerprint
+import app.revanced.patches.youtube.misc.settings.SettingsPatch
+import com.android.tools.smali.dexlib2.AccessFlags
+
+internal object ThemeHelperLightColorFingerprint : MethodFingerprint(
+    accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,
+    returnType = "Ljava/lang/String;",
+    parameters = listOf(),
+    customFingerprint = { methodDef, classDef ->
+        methodDef.name == "lightThemeResourceName" &&
+                classDef.type == SettingsPatch.THEME_HELPER_DESCRIPTOR
+    }
+)
\ No newline at end of file
diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
index 0ada6925..6f96dcff 100644
--- a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
+++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
@@ -12,6 +12,7 @@ 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.NonInteractivePreference
 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
@@ -39,12 +40,20 @@ object SettingsPatch :
     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;"
+    internal 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)
 
+        // Add an about preference to the top.
+        SettingsResourcePatch += NonInteractivePreference(
+            key = "revanced_settings_screen_00_about",
+            summaryKey = null,
+            tag = "app.revanced.integrations.youtube.settings.preference.ReVancedYouTubeAboutPreference",
+            selectable = true,
+        )
+
         PreferenceScreen.MISC.addPreferences(
             TextPreference(
                 key = null,
@@ -52,7 +61,7 @@ object SettingsPatch :
                 summaryKey = "revanced_pref_import_export_summary",
                 inputType = InputType.TEXT_MULTI_LINE,
                 tag = "app.revanced.integrations.shared.settings.preference.ImportExportPreference",
-            ),
+            )
         )
 
         SetThemeFingerprint.result?.mutableMethod?.let { setThemeMethod ->
@@ -69,7 +78,7 @@ object SettingsPatch :
                     replaceInstruction(
                         returnIndex,
                         "invoke-static { v$register }, " +
-                            "$THEME_HELPER_DESCRIPTOR->$SET_THEME_METHOD_NAME(Ljava/lang/Object;)V",
+                            "$THEME_HELPER_DESCRIPTOR->$SET_THEME_METHOD_NAME(Ljava/lang/Enum;)V",
                     )
                     addInstruction(returnIndex + 1, "return-object v$register")
                 }
diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml
index 0157fc4c..a59505d3 100644
--- a/src/main/resources/addresources/values/strings.xml
+++ b/src/main/resources/addresources/values/strings.xml
@@ -53,10 +53,15 @@
     <app id="youtube">
         <patch id="misc.settings.SettingsResourcePatch">
             <string name="revanced_settings">ReVanced</string>
+            <string name="revanced_settings_about_links_body">You are using ReVanced Patches version &lt;i>%s&lt;/i></string>
+            <string name="revanced_settings_about_links_dev_header">Note</string>
+            <string name="revanced_settings_about_links_dev_body">This version is a pre-release and you may experience unexpected issues</string>
+            <string name="revanced_settings_about_links_header">Official links</string>
             <string name="revanced_pref_import_export_title">Import / Export</string>
             <string name="revanced_pref_import_export_summary">Import / Export ReVanced settings</string>
         </patch>
         <patch id="misc.settings.SettingsPatch">
+            <string name="revanced_settings_screen_00_about_title">About</string>
             <string name="revanced_settings_screen_01_ads_title">Ads</string>
             <string name="revanced_settings_screen_02_alt_thumbnails_title">Alternative thumbnails</string>
             <string name="revanced_settings_screen_03_feed_title">Feed</string>