|
| 1 | +package app.revanced.patches.shared.misc.debugging |
| 2 | + |
| 3 | +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions |
| 4 | +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction |
| 5 | +import app.revanced.patcher.patch.BytecodePatchBuilder |
| 6 | +import app.revanced.patcher.patch.BytecodePatchContext |
| 7 | +import app.revanced.patcher.patch.bytecodePatch |
| 8 | +import app.revanced.patches.all.misc.resources.addResources |
| 9 | +import app.revanced.patches.all.misc.resources.addResourcesPatch |
| 10 | +import app.revanced.patches.shared.misc.settings.preference.BasePreference |
| 11 | +import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen |
| 12 | +import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference |
| 13 | +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference |
| 14 | +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting |
| 15 | +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference |
| 16 | +import app.revanced.util.findInstructionIndicesReversedOrThrow |
| 17 | +import app.revanced.util.indexOfFirstInstructionOrThrow |
| 18 | +import app.revanced.util.indexOfFirstInstructionReversedOrThrow |
| 19 | +import com.android.tools.smali.dexlib2.Opcode |
| 20 | +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction |
| 21 | + |
| 22 | +private const val EXTENSION_CLASS_DESCRIPTOR = |
| 23 | + "Lapp/revanced/extension/shared/patches/EnableDebuggingPatch;" |
| 24 | + |
| 25 | +/** |
| 26 | + * Patch shared with YouTube and YT Music. |
| 27 | + */ |
| 28 | +internal fun enableDebuggingPatch( |
| 29 | + block: BytecodePatchBuilder.() -> Unit = {}, |
| 30 | + executeBlock: BytecodePatchContext.() -> Unit = {}, |
| 31 | + hookStringFeatureFlag: Boolean, |
| 32 | + preferenceScreen: BasePreferenceScreen.Screen, |
| 33 | + additionalDebugPreferences: List<BasePreference> = emptyList() |
| 34 | +) = bytecodePatch( |
| 35 | + name = "Enable debugging", |
| 36 | + description = "Adds options for debugging and exporting ReVanced logs to the clipboard.", |
| 37 | +) { |
| 38 | + |
| 39 | + dependsOn(addResourcesPatch) |
| 40 | + |
| 41 | + block() |
| 42 | + |
| 43 | + execute { |
| 44 | + executeBlock() |
| 45 | + |
| 46 | + addResources("shared", "misc.debugging.enableDebuggingPatch") |
| 47 | + |
| 48 | + val preferences = mutableSetOf<BasePreference>( |
| 49 | + SwitchPreference("revanced_debug"), |
| 50 | + ) |
| 51 | + |
| 52 | + preferences.addAll(additionalDebugPreferences) |
| 53 | + |
| 54 | + preferences.addAll( |
| 55 | + listOf( |
| 56 | + SwitchPreference("revanced_debug_stacktrace"), |
| 57 | + SwitchPreference("revanced_debug_toast_on_error"), |
| 58 | + NonInteractivePreference( |
| 59 | + "revanced_debug_export_logs_to_clipboard", |
| 60 | + tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", |
| 61 | + selectable = true |
| 62 | + ), |
| 63 | + NonInteractivePreference( |
| 64 | + "revanced_debug_logs_clear_buffer", |
| 65 | + tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", |
| 66 | + selectable = true |
| 67 | + ) |
| 68 | + ) |
| 69 | + ) |
| 70 | + |
| 71 | + preferenceScreen.addPreferences( |
| 72 | + PreferenceScreenPreference( |
| 73 | + key = "revanced_debug_screen", |
| 74 | + sorting = Sorting.UNSORTED, |
| 75 | + preferences = preferences, |
| 76 | + ) |
| 77 | + ) |
| 78 | + |
| 79 | + // Hook the methods that look up if a feature flag is active. |
| 80 | + experimentalBooleanFeatureFlagFingerprint.match( |
| 81 | + experimentalFeatureFlagParentFingerprint.originalClassDef |
| 82 | + ).method.apply { |
| 83 | + findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> |
| 84 | + val register = getInstruction<OneRegisterInstruction>(index).registerA |
| 85 | + |
| 86 | + addInstructions( |
| 87 | + index, |
| 88 | + """ |
| 89 | + invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z |
| 90 | + move-result v$register |
| 91 | + """ |
| 92 | + ) |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + experimentalDoubleFeatureFlagFingerprint.match( |
| 97 | + experimentalFeatureFlagParentFingerprint.originalClassDef |
| 98 | + ).method.apply { |
| 99 | + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) |
| 100 | + |
| 101 | + addInstructions( |
| 102 | + insertIndex, |
| 103 | + """ |
| 104 | + move-result-wide v0 # Also clobbers v1 (p0) since result is wide. |
| 105 | + invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D |
| 106 | + move-result-wide v0 |
| 107 | + return-wide v0 |
| 108 | + """ |
| 109 | + ) |
| 110 | + } |
| 111 | + |
| 112 | + experimentalLongFeatureFlagFingerprint.match( |
| 113 | + experimentalFeatureFlagParentFingerprint.originalClassDef |
| 114 | + ).method.apply { |
| 115 | + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE) |
| 116 | + |
| 117 | + addInstructions( |
| 118 | + insertIndex, |
| 119 | + """ |
| 120 | + move-result-wide v0 |
| 121 | + invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J |
| 122 | + move-result-wide v0 |
| 123 | + return-wide v0 |
| 124 | + """ |
| 125 | + ) |
| 126 | + } |
| 127 | + |
| 128 | + if (hookStringFeatureFlag) experimentalStringFeatureFlagFingerprint.match( |
| 129 | + experimentalFeatureFlagParentFingerprint.originalClassDef |
| 130 | + ).method.apply { |
| 131 | + val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT) |
| 132 | + |
| 133 | + addInstructions( |
| 134 | + insertIndex, |
| 135 | + """ |
| 136 | + move-result-object v0 |
| 137 | + invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String; |
| 138 | + move-result-object v0 |
| 139 | + return-object v0 |
| 140 | + """ |
| 141 | + ) |
| 142 | + } |
| 143 | + |
| 144 | + // There exists other experimental accessor methods for byte[] |
| 145 | + // and wrappers for obfuscated classes, but currently none of those are hooked. |
| 146 | + } |
| 147 | +} |
0 commit comments