diff --git a/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySetterFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySetterFingerprint.kt new file mode 100644 index 000000000..b7caf0773 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySetterFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.music.misc.quality.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object MusicVideoQualitySetterFingerprint : MethodFingerprint( + returnType = "V", + opcodes = listOf( + Opcode.CHECK_CAST, + Opcode.IPUT_OBJECT + ), + customFingerprint = { it.name == "" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySetterParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySetterParentFingerprint.kt new file mode 100644 index 000000000..73663213e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySetterParentFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.music.misc.quality.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.music.misc.resourceid.patch.SharedResourceIdPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object MusicVideoQualitySetterParentFingerprint : MethodFingerprint( + returnType = "V", + parameters = listOf("L"), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { + it.opcode.ordinal == Opcode.CONST.ordinal && + (it as? WideLiteralInstruction)?.wideLiteral == SharedResourceIdPatch.qualityAutoLabelId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySettingsFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySettingsFingerprint.kt new file mode 100644 index 000000000..68d2e8bbb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySettingsFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.music.misc.quality.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +object MusicVideoQualitySettingsFingerprint : MethodFingerprint( + returnType = "V", + access = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("[L", "I", "Z"), + opcodes = listOf( + Opcode.IPUT_OBJECT, + Opcode.IPUT + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySettingsParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySettingsParentFingerprint.kt new file mode 100644 index 000000000..8dd0a771d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/MusicVideoQualitySettingsParentFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.music.misc.quality.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.music.misc.resourceid.patch.SharedResourceIdPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object MusicVideoQualitySettingsParentFingerprint : MethodFingerprint( + returnType = "L", + parameters = listOf(), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { + it.opcode.ordinal == Opcode.CONST.ordinal && + (it as? WideLiteralInstruction)?.wideLiteral == SharedResourceIdPatch.qualityTitleLabelId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/UserQualityChangeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/UserQualityChangeFingerprint.kt new file mode 100644 index 000000000..885a289e6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/quality/fingerprints/UserQualityChangeFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.music.misc.quality.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object UserQualityChangeFingerprint : MethodFingerprint( + returnType = "V", + opcodes = listOf( + Opcode.CONST_STRING, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IF_EQZ, + Opcode.CHECK_CAST + ), + strings = listOf("VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/quality/patch/VideoQualityPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/quality/patch/VideoQualityPatch.kt new file mode 100644 index 000000000..47e214e52 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/quality/patch/VideoQualityPatch.kt @@ -0,0 +1,107 @@ +package app.revanced.patches.music.misc.quality.patch + +import app.revanced.extensions.findMutableMethodOf +import app.revanced.extensions.toErrorResult +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.misc.quality.fingerprints.* +import app.revanced.patches.music.misc.resourceid.patch.SharedResourceIdPatch +import app.revanced.patches.music.misc.settings.resource.patch.MusicSettingsPatch +import app.revanced.patches.music.misc.videoid.patch.MusicVideoIdPatch +import app.revanced.patches.shared.annotation.YouTubeMusicCompatibility +import app.revanced.util.enum.CategoryType +import app.revanced.util.integrations.Constants.MUSIC_MISC_PATH +import org.jf.dexlib2.builder.instruction.BuilderInstruction21c +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.FieldReference + +@Patch +@Name("remember-video-quality") +@DependsOn( + [ + MusicSettingsPatch::class, + MusicVideoIdPatch::class, + SharedResourceIdPatch::class + ] +) +@YouTubeMusicCompatibility +@Version("0.0.1") +class VideoQualityPatch : BytecodePatch( + listOf( + MusicVideoQualitySetterParentFingerprint, + MusicVideoQualitySettingsParentFingerprint, + UserQualityChangeFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + + MusicVideoQualitySetterParentFingerprint.result?.let { parentResult -> + MusicVideoQualitySetterFingerprint.also { it.resolve(context, parentResult.classDef) }.result?.let { result -> + val endIndex = result.scanResult.patternScanResult!!.endIndex + val instructions = result.method.implementation!!.instructions + + qualityFieldReference = + (instructions.elementAt(endIndex) as ReferenceInstruction).reference as FieldReference + + qIndexMethodName = + context.classes.single { it.type == qualityFieldReference.type }.methods.single { it.parameterTypes.first() == "I" }.name + } ?: return MusicVideoQualitySetterFingerprint.toErrorResult() + } ?: return MusicVideoQualitySetterParentFingerprint.toErrorResult() + + MusicVideoQualitySettingsParentFingerprint.result?.let { parentResult -> + MusicVideoQualitySettingsFingerprint.also { it.resolve(context, parentResult.classDef) }.result?.let { + it.mutableMethod.addInstructions( + 0, """ + const-string v0, "$qIndexMethodName" + sput-object v0, $INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->qIndexMethod:Ljava/lang/String; + iget-object v0, p0, ${it.classDef.type}->${qualityFieldReference.name}:${qualityFieldReference.type} + invoke-static {p1, p2, v0}, $INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->setVideoQuality([Ljava/lang/Object;ILjava/lang/Object;)I + move-result p2 + """ + ) + } ?: return MusicVideoQualitySettingsFingerprint.toErrorResult() + } ?: return MusicVideoQualitySettingsParentFingerprint.toErrorResult() + + UserQualityChangeFingerprint.result?.let { + val endIndex = it.scanResult.patternScanResult!!.endIndex + val qualityChangedClass = + context.findClass((it.mutableMethod.instruction(endIndex) as BuilderInstruction21c) + .reference.toString())!! + .mutableClass + + for (method in qualityChangedClass.methods) { + with (qualityChangedClass.findMutableMethodOf(method)) { + if (this.name == "onItemClick") { + addInstruction( + 0, + "invoke-static {p3}, $INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->userChangedQuality(I)V" + ) + } + } + } + } ?: return UserQualityChangeFingerprint.toErrorResult() + + MusicVideoIdPatch.injectCall("$INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;)V") + MusicSettingsPatch.addMusicPreference(CategoryType.MISC, "revanced_enable_save_video_quality", "true") + + return PatchResultSuccess() + } + private companion object { + const val INTEGRATIONS_VIDEO_QUALITY_CLASS_DESCRIPTOR = + "$MUSIC_MISC_PATH/MusicVideoQualityPatch;" + + private lateinit var qIndexMethodName: String + private lateinit var qualityFieldReference: FieldReference + + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/misc/resourceid/patch/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/resourceid/patch/SharedResourceIdPatch.kt index 43473e753..bfe2672e4 100644 --- a/src/main/kotlin/app/revanced/patches/music/misc/resourceid/patch/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/misc/resourceid/patch/SharedResourceIdPatch.kt @@ -23,6 +23,8 @@ class SharedResourceIdPatch : ResourcePatch { var disabledIconLabelId: Long = -1 var isTabletLabelId: Long = -1 var notifierShelfLabelId: Long = -1 + var qualityAutoLabelId: Long = -1 + var qualityTitleLabelId: Long = -1 } override fun execute(context: ResourceContext): PatchResult { @@ -36,6 +38,8 @@ class SharedResourceIdPatch : ResourcePatch { disabledIconLabelId = find(DIMEN, "disabled_icon_alpha") isTabletLabelId = find(BOOL, "is_tablet") notifierShelfLabelId = find(LAYOUT, "music_notifier_shelf") + qualityAutoLabelId = find(STRING, "quality_auto") + qualityTitleLabelId = find(STRING, "quality_title") return PatchResultSuccess() }