diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/ForceOriginalAudioPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/ForceOriginalAudioPatch.java new file mode 100644 index 000000000..30a2cb8f2 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/ForceOriginalAudioPatch.java @@ -0,0 +1,17 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class ForceOriginalAudioPatch { + + /** + * Injection point. + */ + public static void setPreferredLanguage() { + app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled( + Settings.FORCE_ORIGINAL_AUDIO.get(), + Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() + ); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index 4feb13d9c..a56fb04b9 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -32,4 +32,6 @@ public class Settings extends BaseSettings { // Miscellaneous public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS)); + + public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/ForceOriginalAudioPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/ForceOriginalAudioPatch.java new file mode 100644 index 000000000..762a77352 --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/ForceOriginalAudioPatch.java @@ -0,0 +1,73 @@ +package app.revanced.extension.shared.patches; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.settings.AppLanguage; +import app.revanced.extension.shared.spoof.ClientType; +import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; + +@SuppressWarnings("unused") +public class ForceOriginalAudioPatch { + + private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4"; + + private static volatile boolean enabled = false; + + public static void setEnabled(boolean isEnabled, ClientType client) { + enabled = isEnabled; + + if (isEnabled + && SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams() + && !client.useAuth) { + // If client spoofing does not use authentication and lacks multi-audio streams, + // then can use any language code for the request and if that requested language is + // not available YT uses the original audio language. Authenticated requests ignore + // the language code and always use the account language. Use a language that is + // not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972 + // but the language is also supported natively by the Meta Quest device that + // Android VR is spoofing. + AppLanguage override = AppLanguage.NB; // Norwegian Bokmal. + Logger.printDebug(() -> "Setting language override: " + override); + SpoofVideoStreamsPatch.setLanguageOverride(override); + } + } + + /** + * Injection point. + */ + public static boolean ignoreDefaultAudioStream(boolean original) { + if (enabled) { + return false; + } + return original; + } + + /** + * Injection point. + */ + public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) { + try { + if (!enabled) { + return isDefault; + } + + if (audioTrackId.isEmpty()) { + // Older app targets can have empty audio tracks and these might be placeholders. + // The real audio tracks are called after these. + return isDefault; + } + + Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: " + + String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName); + + final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX); + if (isOriginal) { + Logger.printDebug(() -> "Using audio: " + audioTrackId); + } + + return isOriginal; + } catch (Exception ex) { + Logger.printException(() -> "isDefaultAudioStream failure", ex); + return isDefault; + } + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ForceOriginalAudioSwitchPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ForceOriginalAudioSwitchPreference.java similarity index 87% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ForceOriginalAudioSwitchPreference.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ForceOriginalAudioSwitchPreference.java index b6be1b893..fdcde3668 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ForceOriginalAudioSwitchPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ForceOriginalAudioSwitchPreference.java @@ -1,4 +1,4 @@ -package app.revanced.extension.youtube.settings.preference; +package app.revanced.extension.shared.settings.preference; import static app.revanced.extension.shared.StringRef.str; @@ -6,17 +6,17 @@ import android.content.Context; import android.preference.SwitchPreference; import android.util.AttributeSet; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.spoof.ClientType; import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; -import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings({"deprecation", "unused"}) public class ForceOriginalAudioSwitchPreference extends SwitchPreference { // Spoof stream patch is not included, or is not currently spoofing to Android Studio. private static final boolean available = !SpoofVideoStreamsPatch.isPatchIncluded() - || !(Settings.SPOOF_VIDEO_STREAMS.get() - && Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_CREATOR); + || !(BaseSettings.SPOOF_VIDEO_STREAMS.get() + && SpoofVideoStreamsPatch.getPreferredClient() == ClientType.ANDROID_CREATOR); { if (!available) { diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java index a5f69ba30..8275f9c71 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java @@ -66,6 +66,10 @@ public class SpoofVideoStreamsPatch { StreamingDataRequest.setClientOrderToUse(availableClients, client); } + public static ClientType getPreferredClient() { + return preferredClient; + } + public static boolean spoofingToClientWithNoMultiAudioStreams() { return isPatchIncluded() && SPOOF_STREAMING_DATA diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java index bf3b866d8..5540b670d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java @@ -1,72 +1,17 @@ package app.revanced.extension.youtube.patches; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.AppLanguage; -import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") public class ForceOriginalAudioPatch { - private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4"; - /** * Injection point. */ public static void setPreferredLanguage() { - if (Settings.FORCE_ORIGINAL_AUDIO.get() - && SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams() - && !Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get().useAuth) { - // If client spoofing does not use authentication and lacks multi-audio streams, - // then can use any language code for the request and if that requested language is - // not available YT uses the original audio language. Authenticated requests ignore - // the language code and always use the account language. Use a language that is - // not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972 - // but the language is also supported natively by the Meta Quest device that - // Android VR is spoofing. - AppLanguage override = AppLanguage.NB; // Norwegian Bokmal. - Logger.printDebug(() -> "Setting language override: " + override); - SpoofVideoStreamsPatch.setLanguageOverride(override); - } - } - - /** - * Injection point. - */ - public static boolean ignoreDefaultAudioStream(boolean original) { - if (Settings.FORCE_ORIGINAL_AUDIO.get()) { - return false; - } - return original; - } - - /** - * Injection point. - */ - public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) { - try { - if (!Settings.FORCE_ORIGINAL_AUDIO.get()) { - return isDefault; - } - - if (audioTrackId.isEmpty()) { - // Older app targets can have empty audio tracks and these might be placeholders. - // The real audio tracks are called after these. - return isDefault; - } - - Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: " - + String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName); - - final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX); - if (isOriginal) { - Logger.printDebug(() -> "Using audio: " + audioTrackId); - } - - return isOriginal; - } catch (Exception ex) { - Logger.printException(() -> "isDefaultAudioStream failure", ex); - return isDefault; - } + app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled( + Settings.FORCE_ORIGINAL_AUDIO.get(), + Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() + ); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 17937a660..cfe87af71 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -55,6 +55,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE); public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE); public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting("revanced_force_avc_codec", FALSE, true, "revanced_force_avc_codec_user_dialog_message"); + public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE); @@ -75,9 +76,6 @@ public class Settings extends BaseSettings { public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true); - // Audio - public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, true); - // Ads public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE); public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true); diff --git a/patches/api/patches.api b/patches/api/patches.api index 2e35481c9..01e889e44 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -459,9 +459,14 @@ public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPat public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatchKt { + public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/music/playservice/VersionCheckPatchKt { public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun is_7_33_or_greater ()Z + public static final fun is_8_10_or_greater ()Z public static final fun is_8_11_or_greater ()Z public static final fun is_8_15_or_greater ()Z } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatch.kt new file mode 100644 index 000000000..8130cbe71 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/tracks/ForceOriginalAudioPatch.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.music.misc.tracks + +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.music.playservice.is_8_10_or_greater +import app.revanced.patches.music.playservice.versionCheckPatch +import app.revanced.patches.music.shared.mainActivityOnCreateFingerprint +import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/music/patches/ForceOriginalAudioPatch;" + +@Suppress("unused") +val forceOriginalAudioPatch = forceOriginalAudioPatch( + block = { + dependsOn( + sharedExtensionPatch, + settingsPatch, + versionCheckPatch + ) + + compatibleWith( + "com.google.android.apps.youtube.music"( + "7.29.52", + "8.10.52" + ) + ) + }, + fixUseLocalizedAudioTrackFlag = is_8_10_or_greater, + mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint, + subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR, + preferenceScreen = PreferenceScreen.MISC, +) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/playservice/VersionCheckPatch.kt index 467498bf2..b14d6b659 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/playservice/VersionCheckPatch.kt @@ -7,6 +7,8 @@ import app.revanced.util.findPlayStoreServicesVersion var is_7_33_or_greater = false private set +var is_8_10_or_greater = false + private set var is_8_11_or_greater = false private set var is_8_15_or_greater = false @@ -22,6 +24,7 @@ val versionCheckPatch = resourcePatch( // All bug fix releases always seem to use the same play store version as the minor version. is_7_33_or_greater = 245199000 <= playStoreServicesVersion + is_8_10_or_greater = 244799000 <= playStoreServicesVersion is_8_11_or_greater = 251199000 <= playStoreServicesVersion is_8_15_or_greater = 251530000 <= playStoreServicesVersion } diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/boostforreddit/fix/redgifs/FixRedgifsApiPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/boostforreddit/fix/redgifs/FixRedgifsApiPatch.kt index f19a5964d..340f4096c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/boostforreddit/fix/redgifs/FixRedgifsApiPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/boostforreddit/fix/redgifs/FixRedgifsApiPatch.kt @@ -1,6 +1,5 @@ package app.revanced.patches.reddit.customclients.boostforreddit.fix.redgifs -import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patches.reddit.customclients.CREATE_NEW_CLIENT_METHOD import app.revanced.patches.reddit.customclients.boostforreddit.misc.extension.sharedExtensionPatch @@ -27,9 +26,7 @@ val fixRedgifsApi = fixRedgifsApiPatch( } replaceInstruction( index, - """ - invoke-static { }, ${EXTENSION_CLASS_DESCRIPTOR}->$CREATE_NEW_CLIENT_METHOD - """ + "invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->$CREATE_NEW_CLIENT_METHOD" ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/Fingerprints.kt similarity index 67% rename from patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/Fingerprints.kt rename to patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/Fingerprints.kt index 8e3755bc8..495ac4865 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/Fingerprints.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.video.audio +package app.revanced.patches.shared.misc.audio import app.revanced.patcher.fingerprint import app.revanced.util.containsLiteralInstruction @@ -7,10 +7,14 @@ import com.android.tools.smali.dexlib2.AccessFlags internal val formatStreamModelToStringFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("Ljava/lang/String;") - custom { method, classDef -> - method.name == "toString" && classDef.type == - "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;" + custom { method, _ -> + method.name == "toString" } + strings( + // Strings are partial matches. + "isDefaultAudioTrack=", + "audioTrackId=" + ) } internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L @@ -20,7 +24,6 @@ internal val selectAudioStreamFingerprint = fingerprint { returns("L") custom { method, _ -> method.parameters.size > 2 // Method has a large number of parameters and may change. - && method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" && method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/ForceOriginalAudioPatch.kt new file mode 100644 index 000000000..0f5d8f1ac --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/ForceOriginalAudioPatch.kt @@ -0,0 +1,157 @@ +package app.revanced.patches.shared.misc.audio + +import app.revanced.patcher.Fingerprint +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +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.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.all.misc.resources.addResources +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.SwitchPreference +import app.revanced.util.findMethodFromToString +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.insertLiteralOverride +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.immutable.ImmutableField +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/shared/patches/ForceOriginalAudioPatch;" + +/** + * Patch shared with YouTube and YT Music. + */ +internal fun forceOriginalAudioPatch( + block: BytecodePatchBuilder.() -> Unit = {}, + executeBlock: BytecodePatchContext.() -> Unit = {}, + fixUseLocalizedAudioTrackFlag: Boolean, + mainActivityOnCreateFingerprint: Fingerprint, + subclassExtensionClassDescriptor: String, + preferenceScreen: BasePreferenceScreen.Screen +) = bytecodePatch( + name = "Force original audio", + description = "Adds an option to always use the original audio track.", +) { + + block() + + dependsOn(addResourcesPatch) + + execute { + addResources("shared", "misc.audio.forceOriginalAudioPatch") + + preferenceScreen.addPreferences( + SwitchPreference( + key = "revanced_force_original_audio", + tag = "app.revanced.extension.shared.settings.preference.ForceOriginalAudioSwitchPreference" + ) + ) + + mainActivityOnCreateFingerprint.method.addInstruction( + 0, + "invoke-static { }, $subclassExtensionClassDescriptor->setPreferredLanguage()V" + ) + + // Disable feature flag that ignores the default track flag + // and instead overrides to the user region language. + if (fixUseLocalizedAudioTrackFlag) { + selectAudioStreamFingerprint.method.insertLiteralOverride( + AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z" + ) + } + + formatStreamModelToStringFingerprint.let { + val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=") + val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=") + val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=") + + it.classDef.apply { + // Add a new field to store the override. + val helperFieldName = "patch_isDefaultAudioTrackOverride" + fields.add( + ImmutableField( + type, + helperFieldName, + "Ljava/lang/Boolean;", + // Boolean is a 100% immutable class (all fields are final) + // and safe to write to a shared field without volatile/synchronization, + // but without volatile the field can show stale data + // and the same field is calculated more than once by different threads. + AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value, + null, + null, + null + ).toMutable() + ) + + // Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed. + val helperMethodClass = type + val helperMethodName = "patch_isDefaultAudioTrack" + val helperMethod = ImmutableMethod( + helperMethodClass, + helperMethodName, + listOf(ImmutableMethodParameter("Z", null, null)), + "Z", + AccessFlags.PRIVATE.value, + null, + null, + MutableMethodImplementation(6), + ).toMutable().apply { + addInstructionsWithLabels( + 0, + """ + iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean; + if-eqz v0, :call_extension + invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z + move-result v3 + return v3 + + :call_extension + invoke-virtual { p0 }, $audioTrackIdMethod + move-result-object v1 + + invoke-virtual { p0 }, $audioTrackDisplayNameMethod + move-result-object v2 + + invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z + move-result v3 + + invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean; + move-result-object v0 + iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean; + return v3 + """ + ) + } + methods.add(helperMethod) + + // Modify isDefaultAudioTrack() to call extension helper method. + isDefaultAudioTrackMethod.apply { + val index = indexOfFirstInstructionOrThrow(Opcode.RETURN) + val register = getInstruction(index).registerA + + addInstructions( + index, + """ + invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z + move-result v$register + """ + ) + } + } + } + + executeBlock() + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt index 08450dc4d..463785754 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/shortsplayer/OpenShortsInRegularPlayerPatch.kt @@ -161,7 +161,7 @@ val openShortsInRegularPlayerPatch = bytecodePatch( addInstructions( index + 1, """ - invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->overrideBackPressToExit(Z)Z + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->overrideBackPressToExit(Z)Z move-result v$register """ ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt index ee6750c60..083fdf2e2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt @@ -164,7 +164,7 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig addInstruction( index + 1, - "invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->setToolbar(Landroid/widget/FrameLayout;)V" + "invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->setToolbar(Landroid/widget/FrameLayout;)V" ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt index bc985a70f..3b9097095 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt @@ -1,159 +1,36 @@ package app.revanced.patches.youtube.video.audio -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patches.all.misc.resources.addResources -import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.shared.misc.audio.forceOriginalAudioPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater 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.patches.youtube.shared.mainActivityOnCreateFingerprint -import app.revanced.util.findMethodFromToString -import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.insertLiteralOverride -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.immutable.ImmutableField -import com.android.tools.smali.dexlib2.immutable.ImmutableMethod -import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;" @Suppress("unused") -val forceOriginalAudioPatch = bytecodePatch( - name = "Force original audio", - description = "Adds an option to always use the original audio track.", -) { - dependsOn( - sharedExtensionPatch, - settingsPatch, - addResourcesPatch, - versionCheckPatch - ) - - compatibleWith( - "com.google.android.youtube"( - "19.34.42", - "20.07.39", - "20.13.41", - "20.14.43", +val forceOriginalAudioPatch = forceOriginalAudioPatch( + block = { + dependsOn( + sharedExtensionPatch, + settingsPatch, + versionCheckPatch ) - ) - execute { - addResources("youtube", "video.audio.forceOriginalAudioPatch") - - PreferenceScreen.VIDEO.addPreferences( - SwitchPreference( - key = "revanced_force_original_audio", - tag = "app.revanced.extension.youtube.settings.preference.ForceOriginalAudioSwitchPreference" + compatibleWith( + "com.google.android.youtube"( + "19.34.42", + "20.07.39", + "20.13.41", + "20.14.43", ) ) - - mainActivityOnCreateFingerprint.method.addInstruction( - 0, - "invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setPreferredLanguage()V" - ) - - // Disable feature flag that ignores the default track flag - // and instead overrides to the user region language. - if (is_20_07_or_greater) { - selectAudioStreamFingerprint.method.insertLiteralOverride( - AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG, - "$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z" - ) - } - - formatStreamModelToStringFingerprint.let { - val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=") - val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=") - val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=") - - it.classDef.apply { - // Add a new field to store the override. - val helperFieldName = "patch_isDefaultAudioTrackOverride" - fields.add( - ImmutableField( - type, - helperFieldName, - "Ljava/lang/Boolean;", - // Boolean is a 100% immutable class (all fields are final) - // and safe to write to a shared field without volatile/synchronization, - // but without volatile the field can show stale data - // and the same field is calculated more than once by different threads. - AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value, - null, - null, - null - ).toMutable() - ) - - // Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed. - val helperMethodClass = type - val helperMethodName = "patch_isDefaultAudioTrack" - val helperMethod = ImmutableMethod( - helperMethodClass, - helperMethodName, - listOf(ImmutableMethodParameter("Z", null, null)), - "Z", - AccessFlags.PRIVATE.value, - null, - null, - MutableMethodImplementation(6), - ).toMutable().apply { - addInstructionsWithLabels( - 0, - """ - iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean; - if-eqz v0, :call_extension - invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z - move-result v3 - return v3 - - :call_extension - invoke-virtual { p0 }, $audioTrackIdMethod - move-result-object v1 - - invoke-virtual { p0 }, $audioTrackDisplayNameMethod - move-result-object v2 - - invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z - move-result v3 - - invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean; - move-result-object v0 - iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean; - return v3 - """ - ) - } - methods.add(helperMethod) - - // Modify isDefaultAudioTrack() to call extension helper method. - isDefaultAudioTrackMethod.apply { - val index = indexOfFirstInstructionOrThrow(Opcode.RETURN) - val register = getInstruction(index).registerA - - addInstructions( - index, - """ - invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z - move-result v$register - """ - ) - } - } - } - } -} + }, + fixUseLocalizedAudioTrackFlag = is_20_07_or_greater, + mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint, + subclassExtensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR, + preferenceScreen = PreferenceScreen.VIDEO, +) diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index e7fe5167b..a23b530ae 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -176,6 +176,13 @@ Playback may not work" Turning off this setting may cause playback issues. Default client + + Force original audio language + Using original audio language + Using default audio + + To use this feature, change \'Spoof video streams\' to any client except Android Studio + Debugging Enable or disable debugging options @@ -1590,13 +1597,6 @@ Enabling this can unlock higher video qualities" Opening links in external browser Opening links in in-app browser - - Force original audio language - Using original audio language - Using default audio - - To use this feature, change \'Spoof video streams\' to any client except Android Studio - Auto