fix(youtube/video-ads): add switch to temporarily fix buffering issues

This commit is contained in:
oSumAtrIX 2022-11-01 07:10:54 +01:00
parent deeef4e96d
commit 7aa3bce6cc
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
11 changed files with 248 additions and 97 deletions

View File

@ -15,12 +15,13 @@ import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.ad.video.annotations.VideoAdsCompatibility import app.revanced.patches.youtube.ad.video.annotations.VideoAdsCompatibility
import app.revanced.patches.youtube.ad.video.fingerprints.LoadVideoAdsFingerprint import app.revanced.patches.youtube.ad.video.fingerprints.LoadVideoAdsFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.playback.patch.FixPlaybackPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch @Patch
@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) @DependsOn([IntegrationsPatch::class, SettingsPatch::class, FixPlaybackPatch::class])
@Name("video-ads") @Name("video-ads")
@Description("Removes ads in the video player.") @Description("Removes ads in the video player.")
@VideoAdsCompatibility @VideoAdsCompatibility

View File

@ -7,40 +7,41 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.data.toMethodWalker import app.revanced.patcher.data.toMethodWalker
import app.revanced.patcher.extensions.addInstruction import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.or
import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.youtube.layout.autocaptions.fingerprints.StartVideoInformerFingerprint import app.revanced.patches.youtube.layout.autocaptions.fingerprints.StartVideoInformerFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints.* import app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints.*
import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.mapping.patch.ResourceMappingResourcePatch import app.revanced.patches.youtube.misc.mapping.patch.ResourceMappingResourcePatch
import app.revanced.patches.youtube.misc.playercontroller.patch.PlayerControllerPatch
import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch
import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.iface.instruction.* import org.jf.dexlib2.iface.instruction.*
import org.jf.dexlib2.iface.instruction.formats.Instruction35c import org.jf.dexlib2.iface.instruction.formats.Instruction35c
import org.jf.dexlib2.iface.reference.FieldReference import org.jf.dexlib2.iface.reference.FieldReference
import org.jf.dexlib2.iface.reference.MethodReference import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.iface.reference.StringReference import org.jf.dexlib2.iface.reference.StringReference
import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodParameter
import org.jf.dexlib2.util.MethodUtil
@Patch @Patch
@DependsOn( @DependsOn(
dependencies = [PlayerControlsBytecodePatch::class, IntegrationsPatch::class, SponsorBlockResourcePatch::class, VideoIdPatch::class] dependencies = [
PlayerControllerPatch::class, // updates video length and adds method to seek in video
PlayerControlsBytecodePatch::class,
IntegrationsPatch::class,
SponsorBlockResourcePatch::class,
VideoIdPatch::class
]
) )
@Name("sponsorblock") @Name("sponsorblock")
@Description("Integrate SponsorBlock.") @Description("Integrate SponsorBlock.")
@ -53,7 +54,6 @@ class SponsorBlockBytecodePatch : BytecodePatch(
VideoTimeFingerprint, VideoTimeFingerprint,
NextGenWatchLayoutFingerprint, NextGenWatchLayoutFingerprint,
AppendTimeFingerprint, AppendTimeFingerprint,
PlayerInitFingerprint,
PlayerOverlaysLayoutInitFingerprint, PlayerOverlaysLayoutInitFingerprint,
ShortsPlayerConstructorFingerprint, ShortsPlayerConstructorFingerprint,
StartVideoInformerFingerprint StartVideoInformerFingerprint
@ -160,23 +160,6 @@ class SponsorBlockBytecodePatch : BytecodePatch(
"invoke-static {v$canvasInstance, v$centerY}, Lapp/revanced/integrations/sponsorblock/PlayerController;->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V" "invoke-static {v$canvasInstance, v$centerY}, Lapp/revanced/integrations/sponsorblock/PlayerController;->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V"
) )
/*
Set video length
*/
VideoLengthFingerprint.resolve(context, seekbarSignatureResult.classDef)
val videoLengthMethodResult = VideoLengthFingerprint.result!!
val videoLengthMethod = videoLengthMethodResult.mutableMethod
val videoLengthMethodInstructions = videoLengthMethod.implementation!!.instructions
val videoLengthRegister =
(videoLengthMethodInstructions[videoLengthMethodResult.scanResult.patternScanResult!!.endIndex - 2] as OneRegisterInstruction).registerA
val dummyRegisterForLong =
videoLengthRegister + 1 // this is required for long values since they are 64 bit wide
videoLengthMethod.addInstruction(
videoLengthMethodResult.scanResult.patternScanResult!!.endIndex,
"invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setVideoLength(J)V"
)
/* /*
Voting & Shield button Voting & Shield button
*/ */
@ -249,12 +232,7 @@ class SponsorBlockBytecodePatch : BytecodePatch(
) )
// initialize the player controller // initialize the player controller
val initFingerprintResult = PlayerInitFingerprint.result!! PlayerControllerPatch.onCreateHook("Lapp/revanced/integrations/sponsorblock/PlayerController;", "initialize")
val initInstanceRegister = 0
initFingerprintResult.mutableClass.methods.first { MethodUtil.isConstructor(it) }.addInstruction(
4, // after super class invoke
"invoke-static {v$initInstanceRegister}, Lapp/revanced/integrations/sponsorblock/PlayerController;->onCreate(Ljava/lang/Object;)V"
)
// initialize the sponsorblock view // initialize the sponsorblock view
PlayerOverlaysLayoutInitFingerprint.result!!.mutableMethod.addInstruction( PlayerOverlaysLayoutInitFingerprint.result!!.mutableMethod.addInstruction(
@ -262,39 +240,6 @@ class SponsorBlockBytecodePatch : BytecodePatch(
"invoke-static {p0}, Lapp/revanced/integrations/sponsorblock/player/ui/SponsorBlockView;->initialize(Ljava/lang/Object;)V" "invoke-static {p0}, Lapp/revanced/integrations/sponsorblock/player/ui/SponsorBlockView;->initialize(Ljava/lang/Object;)V"
) )
// lastly create hooks for the player controller
// get original seek method
SeekFingerprint.resolve(context, initFingerprintResult.classDef)
val seekFingerprintResultMethod = SeekFingerprint.result!!.method
// get enum type for the seek helper method
val seekSourceEnumType = seekFingerprintResultMethod.parameterTypes[1].toString()
// create helper method
val seekHelperMethod = ImmutableMethod(
seekFingerprintResultMethod.definingClass,
"seekHelper",
listOf(ImmutableMethodParameter("J", null, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null, null,
MutableMethodImplementation(4)
).toMutable()
// insert helper method instructions
seekHelperMethod.addInstructions(
0,
"""
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, ${seekFingerprintResultMethod.definingClass}->${seekFingerprintResultMethod.name}(J$seekSourceEnumType)Z
move-result p1
return p1
"""
)
// add the helper method to the original class
initFingerprintResult.mutableClass.methods.add(seekHelperMethod)
// get rectangle field name // get rectangle field name
RectangleFieldInvalidatorFingerprint.resolve(context, seekbarSignatureResult.classDef) RectangleFieldInvalidatorFingerprint.resolve(context, seekbarSignatureResult.classDef)
val rectangleFieldInvalidatorInstructions = val rectangleFieldInvalidatorInstructions =
@ -302,35 +247,31 @@ class SponsorBlockBytecodePatch : BytecodePatch(
val rectangleFieldName = val rectangleFieldName =
((rectangleFieldInvalidatorInstructions.elementAt(rectangleFieldInvalidatorInstructions.count() - 3) as ReferenceInstruction).reference as FieldReference).name ((rectangleFieldInvalidatorInstructions.elementAt(rectangleFieldInvalidatorInstructions.count() - 3) as ReferenceInstruction).reference as FieldReference).name
// get the player controller class from the integrations
val playerControllerMethods =
context.proxy(context.classes.first { it.type.endsWith("PlayerController;") }).mutableClass.methods
// get the method which contain the "replaceMe" strings
val replaceMeMethods =
playerControllerMethods.filter { it.name == "onCreate" || it.name == "setSponsorBarRect" }
fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) {
val register = (instruction as OneRegisterInstruction).registerA
this.replaceInstruction(
index, "const-string v$register, \"$with\""
)
}
// replace the "replaceMeWith*" strings // replace the "replaceMeWith*" strings
for (method in replaceMeMethods) { context
for ((index, it) in method.implementation!!.instructions.withIndex()) { .proxy(context.classes.first { it.type.endsWith("PlayerController;") })
if (it.opcode.ordinal != Opcode.CONST_STRING.ordinal) continue .mutableClass
.methods
when (((it as ReferenceInstruction).reference as StringReference).string) { .find { it.name == "setSponsorBarRect" }
"replaceMeWithsetSponsorBarRect" -> ?.let { method ->
method.replaceStringInstruction(index, it, rectangleFieldName) fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) {
val register = (instruction as OneRegisterInstruction).registerA
"replaceMeWithsetMillisecondMethod" -> this.replaceInstruction(
method.replaceStringInstruction(index, it, "seekHelper") index, "const-string v$register, \"$with\""
)
} }
} for ((index, it) in method.implementation!!.instructions.withIndex()) {
} if (it.opcode.ordinal != Opcode.CONST_STRING.ordinal) continue
when (((it as ReferenceInstruction).reference as StringReference).string) {
"replaceMeWithsetSponsorBarRect" ->
method.replaceStringInstruction(index, it, rectangleFieldName)
"replaceMeWithsetMillisecondMethod" ->
method.replaceStringInstruction(index, it, "seekHelper")
}
}
} ?: return PatchResultError("Could not find the method which contains the replaceMeWith* strings")
val startVideoInformerMethod = StartVideoInformerFingerprint.result!!.mutableMethod val startVideoInformerMethod = StartVideoInformerFingerprint.result!!.mutableMethod
startVideoInformerMethod.addInstructions( startVideoInformerMethod.addInstructions(

View File

@ -0,0 +1,13 @@
package app.revanced.patches.youtube.misc.playback.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.google.android.youtube", arrayOf("17.41.37")
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class FixPlaybackCompatibility

View File

@ -0,0 +1,52 @@
package app.revanced.patches.youtube.misc.playback.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.playback.annotations.FixPlaybackCompatibility
import app.revanced.patches.youtube.misc.playercontroller.patch.PlayerControllerPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch
@DependsOn([
IntegrationsPatch::class,
PlayerControllerPatch::class, // updates video length and adds method to seek in video, necessary for this patch
SettingsPatch::class,
VideoIdPatch::class
])
@Name("fix-playback")
@Description("Fixes the issue with videos not playing when video ads are removed.")
@FixPlaybackCompatibility
@Version("0.0.1")
class FixPlaybackPatch : ResourcePatch {
override fun execute(context: ResourceContext): PatchResult {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
SwitchPreference(
"revanced_fix_playback",
StringResource("revanced_fix_playback_title", "Fix video playback issues"),
false,
StringResource(
"revanced_fix_playback_summary_on",
"The fix is enabled"
),
StringResource(
"revanced_fix_playback_summary_off",
"The fix is disabled"
)
)
)
// If a new video loads, fix the playback issue
VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/FixPlaybackPatch;->newVideoLoaded(Ljava/lang/String;)V")
return PatchResultSuccess()
}
}

View File

@ -0,0 +1,13 @@
package app.revanced.patches.youtube.misc.playercontroller.annotation
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.google.android.youtube", arrayOf("17.41.37")
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class PlayerControllerCompatibility

View File

@ -0,0 +1,15 @@
package app.revanced.patches.youtube.misc.playercontroller.fingerprint
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("create-video-player-seekbar-fingerprint")
@SponsorBlockCompatibility
@Version("0.0.1")
object CreateVideoPlayerSeekbarFingerprint : MethodFingerprint(
"V",
strings = listOf("timed_markers_width")
)

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints package app.revanced.patches.youtube.misc.playercontroller.fingerprint
import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version import app.revanced.patcher.annotation.Version

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints package app.revanced.patches.youtube.misc.playercontroller.fingerprint
import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version import app.revanced.patcher.annotation.Version

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints package app.revanced.patches.youtube.misc.playercontroller.fingerprint
import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version import app.revanced.patcher.annotation.Version

View File

@ -0,0 +1,116 @@
package app.revanced.patches.youtube.misc.playercontroller.patch
import app.revanced.patcher.annotation.Description
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.extensions.or
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.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.playercontroller.annotation.PlayerControllerCompatibility
import app.revanced.patches.youtube.misc.playercontroller.fingerprint.CreateVideoPlayerSeekbarFingerprint
import app.revanced.patches.youtube.misc.playercontroller.fingerprint.PlayerInitFingerprint
import app.revanced.patches.youtube.misc.playercontroller.fingerprint.SeekFingerprint
import app.revanced.patches.youtube.misc.playercontroller.fingerprint.VideoLengthFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodParameter
import org.jf.dexlib2.util.MethodUtil
@Name("player-controller-hook")
@Description("Hooks the player controller")
@PlayerControllerCompatibility
@Version("0.0.1")
@DependsOn([IntegrationsPatch::class])
class PlayerControllerPatch : BytecodePatch(
listOf(
PlayerInitFingerprint,
CreateVideoPlayerSeekbarFingerprint
)
) {
override fun execute(context: BytecodeContext): PatchResult {
with(PlayerInitFingerprint.result!!) {
playerInitMethod = mutableClass.methods.first { MethodUtil.isConstructor(it) }
// hook the player controller for use through integrations
onCreateHook(INTEGRATIONS_CLASS_DESCRIPTOR, "playerController_onCreateHook")
// seek method
val seekFingerprintResultMethod = SeekFingerprint.also { it.resolve(context, classDef) }.result!!.method
// create helper method
val seekHelperMethod = ImmutableMethod(
seekFingerprintResultMethod.definingClass,
"seekTo",
listOf(ImmutableMethodParameter("J", null, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null, null,
MutableMethodImplementation(4)
).toMutable()
// get enum type for the seek helper method
val seekSourceEnumType = seekFingerprintResultMethod.parameterTypes[1].toString()
// insert helper method instructions
seekHelperMethod.addInstructions(
0,
"""
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, ${seekFingerprintResultMethod.definingClass}->${seekFingerprintResultMethod.name}(J$seekSourceEnumType)Z
move-result p1
return p1
"""
)
// add the seekTo method to the class for the integrations to call
mutableClass.methods.add(seekHelperMethod)
}
with(CreateVideoPlayerSeekbarFingerprint.result!!) {
val videoLengthMethodResult = VideoLengthFingerprint.also { it.resolve(context, classDef) }.result!!
with(videoLengthMethodResult.mutableMethod) {
val videoLengthRegisterIndex = videoLengthMethodResult.scanResult.patternScanResult!!.endIndex - 2
val videoLengthRegister = (instruction(videoLengthRegisterIndex) as OneRegisterInstruction).registerA
val dummyRegisterForLong = videoLengthRegister + 1 // required for long values since they are wide
addInstruction(
videoLengthMethodResult.scanResult.patternScanResult!!.endIndex,
"invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, $INTEGRATIONS_CLASS_DESCRIPTOR->setCurrentVideoLength(J)V"
)
}
}
return PatchResultSuccess()
}
companion object {
private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/PlayerControllerPatch;"
private const val INSERT_INDEX = 4
private lateinit var playerInitMethod: MutableMethod
/**
* Hook the player controller.
*
* @param targetMethodClass The descriptor for the static method to invoke when the player controller is created.
*/
internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) =
playerInitMethod.addInstruction(
INSERT_INDEX,
"invoke-static { v0 }, $targetMethodClass->$targetMethodName(Ljava/lang/Object;)V"
)
}
}

View File

@ -17,7 +17,7 @@ import app.revanced.patches.youtube.misc.videoid.fingerprint.VideoIdFingerprint
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
@Name("video-id-hook") @Name("video-id-hook")
@Description("Hook to detect when the video id changes") @Description("Hooks to detect when the video id changes")
@VideoIdCompatibility @VideoIdCompatibility
@Version("0.0.1") @Version("0.0.1")
@DependsOn([IntegrationsPatch::class]) @DependsOn([IntegrationsPatch::class])