fix(YouTube - Video quality): Initial video quality is not overridden
Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com>
This commit is contained in:
parent
009cf71462
commit
53318c48ee
5 changed files with 141 additions and 16 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.patches.youtube.video.quality
|
||||
|
||||
import app.revanced.patcher.CompositeMatch
|
||||
import app.revanced.patcher.accessFlags
|
||||
import app.revanced.patcher.afterAtMost
|
||||
import app.revanced.patcher.allOf
|
||||
|
|
@ -7,6 +8,7 @@ import app.revanced.patcher.composingFirstMethod
|
|||
import app.revanced.patcher.custom
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.field
|
||||
import app.revanced.patcher.firstMethodComposite
|
||||
import app.revanced.patcher.firstMethodDeclaratively
|
||||
import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively
|
||||
import app.revanced.patcher.gettingFirstMethodDeclaratively
|
||||
|
|
@ -18,6 +20,7 @@ import app.revanced.patcher.opcodes
|
|||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.returnType
|
||||
import app.revanced.util.findFieldFromToString
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
|
@ -60,6 +63,31 @@ internal val BytecodePatchContext.hidePremiumVideoQualityGetArrayMethod by getti
|
|||
custom { AccessFlags.SYNTHETIC.isSet(immutableClassDef.accessFlags) }
|
||||
}
|
||||
|
||||
internal const val FIXED_RESOLUTION_STRING = ", initialPlaybackVideoQualityFixedResolution="
|
||||
|
||||
|
||||
internal fun BytecodePatchContext.getPlaybackStartParametersConstructorMethod(): CompositeMatch {
|
||||
val playbackStartParametersToStringMethod = firstMethodDeclaratively(
|
||||
FIXED_RESOLUTION_STRING
|
||||
) {
|
||||
name("toString")
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("Ljava/lang/String;")
|
||||
parameterTypes()
|
||||
}
|
||||
|
||||
val initialResolutionField = playbackStartParametersToStringMethod
|
||||
.findFieldFromToString(FIXED_RESOLUTION_STRING)
|
||||
|
||||
// Inject a call to override initial video quality.
|
||||
return playbackStartParametersToStringMethod.immutableClassDef.firstMethodComposite {
|
||||
name("<init>")
|
||||
instructions(
|
||||
allOf(Opcode.IPUT_OBJECT(), field { this == initialResolutionField })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.videoQualityItemOnClickParentMethod by gettingFirstImmutableMethodDeclaratively(
|
||||
"VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT",
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
package app.revanced.patches.youtube.video.quality
|
||||
|
||||
import app.revanced.patcher.allOf
|
||||
import app.revanced.patcher.extensions.addInstruction
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.field
|
||||
import app.revanced.patcher.firstMethodComposite
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.instructions
|
||||
import app.revanced.patcher.invoke
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
|
|
@ -15,6 +22,8 @@ import app.revanced.patches.youtube.misc.settings.settingsPatch
|
|||
import app.revanced.patches.youtube.shared.videoQualityChangedMethodMatch
|
||||
import app.revanced.patches.youtube.video.information.onCreateHook
|
||||
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
||||
import app.revanced.util.findFieldFromToString
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
|
@ -64,11 +73,28 @@ val rememberVideoQualityPatch = bytecodePatch {
|
|||
|
||||
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted")
|
||||
|
||||
// Inject a call to override initial video quality.
|
||||
getPlaybackStartParametersConstructorMethod().let {
|
||||
it.method.apply {
|
||||
val index = it[-1]
|
||||
val register = getInstruction<TwoRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index,
|
||||
"""
|
||||
invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->getInitialVideoQuality(Lj$/util/Optional;)Lj$/util/Optional;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject a call to remember the selected quality for Shorts.
|
||||
videoQualityItemOnClickParentMethod.immutableClassDef.getVideoQualityItemOnClickMethod().addInstruction(
|
||||
0,
|
||||
"invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V",
|
||||
)
|
||||
videoQualityItemOnClickParentMethod.immutableClassDef.getVideoQualityItemOnClickMethod()
|
||||
.addInstruction(
|
||||
0,
|
||||
"invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V",
|
||||
)
|
||||
|
||||
// Inject a call to remember the user selected quality for regular videos.
|
||||
videoQualityChangedMethodMatch.let { match ->
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import kotlin.collections.remove
|
|||
*
|
||||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
private fun Method.findInstructionIndexFromToString(fieldName: String): Int {
|
||||
private fun Method.findInstructionIndexFromToString(fieldName: String, isField: Boolean) : Int {
|
||||
val stringIndex = indexOfFirstInstruction {
|
||||
val reference = getReference<StringReference>()
|
||||
reference?.string?.contains(fieldName) == true
|
||||
|
|
@ -67,22 +67,55 @@ private fun Method.findInstructionIndexFromToString(fieldName: String): Int {
|
|||
// Should never happen.
|
||||
throw IllegalArgumentException("Could not find StringBuilder append usage in: $this")
|
||||
}
|
||||
val fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldUsageIndex).registerD
|
||||
|
||||
var 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--
|
||||
// Some 'toString()' methods, despite using a StringBuilder, convert the value via
|
||||
// 'Object.toString()' or 'String.valueOf(object)' before appending it to the StringBuilder.
|
||||
// In this case, the correct index cannot be found.
|
||||
// Additional validation is done to find the index of the correct field or method.
|
||||
//
|
||||
// Check up to 3 method calls.
|
||||
var checksLeft = 3
|
||||
while (checksLeft > 0) {
|
||||
// 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--
|
||||
}
|
||||
|
||||
val fieldSetReference = getInstruction<ReferenceInstruction>(fieldSetIndex).reference
|
||||
|
||||
if (isField && fieldSetReference is FieldReference ||
|
||||
!isField && fieldSetReference is MethodReference
|
||||
) {
|
||||
// Valid index.
|
||||
return fieldSetIndex
|
||||
} else if (fieldSetReference is MethodReference &&
|
||||
// Object.toString(), String.valueOf(object)
|
||||
fieldSetReference.returnType == "Ljava/lang/String;"
|
||||
) {
|
||||
fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldSetIndex).registerC
|
||||
|
||||
// Look backwards up the method to find the instruction that sets the register.
|
||||
fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldSetIndex - 1) {
|
||||
fieldUsageRegister == writeRegister
|
||||
}
|
||||
checksLeft--
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown reference: $fieldSetReference")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return fieldSetIndex
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +126,7 @@ private fun Method.findInstructionIndexFromToString(fieldName: String): Int {
|
|||
*/
|
||||
context(context: BytecodePatchContext)
|
||||
internal fun Method.findMethodFromToString(fieldName: String): MutableMethod {
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName, false)
|
||||
return context.navigate(this).to(methodUsageIndex).stop()
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +136,7 @@ internal fun Method.findMethodFromToString(fieldName: String): MutableMethod {
|
|||
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||
*/
|
||||
internal fun Method.findFieldFromToString(fieldName: String): FieldReference {
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||
val methodUsageIndex = findInstructionIndexFromToString(fieldName, true)
|
||||
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue