Skip to content

Commit 1dd01cf

Browse files
author
LisoUseInAIKyrios
authored
fix(YouTube - Force original audio): Disable a/b feature flag that forces localized audio (#5582)
1 parent 8c31374 commit 1dd01cf

File tree

4 files changed

+128
-42
lines changed

4 files changed

+128
-42
lines changed

extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ForceOriginalAudioPatch.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ public boolean isAvailable() {
2424
}
2525
}
2626

27+
/**
28+
* Injection point.
29+
*/
30+
public static boolean ignoreDefaultAudioStream(boolean original) {
31+
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
32+
return false;
33+
}
34+
return original;
35+
}
36+
2737
/**
2838
* Injection point.
2939
*/
@@ -50,7 +60,6 @@ public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackI
5060
return isOriginal;
5161
} catch (Exception ex) {
5262
Logger.printException(() -> "isDefaultAudioStream failure", ex);
53-
5463
return isDefault;
5564
}
5665
}
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
package app.revanced.patches.youtube.video.audio
22

33
import app.revanced.patcher.fingerprint
4+
import app.revanced.util.containsLiteralInstruction
45
import com.android.tools.smali.dexlib2.AccessFlags
56

6-
internal val streamingModelBuilderFingerprint = fingerprint {
7+
internal val formatStreamModelToStringFingerprint = fingerprint {
78
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
8-
returns("L")
9-
strings("vprng")
9+
returns("Ljava/lang/String;")
10+
custom { method, classDef ->
11+
method.name == "toString" && classDef.type ==
12+
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
13+
}
1014
}
1115

12-
internal val menuItemAudioTrackFingerprint = fingerprint {
13-
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
14-
parameters("L")
15-
returns("V")
16-
strings("menu_item_audio_track")
17-
}
16+
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
1817

19-
internal val audioStreamingTypeSelector = fingerprint {
20-
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
18+
internal val selectAudioStreamFingerprint = fingerprint {
19+
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
2120
returns("L")
22-
strings("raw") // String is not unique
23-
}
21+
custom { method, _ ->
22+
method.parameters.size > 2 // Method has a large number of parameters and may change.
23+
&& method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
24+
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
25+
}
26+
}
27+

patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
55
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
66
import app.revanced.patcher.patch.bytecodePatch
77
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
8-
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
98
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
109
import app.revanced.patches.all.misc.resources.addResources
1110
import app.revanced.patches.all.misc.resources.addResourcesPatch
1211
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
1312
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
13+
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
14+
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
1415
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
1516
import app.revanced.patches.youtube.misc.settings.settingsPatch
16-
import app.revanced.util.getReference
17+
import app.revanced.util.findMethodFromToString
1718
import app.revanced.util.indexOfFirstInstructionOrThrow
19+
import app.revanced.util.insertLiteralOverride
1820
import com.android.tools.smali.dexlib2.AccessFlags
1921
import com.android.tools.smali.dexlib2.Opcode
2022
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
21-
import com.android.tools.smali.dexlib2.iface.Method
2223
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
23-
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
2424
import com.android.tools.smali.dexlib2.immutable.ImmutableField
2525
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
2626
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@@ -37,6 +37,7 @@ val forceOriginalAudioPatch = bytecodePatch(
3737
sharedExtensionPatch,
3838
settingsPatch,
3939
addResourcesPatch,
40+
versionCheckPatch
4041
)
4142

4243
compatibleWith(
@@ -60,29 +61,25 @@ val forceOriginalAudioPatch = bytecodePatch(
6061
)
6162
)
6263

63-
fun Method.firstFormatStreamingModelCall(
64-
returnType: String = "Ljava/lang/String;"
65-
): MutableMethod {
66-
val audioTrackIdIndex = indexOfFirstInstructionOrThrow {
67-
val reference = getReference<MethodReference>()
68-
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
69-
&& reference.returnType == returnType
70-
}
71-
72-
return navigate(this).to(audioTrackIdIndex).stop()
64+
// Disable feature flag that ignores the default track flag
65+
// and instead overrides to the user region language.
66+
if (is_20_07_or_greater) {
67+
selectAudioStreamFingerprint.method.insertLiteralOverride(
68+
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
69+
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
70+
)
7371
}
7472

75-
// Accessor methods of FormatStreamModel have no string constants and
76-
// opcodes are identical to other methods in the same class,
77-
// so must walk from another class that use the methods.
78-
val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z")
79-
val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall()
80-
val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall()
81-
val formatStreamModelClass = proxy(classes.first {
82-
it.type == audioTrackIdMethod.definingClass
83-
}).mutableClass
73+
val isDefaultAudioTrackMethod = formatStreamModelToStringFingerprint.originalMethod
74+
.findMethodFromToString("isDefaultAudioTrack=")
75+
val audioTrackDisplayNameMethod = formatStreamModelToStringFingerprint.originalMethod
76+
.findMethodFromToString("audioTrackDisplayName=")
77+
val audioTrackIdMethod = formatStreamModelToStringFingerprint.originalMethod
78+
.findMethodFromToString("audioTrackId=")
8479

85-
formatStreamModelClass.apply {
80+
proxy(classes.first {
81+
it.type == audioTrackIdMethod.definingClass
82+
}).mutableClass.apply {
8683
// Add a new field to store the override.
8784
val helperFieldName = "isDefaultAudioTrackOverride"
8885
fields.add(
@@ -103,7 +100,7 @@ val forceOriginalAudioPatch = bytecodePatch(
103100

104101
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
105102
val helperMethodClass = type
106-
val helperMethodName = "extension_isDefaultAudioTrack"
103+
val helperMethodName = "patch_isDefaultAudioTrack"
107104
val helperMethod = ImmutableMethod(
108105
helperMethodClass,
109106
helperMethodName,
@@ -143,7 +140,7 @@ val forceOriginalAudioPatch = bytecodePatch(
143140
methods.add(helperMethod)
144141

145142
// Modify isDefaultAudioTrack() to call extension helper method.
146-
isDefaultMethod.apply {
143+
isDefaultAudioTrackMethod.apply {
147144
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
148145
val register = getInstruction<OneRegisterInstruction>(index).registerA
149146

patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstructio
3232
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
3333
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
3434
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
35+
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
36+
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
3537
import com.android.tools.smali.dexlib2.iface.reference.Reference
38+
import com.android.tools.smali.dexlib2.iface.reference.StringReference
3639
import com.android.tools.smali.dexlib2.immutable.ImmutableField
3740
import com.android.tools.smali.dexlib2.util.MethodUtil
3841
import java.util.EnumSet
@@ -171,6 +174,79 @@ internal val Instruction.isBranchInstruction: Boolean
171174
internal val Instruction.isReturnInstruction: Boolean
172175
get() = this.opcode in returnOpcodes
173176

177+
/**
178+
* Find the instruction index used for a toString() StringBuilder write of a given String name.
179+
*
180+
* @param fieldName The name of the field to find. Partial matches are allowed.
181+
*/
182+
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
183+
val stringIndex = indexOfFirstInstruction {
184+
val reference = getReference<StringReference>()
185+
reference?.string?.contains(fieldName) == true
186+
}
187+
if (stringIndex < 0) {
188+
throw IllegalArgumentException("Could not find usage of string: '$fieldName'")
189+
}
190+
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
191+
192+
// Find use of the string with a StringBuilder.
193+
val stringUsageIndex = indexOfFirstInstruction(stringIndex) {
194+
val reference = getReference<MethodReference>()
195+
reference?.definingClass == "Ljava/lang/StringBuilder;" &&
196+
(this as? FiveRegisterInstruction)?.registerD == stringRegister
197+
}
198+
if (stringUsageIndex < 0) {
199+
throw IllegalArgumentException("Could not find StringBuilder usage in: $this")
200+
}
201+
202+
// Find the next usage of StringBuilder, which should be the desired field.
203+
val fieldUsageIndex = indexOfFirstInstruction(stringUsageIndex + 1) {
204+
val reference = getReference<MethodReference>()
205+
reference?.definingClass == "Ljava/lang/StringBuilder;" && reference.name == "append"
206+
}
207+
if (fieldUsageIndex < 0) {
208+
// Should never happen.
209+
throw IllegalArgumentException("Could not find StringBuilder append usage in: $this")
210+
}
211+
val fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldUsageIndex).registerD
212+
213+
// Look backwards up the method to find the instruction that sets the register.
214+
var fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldUsageIndex - 1) {
215+
fieldUsageRegister == writeRegister
216+
}
217+
218+
// If the field is a method call, then adjust from MOVE_RESULT to the method call.
219+
val fieldSetOpcode = getInstruction(fieldSetIndex).opcode
220+
if (fieldSetOpcode == MOVE_RESULT ||
221+
fieldSetOpcode == MOVE_RESULT_WIDE ||
222+
fieldSetOpcode == MOVE_RESULT_OBJECT) {
223+
fieldSetIndex--
224+
}
225+
226+
return fieldSetIndex
227+
}
228+
229+
/**
230+
* Find the method used for a toString() StringBuilder write of a given String name.
231+
*
232+
* @param fieldName The name of the field to find. Partial matches are allowed.
233+
*/
234+
context(BytecodePatchContext)
235+
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
236+
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
237+
return navigate(this).to(methodUsageIndex).stop()
238+
}
239+
240+
/**
241+
* Find the field used for a toString() StringBuilder write of a given String name.
242+
*
243+
* @param fieldName The name of the field to find. Partial matches are allowed.
244+
*/
245+
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference {
246+
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
247+
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
248+
}
249+
174250
/**
175251
* Adds public [AccessFlags] and removes private and protected flags (if present).
176252
*/
@@ -594,7 +670,7 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
594670

595671
/**
596672
* Get the index of matching instruction,
597-
* starting from and [startIndex] and searching down.
673+
* starting from [startIndex] and searching down.
598674
*
599675
* @param startIndex Optional starting index to search down from. Searching includes the start index.
600676
* @return The index of the instruction.
@@ -617,7 +693,7 @@ fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int = i
617693

618694
/**
619695
* Get the index of matching instruction,
620-
* starting from and [startIndex] and searching down.
696+
* starting from [startIndex] and searching down.
621697
*
622698
* @param startIndex Optional starting index to search down from. Searching includes the start index.
623699
* @return The index of the instruction.

0 commit comments

Comments
 (0)