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 (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V public synthetic fun (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, -) : 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? = 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? = null, strings: Iterable? = 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 @@ ReVanced + You are using ReVanced Patches version <i>%s</i> + Note + This version is a pre-release and you may experience unexpected issues + Official links Import / Export Import / Export ReVanced settings + About Ads Alternative thumbnails Feed