diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/EnableDebuggingPatch.java similarity index 98% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/EnableDebuggingPatch.java index ff891edc1..424ba7b12 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/EnableDebuggingPatch.java @@ -1,4 +1,4 @@ -package app.revanced.extension.youtube.patches; +package app.revanced.extension.shared.patches; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/patches/api/patches.api b/patches/api/patches.api index 69db9f4de..aeaab90db 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -401,6 +401,10 @@ public final class app/revanced/patches/music/misc/backgroundplayback/Background public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/music/misc/debugging/EnableDebuggingPatchKt { + public static final fun getEnableDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/music/misc/extension/SharedExtensionPatchKt { public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt new file mode 100644 index 000000000..f4747b2fc --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.music.misc.debugging + +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch + +@Suppress("unused") +val enableDebuggingPatch = enableDebuggingPatch( + block = { + dependsOn( + sharedExtensionPatch, + settingsPatch, + ) + + compatibleWith( + "com.google.android.apps.youtube.music"( + "7.29.52" + ) + ) + }, + // String feature flag does not appear to be present with YT Music. + hookStringFeatureFlag = false, + preferenceScreen = PreferenceScreen.MISC +) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt index 09fab446b..5d282b715 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt @@ -8,12 +8,16 @@ import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch -import app.revanced.patches.shared.misc.settings.preference.* +import app.revanced.patches.shared.misc.settings.preference.BasePreference +import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen +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.PreferenceScreenPreference -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.settingsPatch -import app.revanced.util.* +import app.revanced.util.ResourceGroup +import app.revanced.util.copyResources +import app.revanced.util.copyXmlNode +import app.revanced.util.inputStreamFromBundledResource import com.android.tools.smali.dexlib2.util.MethodUtil private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR = @@ -23,7 +27,6 @@ private const val GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR = private val preferences = mutableSetOf() - private val settingsResourcePatch = resourcePatch { dependsOn( resourceMappingPatch, @@ -87,27 +90,6 @@ val settingsPatch = bytecodePatch( addResources("music", "misc.settings.settingsPatch") addResources("shared", "misc.debugging.enableDebuggingPatch") - // Should make a separate debugging patch, but for now include it with all installations. - PreferenceScreen.MISC.addPreferences( - PreferenceScreenPreference( - key = "revanced_debug_screen", - sorting = Sorting.UNSORTED, - preferences = setOf( - SwitchPreference("revanced_debug"), - NonInteractivePreference( - "revanced_debug_export_logs_to_clipboard", - tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", - selectable = true - ), - NonInteractivePreference( - "revanced_debug_logs_clear_buffer", - tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", - selectable = true - ) - ) - ) - ) - // Add an "About" preference to the top. preferences += NonInteractivePreference( key = "revanced_settings_music_screen_0_about", @@ -154,19 +136,19 @@ fun newIntent(settingsName: String) = IntentPreference.Intent( object PreferenceScreen : BasePreferenceScreen() { val ADS = Screen( - "revanced_settings_music_screen_1_ads", + key = "revanced_settings_music_screen_1_ads", summaryKey = null ) val GENERAL = Screen( - "revanced_settings_music_screen_2_general", + key = "revanced_settings_music_screen_2_general", summaryKey = null ) val PLAYER = Screen( - "revanced_settings_music_screen_3_player", + key = "revanced_settings_music_screen_3_player", summaryKey = null ) val MISC = Screen( - "revanced_settings_music_screen_4_misc", + key = "revanced_settings_music_screen_4_misc", summaryKey = null ) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt new file mode 100644 index 000000000..85cf53bd0 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt @@ -0,0 +1,147 @@ +package app.revanced.patches.shared.misc.debugging + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.BytecodePatchBuilder +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.BasePreference +import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen +import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.util.findInstructionIndicesReversedOrThrow +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/shared/patches/EnableDebuggingPatch;" + +/** + * Patch shared with YouTube and YT Music. + */ +internal fun enableDebuggingPatch( + block: BytecodePatchBuilder.() -> Unit = {}, + executeBlock: BytecodePatchContext.() -> Unit = {}, + hookStringFeatureFlag: Boolean, + preferenceScreen: BasePreferenceScreen.Screen, + additionalDebugPreferences: List = emptyList() +) = bytecodePatch( + name = "Enable debugging", + description = "Adds options for debugging and exporting ReVanced logs to the clipboard.", +) { + + dependsOn(addResourcesPatch) + + block() + + execute { + executeBlock() + + addResources("shared", "misc.debugging.enableDebuggingPatch") + + val preferences = mutableSetOf( + SwitchPreference("revanced_debug"), + ) + + preferences.addAll(additionalDebugPreferences) + + preferences.addAll( + listOf( + SwitchPreference("revanced_debug_stacktrace"), + SwitchPreference("revanced_debug_toast_on_error"), + NonInteractivePreference( + "revanced_debug_export_logs_to_clipboard", + tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", + selectable = true + ), + NonInteractivePreference( + "revanced_debug_logs_clear_buffer", + tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", + selectable = true + ) + ) + ) + + preferenceScreen.addPreferences( + PreferenceScreenPreference( + key = "revanced_debug_screen", + sorting = Sorting.UNSORTED, + preferences = preferences, + ) + ) + + // Hook the methods that look up if a feature flag is active. + experimentalBooleanFeatureFlagFingerprint.match( + experimentalFeatureFlagParentFingerprint.originalClassDef + ).method.apply { + findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> + val register = getInstruction(index).registerA + + addInstructions( + index, + """ + invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z + move-result v$register + """ + ) + } + } + + experimentalDoubleFeatureFlagFingerprint.match( + experimentalFeatureFlagParentFingerprint.originalClassDef + ).method.apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) + + addInstructions( + insertIndex, + """ + move-result-wide v0 # Also clobbers v1 (p0) since result is wide. + invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D + move-result-wide v0 + return-wide v0 + """ + ) + } + + experimentalLongFeatureFlagFingerprint.match( + experimentalFeatureFlagParentFingerprint.originalClassDef + ).method.apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) + + addInstructions( + insertIndex, + """ + move-result-wide v0 + invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J + move-result-wide v0 + return-wide v0 + """ + ) + } + + if (hookStringFeatureFlag) experimentalStringFeatureFlagFingerprint.match( + experimentalFeatureFlagParentFingerprint.originalClassDef + ).method.apply { + val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) + + addInstructions( + insertIndex, + """ + move-result-object v0 + invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 + """ + ) + } + + // There exists other experimental accessor methods for byte[] + // and wrappers for obfuscated classes, but currently none of those are hooked. + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/Fingerprints.kt new file mode 100644 index 000000000..6f183dd08 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/Fingerprints.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.shared.misc.debugging + +import app.revanced.patcher.fingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal val experimentalFeatureFlagParentFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returns("L") + parameters("L", "J", "[B") + strings("Unable to parse proto typed experiment flag: ") +} + +internal val experimentalBooleanFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returns("Z") + parameters("L", "J", "Z") +} + +internal val experimentalDoubleFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("D") + parameters("J", "D") +} + +internal val experimentalLongFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("J") + parameters("J", "J") +} + +internal val experimentalStringFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("Ljava/lang/String;") + parameters("J", "Ljava/lang/String;") +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index ab8c54afb..e2e4bc4fb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -1,144 +1,35 @@ package app.revanced.patches.youtube.misc.debugging -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.resources.addResources -import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting +import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.util.findInstructionIndicesReversedOrThrow -import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstInstructionReversedOrThrow -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -private const val EXTENSION_CLASS_DESCRIPTOR = - "Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;" - -// TODO: Refactor this into a shared patch that can be used by both YT and YT Music. -// Almost all of the feature flag hooks are the same between both apps. -val enableDebuggingPatch = bytecodePatch( - name = "Enable debugging", - description = "Adds options for debugging and exporting ReVanced logs to the clipboard.", -) { - dependsOn( - sharedExtensionPatch, - settingsPatch, - addResourcesPatch, - versionCheckPatch - ) - - compatibleWith( - "com.google.android.youtube"( - "19.34.42", - "19.43.41", - "19.47.53", - "20.07.39", - "20.12.46", - "20.13.41", +@Suppress("unused") +val enableDebuggingPatch = enableDebuggingPatch( + block = { + dependsOn( + sharedExtensionPatch, + settingsPatch, ) - ) - execute { - addResources("shared", "misc.debugging.enableDebuggingPatch") + compatibleWith( + "com.google.android.youtube"( + "19.34.42", + "19.43.41", + "19.47.53", + "20.07.39", + "20.12.46", + "20.13.41", + ) + ) + }, + executeBlock = { addResources("youtube", "misc.debugging.enableDebuggingPatch") - - PreferenceScreen.MISC.addPreferences( - PreferenceScreenPreference( - key = "revanced_debug_screen", - sorting = Sorting.UNSORTED, - preferences = setOf( - SwitchPreference("revanced_debug"), - SwitchPreference("revanced_debug_protobuffer"), - SwitchPreference("revanced_debug_stacktrace"), - SwitchPreference("revanced_debug_toast_on_error"), - NonInteractivePreference( - "revanced_debug_export_logs_to_clipboard", - tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", - selectable = true - ), - NonInteractivePreference( - "revanced_debug_logs_clear_buffer", - tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", - selectable = true - ), - ), - ), - ) - - // Hook the methods that look up if a feature flag is active. - experimentalBooleanFeatureFlagFingerprint.match( - experimentalFeatureFlagParentFingerprint.originalClassDef - ).method.apply { - findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> - val register = getInstruction(index).registerA - - addInstructions( - index, - """ - invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z - move-result v$register - """ - ) - } - } - - experimentalDoubleFeatureFlagFingerprint.match( - experimentalFeatureFlagParentFingerprint.originalClassDef - ).method.apply { - val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) - - addInstructions( - insertIndex, - """ - move-result-wide v0 # Also clobbers v1 (p0) since result is wide. - invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D - move-result-wide v0 - return-wide v0 - """ - ) - } - - experimentalLongFeatureFlagFingerprint.match( - experimentalFeatureFlagParentFingerprint.originalClassDef - ).method.apply { - val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) - - addInstructions( - insertIndex, - """ - move-result-wide v0 - invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J - move-result-wide v0 - return-wide v0 - """ - ) - } - - experimentalStringFeatureFlagFingerprint.match( - experimentalFeatureFlagParentFingerprint.originalClassDef - ).method.apply { - val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) - - addInstructions( - insertIndex, - """ - move-result-object v0 - invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; - move-result-object v0 - return-object v0 - """ - ) - } - - // There exists other experimental accessor methods for byte[] - // and wrappers for obfuscated classes, but currently none of those are hooked. - } -} + }, + hookStringFeatureFlag = true, + preferenceScreen = PreferenceScreen.MISC, + additionalDebugPreferences = listOf(SwitchPreference("revanced_debug_protobuffer")) +) diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 86d617500..5f4d5d5d7 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -163,6 +163,15 @@ Playback may not work" Debug logging Debug logs are enabled Debug logs are disabled + Log stack traces + Debug logs include stack trace + Debug logs do not include stack trace + Show toast on ReVanced error + Toast is shown if error occurs + Toast is not shown if error occurs + "Turning off error toasts hides all ReVanced error notifications. + +You will not be notified of any unexpected events." Export debug logs Copies ReVanced debug logs to the clipboard Debug logging is disabled @@ -209,15 +218,6 @@ Playback may not work" This can help identify components when creating custom filters. However, enabling this will also log some user data such as your IP address." - Log stack traces - Debug logs include stack trace - Debug logs do not include stack trace - Show toast on ReVanced error - Toast is shown if error occurs - Toast is not shown if error occurs - "Turning off error toasts hides all ReVanced error notifications. - -You will not be notified of any unexpected events." Hide album cards