mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-11 13:46:17 +00:00
fix(YouTube - Force original audio): Disable a/b feature flag that forces localized audio (#5582)
This commit is contained in:
committed by
GitHub
parent
8c31374c53
commit
1dd01cf54a
@@ -24,6 +24,16 @@ public class ForceOriginalAudioPatch {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean ignoreDefaultAudioStream(boolean original) {
|
||||
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||
return false;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@@ -50,7 +60,6 @@ public class ForceOriginalAudioPatch {
|
||||
return isOriginal;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||
|
||||
return isDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package app.revanced.patches.youtube.video.audio
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val streamingModelBuilderFingerprint = fingerprint {
|
||||
internal val formatStreamModelToStringFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
strings("vprng")
|
||||
returns("Ljava/lang/String;")
|
||||
custom { method, classDef ->
|
||||
method.name == "toString" && classDef.type ==
|
||||
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val menuItemAudioTrackFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
parameters("L")
|
||||
returns("V")
|
||||
strings("menu_item_audio_track")
|
||||
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
|
||||
|
||||
internal val selectAudioStreamFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
internal val audioStreamingTypeSelector = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
strings("raw") // String is not unique
|
||||
}
|
||||
@@ -5,22 +5,22 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
||||
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
|
||||
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.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.util.getReference
|
||||
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.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
@@ -37,6 +37,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
versionCheckPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
@@ -60,29 +61,25 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
)
|
||||
)
|
||||
|
||||
fun Method.firstFormatStreamingModelCall(
|
||||
returnType: String = "Ljava/lang/String;"
|
||||
): MutableMethod {
|
||||
val audioTrackIdIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
||||
&& reference.returnType == returnType
|
||||
}
|
||||
|
||||
return navigate(this).to(audioTrackIdIndex).stop()
|
||||
// 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"
|
||||
)
|
||||
}
|
||||
|
||||
// Accessor methods of FormatStreamModel have no string constants and
|
||||
// opcodes are identical to other methods in the same class,
|
||||
// so must walk from another class that use the methods.
|
||||
val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z")
|
||||
val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall()
|
||||
val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall()
|
||||
val formatStreamModelClass = proxy(classes.first {
|
||||
it.type == audioTrackIdMethod.definingClass
|
||||
}).mutableClass
|
||||
val isDefaultAudioTrackMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||
.findMethodFromToString("isDefaultAudioTrack=")
|
||||
val audioTrackDisplayNameMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||
.findMethodFromToString("audioTrackDisplayName=")
|
||||
val audioTrackIdMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||
.findMethodFromToString("audioTrackId=")
|
||||
|
||||
formatStreamModelClass.apply {
|
||||
proxy(classes.first {
|
||||
it.type == audioTrackIdMethod.definingClass
|
||||
}).mutableClass.apply {
|
||||
// Add a new field to store the override.
|
||||
val helperFieldName = "isDefaultAudioTrackOverride"
|
||||
fields.add(
|
||||
@@ -103,7 +100,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
|
||||
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
|
||||
val helperMethodClass = type
|
||||
val helperMethodName = "extension_isDefaultAudioTrack"
|
||||
val helperMethodName = "patch_isDefaultAudioTrack"
|
||||
val helperMethod = ImmutableMethod(
|
||||
helperMethodClass,
|
||||
helperMethodName,
|
||||
@@ -143,7 +140,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
||||
methods.add(helperMethod)
|
||||
|
||||
// Modify isDefaultAudioTrack() to call extension helper method.
|
||||
isDefaultMethod.apply {
|
||||
isDefaultAudioTrackMethod.apply {
|
||||
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstructio
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.EnumSet
|
||||
@@ -171,6 +174,79 @@ internal val Instruction.isBranchInstruction: Boolean
|
||||
internal val Instruction.isReturnInstruction: Boolean
|
||||
get() = this.opcode in returnOpcodes
|
||||
|
||||
/**
|
||||
* Find the instruction index used for a toString() StringBuilder write of a given String name.
|
||||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
|
||||
val stringIndex = indexOfFirstInstruction {
|
||||
val reference = getReference<StringReference>()
|
||||
reference?.string?.contains(fieldName) == true
|
||||
}
|
||||
if (stringIndex < 0) {
|
||||
throw IllegalArgumentException("Could not find usage of string: '$fieldName'")
|
||||
}
|
||||
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
|
||||
|
||||
// Find use of the string with a StringBuilder.
|
||||
val stringUsageIndex = indexOfFirstInstruction(stringIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Ljava/lang/StringBuilder;" &&
|
||||
(this as? FiveRegisterInstruction)?.registerD == stringRegister
|
||||
}
|
||||
if (stringUsageIndex < 0) {
|
||||
throw IllegalArgumentException("Could not find StringBuilder usage in: $this")
|
||||
}
|
||||
|
||||
// Find the next usage of StringBuilder, which should be the desired field.
|
||||
val fieldUsageIndex = indexOfFirstInstruction(stringUsageIndex + 1) {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Ljava/lang/StringBuilder;" && reference.name == "append"
|
||||
}
|
||||
if (fieldUsageIndex < 0) {
|
||||
// Should never happen.
|
||||
throw IllegalArgumentException("Could not find StringBuilder append usage in: $this")
|
||||
}
|
||||
val fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldUsageIndex).registerD
|
||||
|
||||
// Look backwards up the method to find the instruction that sets the register.
|
||||
var fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldUsageIndex - 1) {
|
||||
fieldUsageRegister == writeRegister
|
||||
}
|
||||
|
||||
// If the field is a method call, then adjust from MOVE_RESULT to the method call.
|
||||
val fieldSetOpcode = getInstruction(fieldSetIndex).opcode
|
||||
if (fieldSetOpcode == MOVE_RESULT ||
|
||||
fieldSetOpcode == MOVE_RESULT_WIDE ||
|
||||
fieldSetOpcode == MOVE_RESULT_OBJECT) {
|
||||
fieldSetIndex--
|
||||
}
|
||||
|
||||
return fieldSetIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the method used for a toString() StringBuilder write of a given String name.
|
||||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||
return navigate(this).to(methodUsageIndex).stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the field used for a toString() StringBuilder write of a given String name.
|
||||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference {
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds public [AccessFlags] and removes private and protected flags (if present).
|
||||
*/
|
||||
@@ -594,7 +670,7 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
* starting from and [startIndex] and searching down.
|
||||
* starting from [startIndex] and searching down.
|
||||
*
|
||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||
* @return The index of the instruction.
|
||||
@@ -617,7 +693,7 @@ fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int = i
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
* starting from and [startIndex] and searching down.
|
||||
* starting from [startIndex] and searching down.
|
||||
*
|
||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||
* @return The index of the instruction.
|
||||
|
||||
Reference in New Issue
Block a user