From 5d072076cd746c6e76f124dfde0b57df08083fb9 Mon Sep 17 00:00:00 2001 From: Cuynu <90895715+cuynu@users.noreply.github.com> Date: Fri, 23 Dec 2022 18:40:43 +0700 Subject: [PATCH] Sync new patches --- .gitignore | 123 + .idea/.gitignore | 9 + .idea/discord.xml | 7 + .idea/git_toolbox_prj.xml | 15 + .idea/misc.xml | 8 + .idea/vcs.xml | 12 + .releaserc | 45 + CHANGELOG.md | 2751 ++++++++ LICENSE | 674 ++ build.gradle.kts | 66 + gradle.properties | 2 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 + gradlew.bat | 89 + package-lock.json | 6107 +++++++++++++++++ package.json | 8 + patches.json | 1 + settings.gradle.kts | 1 + .../app/revanced/extensions/Extensions.kt | 44 + src/main/kotlin/app/revanced/meta/Meta.kt | 27 + .../app/revanced/meta/json/Generator.kt | 48 + .../app/revanced/meta/json/JsonPatch.kt | 26 + .../app/revanced/meta/readme/Extensions.kt | 10 + .../app/revanced/meta/readme/Generator.kt | 42 + .../revanced/meta/readme/SemanticVersion.kt | 22 + .../app/revanced/meta/readme/Template.kt | 12 + .../patch/PredictiveBackGesturePatch.kt | 37 + .../pro/annotations/ProUnlockCompatibility.kt | 7 + .../pro/fingerprints/ProUnlockFingerprint.kt | 15 + .../misc/pro/patch/ProUnlockPatch.kt | 42 + .../annotations/PremiumUnlockCompatbility.kt | 7 + .../fingerprints/PremiumUnlockFingerprint.kt | 7 + .../misc/premium/patch/PremiumUnlockPatch.kt | 37 + .../annotations/HexEditorAdsCompatibility.kt | 13 + .../ad/fingerprints/PrimaryAdsFingerprint.kt | 9 + .../hexeditor/ad/patch/HexEditorAdsPatch.kt | 39 + .../pro/annotations/UnlockProCompatibility.kt | 9 + .../pro/fingerprints/CheckProFingerprint.kt | 8 + .../misc/pro/patch/UnlockProPatch.kt | 38 + .../annotations/UnlockProCompatibility.kt | 9 + .../fingerprints/UnlockProFingerprint.kt | 19 + .../moneymanager/patch/UnlockProPatch.kt | 34 + .../annotations/MusicVideoAdsCompatibility.kt | 30 + ...ShowMusicVideoAdsConstructorFingerprint.kt | 27 + .../ShowMusicVideoAdsFingerprint.kt | 14 + .../ad/video/patch/MusicVideoAdsPatch.kt | 40 + .../annotations/CodecsUnlockCompatibility.kt | 30 + .../AllCodecsReferenceFingerprint.kt | 52 + .../fingerprints/CodecsLockFingerprint.kt | 29 + .../audio/codecs/patch/CodecsUnlockPatch.kt | 56 + .../ExclusiveAudioCompatibility.kt | 30 + .../AudioOnlyEnablerFingerprint.kt | 28 + .../fingerprints/ExclusiveAudioFingerprint.kt | 51 + .../patch/ExclusiveAudioPatch.kt | 33 + .../annotations/CompactHeaderCompatibility.kt | 29 + .../CompactHeaderConstructorFingerprint.kt | 23 + .../compactheader/patch/CompactHeaderPatch.kt | 41 + .../MinimizedPlaybackCompatibility.kt | 30 + .../MinimizedPlaybackManagerFingerprint.kt | 26 + .../patch/MinimizedPlaybackPatch.kt | 34 + .../HideGetPremiumCompatibility.kt | 30 + .../fingerprints/HideGetPremiumFingerprint.kt | 16 + .../HideGetPremiumParentFingerprint.kt | 19 + .../premium/patch/HideGetPremiumPatch.kt | 51 + .../RemoveTasteBuilderCompatibility.kt | 32 + .../TasteBuilderConstructorFingerprint.kt | 34 + .../patch/RemoveTasteBuilderPatch.kt | 41 + .../RemoveUpgradeButtonCompatibility.kt | 30 + .../PivotBarConstructorFingerprint.kt | 62 + .../patch/RemoveUpgradeButtonPatch.kt | 78 + .../MusicMicroGPatchCompatibility.kt | 30 + .../CastContextFetchFingerprint.kt | 8 + .../CastDynamiteModuleFingerprint.kt | 8 + .../CastDynamiteModuleV2Fingerprint.kt | 8 + .../GooglePlayUtilityFingerprint.kt | 18 + .../microg/fingerprints/PrimeFingerprint.kt | 8 + .../fingerprints/ServiceCheckFingerprint.kt | 15 + .../bytecode/MusicMicroGBytecodePatch.kt | 65 + .../resource/MusicMicroGResourcePatch.kt | 41 + .../music/misc/microg/shared/Constants.kt | 9 + .../BackgroundPlayCompatibility.kt | 30 + .../BackgroundPlaybackDisableFingerprint.kt | 43 + .../patch/BackgroundPlayPatch.kt | 36 + .../pro/annotations/UnlockProCompatibility.kt | 9 + .../pro/fingerprints/IsEnabledFingerprint.kt | 8 + .../misc/pro/patch/UnlockProPatch.kt | 38 + .../pro/annotations/UnlockProCompatibility.kt | 9 + .../pro/fingerprints/CheckProFingerprint.kt | 9 + .../nyx/misc/pro/patch/UnlockProPatch.kt | 38 + .../annotations/GeneralAdsCompatibility.kt | 9 + .../ad/general/patch/GeneralAdsPatch.kt | 54 + .../annotations/PremiumIconCompatibility.kt | 9 + .../fingerprints/PremiumIconFingerprint.kt | 10 + .../premiumicon/patch/PremiumIconPatch.kt | 36 + .../shared/fingerprints/SeekbarFingerprint.kt | 9 + .../fingerprints/SeekbarOnDrawFingerprint.kt | 8 + .../WatchWhileActivityFingerprint.kt | 10 + .../patch/AbstractIntegrationsPatch.kt | 66 + .../misc/patch/ResourceMappingPatch.kt | 73 + .../settings/preference/BasePreference.kt | 31 + .../settings/preference/BaseResource.kt | 26 + .../preference/ComponentsExtensions.kt | 42 + .../shared/settings/preference/IPreference.kt | 23 + .../shared/settings/preference/IResource.kt | 16 + .../shared/settings/preference/SummaryType.kt | 5 + .../settings/preference/impl/ArrayResource.kt | 33 + .../settings/preference/impl/InputType.kt | 6 + .../preference/impl/ListPreference.kt | 38 + .../settings/preference/impl/Preference.kt | 44 + .../preference/impl/PreferenceCategory.kt | 29 + .../preference/impl/PreferenceScreen.kt | 34 + .../preference/impl/StringResource.kt | 31 + .../preference/impl/SwitchPreference.kt | 35 + .../preference/impl/TextPreference.kt | 35 + .../patch/AbstractSettingsResourcePatch.kt | 135 + .../settings/util/AbstractPreferenceScreen.kt | 89 + .../DisableCaptureRestrictionCompatibility.kt | 12 + .../DisableCaptureRestrictionBytecodePatch.kt | 86 + ...aptureRestrictionAudioDriverFingerprint.kt | 10 + .../DisableCaptureRestrictionResourcePatch.kt | 33 + .../theme/annotations/ThemeCompatibility.kt | 9 + .../spotify/layout/theme/patch/ThemePatch.kt | 66 + .../PremiumNavbarTabCompatibility.kt | 9 + .../AddPremiumNavbarTabFingerprint.kt | 7 + .../AddPremiumNavbarTabParentFingerprint.kt | 7 + .../patch/PremiumNavbarTabPatch.kt | 63 + .../annotations/UnlockThemesCompatibility.kt | 9 + .../CheckLockedThemesFingerprint.kt | 9 + .../fingerprints/SetThemeFingerprint.kt | 9 + .../themeunlock/patch/UnlockThemePatch.kt | 43 + .../ad/annotations/HideAdsCompatibility.kt | 14 + .../ConvertHelpFeedItemListFingerprint.kt | 10 + .../FeedItemListCloneFingerprint.kt | 9 + .../patches/tiktok/ad/patch/HideAdsPatch.kt | 57 + .../annotations/FeedFilterCompatibility.kt | 14 + .../FeedApiServiceLIZFingerprint.kt | 12 + .../feedfilter/patch/FeedFilterPatch.kt | 51 + .../annotations/DownloadsCompatibility.kt | 14 + .../fingerprints/ACLCommonShareFingerprint.kt | 14 + .../ACLCommonShareFingerprint2.kt | 20 + .../ACLCommonShareFingerprint3.kt | 20 + .../DownloadPathParentFingerprint.kt | 15 + .../downloads/patch/DownloadsPatch.kt | 131 + .../annotations/ShowSeekbarCompatibility.kt | 14 + .../AwemeGetVideoControlFingerprint.kt | 12 + .../seekbar/patch/ShowSeekbarPatch.kt | 47 + .../annotations/PlaybackSpeedCompatibility.kt | 14 + .../SpeedControlParentFingerprint.kt | 16 + .../speed/patch/PlaybackSpeedPatch.kt | 48 + .../annotations/IntegrationsCompatibility.kt | 14 + .../fingerprints/InitFingerprint.kt | 10 + .../integrations/patch/IntegrationsPatch.kt | 13 + .../DisableLoginRequirementCompatibility.kt | 14 + .../MandatoryLoginServiceFingerprint.kt | 9 + .../MandatoryLoginServiceFingerprint2.kt | 16 + .../patch/DisableLoginRequirementPatch.kt | 43 + .../FixGoogleLoginCompatibility.kt | 14 + .../GoogleAuthAvailableFingerprint.kt | 14 + .../GoogleOneTapAuthAvailableFingerprint.kt | 14 + .../fixgoogle/patch/FixGoogleLoginPatch.kt | 44 + .../annotations/SettingsCompatibility.kt | 14 + .../fingerprints/AboutViewFingerprint.kt | 39 + ...sonalizationActivityOnCreateFingerprint.kt | 10 + .../SettingsOnViewCreatedFingerprint.kt | 10 + .../SettingsStatusLoadFingerprint.kt | 10 + .../misc/settings/patch/SettingsPatch.kt | 120 + .../sim/annotations/SpoofSimCompatibility.kt | 14 + .../misc/spoof/sim/patch/SpoofSimPatch.kt | 110 + .../annotations/AudioAdsCompatibility.kt | 10 + .../AudioAdsPresenterPlayFingerprint.kt | 10 + .../twitch/ad/audio/patch/AudioAdsPatch.kt | 67 + .../annotations/EmbeddedAdsCompatibility.kt | 10 + .../CreateUsherClientFingerprint.kt | 9 + .../ad/embedded/patch/EmbeddedAdsPatch.kt | 78 + .../twitch/ad/shared/util/AbstractAdPatch.kt | 51 + .../annotations/VideoAdsCompatibility.kt | 10 + .../CheckAdEligibilityLambdaFingerprint.kt | 15 + .../ContentConfigShowAdsFingerprint.kt | 10 + .../GetReadyToShowAdFingerprint.kt | 9 + .../twitch/ad/video/patch/VideoAdsPatch.kt | 149 + .../ShowDeletedMessagesCompatibility.kt | 10 + .../ChatUtilCreateDeletedSpanFingerprint.kt | 9 + ...etedMessageClickableSpanCtorFingerprint.kt | 12 + .../SetHasModAccessFingerprint.kt | 10 + .../patch/ShowDeletedMessagesPatch.kt | 103 + .../annotations/DebugModeCompatibility.kt | 10 + .../IsDebugConfigEnabledFingerprint.kt | 10 + .../IsOmVerificationEnabledFingerprint.kt | 10 + .../ShouldShowDebugOptionsFingerprint.kt | 10 + .../twitch/debug/patch/DebugModePatch.kt | 76 + .../annotations/IntegrationsCompatibility.kt | 9 + .../fingerprints/InitFingerprint.kt | 16 + .../integrations/patch/IntegrationsPatch.kt | 13 + .../annotations/SettingsCompatibility.kt | 9 + .../settings/bytecode/patch/SettingsPatch.kt | 196 + .../components/CustomPreferenceCategory.kt | 20 + .../MenuGroupsOnClickFingerprint.kt | 15 + .../MenuGroupsUpdatedFingerprint.kt | 10 + .../SettingsActivityOnCreateFingerprint.kt | 10 + .../SettingsMenuItemEnumFingerprint.kt | 9 + .../resource/patch/SettingsResourcePatch.kt | 44 + .../annotations/TimelineAdsCompatibility.kt | 9 + .../TimelineTweetJsonParserFingerprint.kt | 18 + .../ad/timeline/patch/TimelineAdsPatch.kt | 77 + .../annotations/DynamicColorCompatibility.kt | 9 + .../dynamiccolor/patch/DynamicColorPatch.kt | 87 + .../MonochromeIconCompatibility.kt | 9 + .../monochrome/patch/MonochromeIconPatch.kt | 55 + .../FirebaseGetCertPatchCompatibility.kt | 9 + .../GetCertMessagingFingerprint.kt | 12 + .../GetCertRegistrationFingerprint.kt | 12 + .../patch/FirebaseGetCertPatch.kt | 46 + .../PromoCodeUnlockCompatibility.kt | 9 + .../PromoCodeUnlockFingerprint.kt | 8 + .../promocode/patch/PromoCodeUnlockPatch.kt | 47 + .../annotations/UnlockProCompatibility.kt | 9 + .../fingerprints/CheckProFingerprint.kt | 10 + .../misc/unlockpro/patch/UnlockProPatch.kt | 38 + .../annotation/GeneralAdsCompatibility.kt | 14 + .../ReelConstructorFingerprint.kt | 17 + .../general/bytecode/patch/GeneralAdsPatch.kt | 91 + .../resource/patch/GeneralAdsResourcePatch.kt | 232 + .../annotations/VideoAdsCompatibility.kt | 14 + .../fingerprints/LoadVideoAdsFingerprint.kt | 12 + .../youtube/ad/video/patch/VideoAdsPatch.kt | 58 + .../annotation/DownloadsCompatibility.kt | 14 + .../bytecode/patch/DownloadsBytecodePatch.kt | 52 + .../resource/patch/DownloadsResourcePatch.kt | 72 + .../annotation/SeekbarTappingCompatibility.kt | 14 + .../fingerprints/SeekbarTappingFingerprint.kt | 34 + .../SeekbarTappingParentFingerprint.kt | 44 + .../patch/EnableSeekbarTappingPatch.kt | 110 + .../annotation/SwipeControlsCompatibility.kt | 13 + .../SwipeControlsHostActivityFingerprint.kt | 10 + .../bytecode/SwipeControlsBytecodePatch.kt | 68 + .../resource/SwipeControlsResourcePatch.kt | 111 + .../annotations/AutoCaptionsCompatibility.kt | 13 + .../StartVideoInformerFingerprint.kt | 33 + .../SubtitleButtonControllerFingerprint.kt | 20 + .../fingerprints/SubtitleTrackFingerprint.kt | 18 + .../autocaptions/patch/AutoCaptionsPatch.kt | 80 + .../AutoplayButtonCompatibility.kt | 13 + .../AutoNavInformerFingerprint.kt | 20 + .../LayoutConstructorFingerprint.kt | 10 + .../patch/HideAutoplayButtonPatch.kt | 92 + .../PremiumHeadingCompatibility.kt | 9 + .../header/patch/PremiumHeadingPatch.kt | 51 + .../CustomBrandingCompatibility.kt | 9 + .../icon/patch/CustomBrandingPatch.kt | 94 + .../annotations/HideButtonsCompatibility.kt | 13 + .../layout/buttons/patch/HideButtonsPatch.kt | 81 + .../annotations/CastPatchCompatibility.kt | 9 + .../castbutton/patch/HideCastButtonPatch.kt | 57 + .../annotations/CommentsCompatibility.kt | 13 + .../ShortsCommentsButtonFingerprint.kt | 17 + .../comments/bytecode/patch/CommentsPatch.kt | 62 + .../resource/patch/CommentsResourcePatch.kt | 64 + .../FullscreenPanelsCompatibility.kt | 13 + .../FullscreenViewAdderFingerprint.kt | 11 + .../FullscreenViewAdderParentFingerprint.kt | 18 + .../patch/FullscreenPanelsRemoverPatch.kt | 65 + .../annotations/AlbumCardsCompatibility.kt | 13 + .../fingerprints/AlbumCardsFingerprint.kt | 23 + .../bytecode/patch/AlbumCardsPatch.kt | 45 + .../resource/patch/AlbumCardsResourcePatch.kt | 42 + .../HideArtistCardCompatibility.kt | 13 + .../patch/HideArtistCardPatch.kt | 38 + .../patch/HideCaptionsButtonPatch.kt | 56 + .../CrowdfundingBoxCompatibility.kt | 13 + .../CrowdfundingBoxFingerprint.kt | 23 + .../bytecode/patch/CrowdfundingBoxPatch.kt | 46 + .../patch/CrowdfundingBoxResourcePatch.kt | 42 + .../HideEndScreenCardsCompatibility.kt | 13 + .../fingerprints/LayoutCircleFingerprint.kt | 22 + .../fingerprints/LayoutIconFingerprint.kt | 22 + .../fingerprints/LayoutVideoFingerprint.kt | 22 + .../bytecode/patch/HideEndscreenCardsPatch.kt | 57 + .../patch/HideEndscreenCardsResourcePatch.kt | 48 + .../annotations/HideInfocardsCompatibility.kt | 13 + .../InfocardsIncognitoFingerprint.kt | 11 + .../InfocardsIncognitoParentFingerprint.kt | 11 + .../InfocardsMethodCallFingerprint.kt | 19 + .../hideinfocards/patch/HideInfocardsPatch.kt | 77 + .../patch/HideInfocardsResourcePatch.kt | 40 + .../MixPlaylistsPatchCompatibility.kt | 13 + .../CreateMixPlaylistFingerprint.kt | 24 + .../SecondCreateMixPlaylistFingerprint.kt | 24 + .../patch/MixPlaylistsPatch.kt | 64 + .../HideTimeAndSeekbarCompatibility.kt | 13 + .../fingerprints/TimeCounterFingerprint.kt | 25 + .../patch/HideTimeAndSeekbarPatch.kt | 76 + .../OldQualityLayoutCompatibility.kt | 13 + .../QualityMenuViewInflateFingerprint.kt | 31 + .../patch/OldQualityLayoutPatch.kt | 61 + .../HideEmailAddressCompatibility.kt | 13 + ...ntSwitcherAccessibilityLabelFingerprint.kt | 24 + .../bytecode/patch/HideEmailAddressPatch.kt | 53 + .../patch/HideEmailAddressResourcePatch.kt | 42 + .../annotations/PivotBarCompatibility.kt | 13 + .../PivotBarCreateButtonViewFingerprint.kt | 14 + .../patch/CreateButtonRemoverPatch.kt | 71 + .../InitializeButtonsFingerprint.kt | 15 + .../PivotBarConstructorFingerprint.kt | 10 + .../patch/ResolvePivotBarFingerprintsPatch.kt | 45 + .../fingerprints/PivotBarEnumFingerprint.kt | 15 + .../PivotBarShortsButtonViewFingerprint.kt | 12 + .../patch/ShortsButtonRemoverPatch.kt | 93 + .../layout/pivotbar/utils/InjectionUtils.kt | 30 + .../PlayerButtonBackgroundCompatibility.kt | 12 + .../patch/PlayerButtonBackgroundPatch.kt | 41 + .../PlayerPopupPanelsCompatibility.kt | 13 + .../EngagementPanelControllerFingerprint.kt | 36 + .../patch/PlayerPopupPanelsPatch.kt | 59 + .../annotations/HideReelsCompatibility.kt | 13 + .../fingerprints/HideReelsFingerprint.kt | 10 + .../layout/reels/patch/HideReelsPatch.kt | 51 + .../ReturnYouTubeDislikeCompatibility.kt | 13 + .../fingerprints/DislikeFingerprint.kt | 11 + .../fingerprints/LikeFingerprint.kt | 14 + .../fingerprints/RemoveLikeFingerprint.kt | 11 + .../fingerprints/TextComponentFingerprint.kt | 8 + .../TextComponentSpecParentFingerprint.kt | 8 + .../patch/ReturnYouTubeDislikePatch.kt | 85 + .../ReturnYouTubeDislikeResourcePatch.kt | 42 + .../annotations/SponsorBlockCompatibility.kt | 13 + .../fingerprints/AppendTimeFingerprint.kt | 29 + .../NextGenWatchLayoutFingerprint.kt | 10 + .../PlayerOverlaysLayoutInitFingerprint.kt | 9 + .../RectangleFieldInvalidatorFingerprint.kt | 22 + .../ShortsPlayerConstructorFingerprint.kt | 41 + .../patch/SponsorBlockBytecodePatch.kt | 293 + .../patch/SponsorBlockResourcePatch.kt | 116 + .../StartupShortsResetCompatibility.kt | 13 + .../UserWasInShortsFingerprint.kt | 24 + .../patch/DisableShortsOnStartupPatch.kt | 59 + .../TabletMiniPlayerCompatibility.kt | 13 + ...niPlayerDimensionsCalculatorFingerprint.kt | 23 + .../MiniPlayerOverrideFingerprint.kt | 11 + .../MiniPlayerOverrideNoContextFingerprint.kt | 11 + ...PlayerResponseModelSizeCheckFingerprint.kt | 20 + .../patch/TabletMiniPlayerPatch.kt | 105 + .../theme/annotations/ThemeCompatibility.kt | 9 + .../fingerprints/LithoThemeFingerprint.kt | 26 + .../layout/theme/patch/LithoThemePatch.kt | 36 + .../youtube/layout/theme/patch/ThemePatch.kt | 74 + .../annotations/WatchinVRCompatibility.kt | 13 + .../fingerprints/WatchinVRFingerprint.kt | 10 + .../layout/watchinvr/patch/WatchinVRPatch.kt | 55 + .../annotations/HideWaterwarkCompatibility.kt | 13 + .../fingerprints/HideWatermarkFingerprint.kt | 9 + .../HideWatermarkParentFingerprint.kt | 9 + .../watermark/patch/HideWatermarkPatch.kt | 63 + .../annotations/WideSearchbarCompatibility.kt | 13 + .../fingerprints/DrawActionBarFingerprint.kt | 27 + .../SetWordmarkHeaderFingerprint.kt | 22 + .../widesearchbar/patch/WideSearchbarPatch.kt | 88 + .../annotations/AutoRepeatCompatibility.kt | 13 + .../fingerprints/AutoRepeatFingerprint.kt | 12 + .../AutoRepeatParentFingerprint.kt | 14 + .../misc/autorepeat/patch/AutoRepeatPatch.kt | 88 + .../annotations/DebuggingCompatibility.kt | 9 + .../misc/debugging/patch/DebuggingPatch.kt | 77 + .../annotations/FixPlaybackCompatibility.kt | 13 + .../fix/playback/patch/FixPlaybackPatch.kt | 52 + .../annotations/ClientSpoofCompatibility.kt | 14 + .../UserAgentHeaderBuilderFingerprint.kt | 11 + .../misc/fix/spoof/patch/ClientSpoofPatch.kt | 37 + .../VerticalScrollCompatibility.kt | 13 + .../CanScrollVerticallyFingerprint.kt | 12 + .../patch/VerticalScrollPatch.kt | 33 + .../annotations/HDRBrightnessCompatibility.kt | 13 + .../fingerprints/HDRBrightnessFingerprint.kt | 10 + .../hdrbrightness/patch/HDRBrightnessPatch.kt | 64 + .../annotations/IntegrationsCompatibility.kt | 13 + .../fingerprints/InitFingerprint.kt | 7 + .../fingerprints/ServiceFingerprint.kt | 8 + .../StandalonePlayerFingerprint.kt | 10 + .../integrations/patch/IntegrationsPatch.kt | 15 + .../annotation/LithoFilterCompatibility.kt | 14 + .../ComponentContextParserFingerprint.kt | 20 + .../EmptyComponentBuilderFingerprint.kt | 10 + .../litho/filter/patch/LithoFilterPatch.kt | 81 + .../FixLocaleConfigErrorCompatibility.kt | 9 + .../patch/FixLocaleConfigErrorPatch.kt | 36 + .../annotations/MicroGPatchCompatibility.kt | 13 + .../CastContextFetchFingerprint.kt | 8 + .../CastDynamiteModuleFingerprint.kt | 8 + .../CastDynamiteModuleV2Fingerprint.kt | 8 + .../GooglePlayUtilityFingerprint.kt | 12 + .../fingerprints/IntegrityCheckFingerprint.kt | 10 + .../microg/fingerprints/PrimeFingerprint.kt | 8 + .../fingerprints/ServiceCheckFingerprint.kt | 10 + .../patch/bytecode/MicroGBytecodePatch.kt | 75 + .../patch/resource/MicroGResourcePatch.kt | 62 + .../youtube/misc/microg/shared/Constants.kt | 9 + .../MinimizedPlaybackCompatibility.kt | 13 + .../MinimizedPlaybackKidsFingerprint.kt | 23 + .../MinimizedPlaybackManagerFingerprint.kt | 42 + .../MinimizedPlaybackSettingsFingerprint.kt | 25 + .../patch/MinimizedPlaybackPatch.kt | 88 + .../OpenLinksDirectlyCompatibility.kt | 13 + .../OpenLinksDirectlyFingerprintPrimary.kt | 23 + .../OpenLinksDirectlyFingerprintSecondary.kt | 28 + .../patch/OpenLinksDirectlyPatch.kt | 72 + .../annotation/PlayerControlsCompatibility.kt | 13 + .../patch/PlayerControlsBytecodePatch.kt | 83 + .../BottomControlsInflateFingerprint.kt | 21 + .../PlayerControlsVisibilityFingerprint.kt | 12 + .../patch/BottomControlsResourcePatch.kt | 85 + .../PlayerOverlaysHookCompatibility.kt | 13 + ...layerOverlaysOnFinishInflateFingerprint.kt | 10 + .../patch/PlayerOverlaysHookPatch.kt | 35 + .../annotation/PlayerTypeHookCompatibility.kt | 13 + .../UpdatePlayerTypeFingerprint.kt | 33 + .../playertype/patch/PlayerTypeHookPatch.kt | 34 + .../annotations/SettingsCompatibility.kt | 9 + .../LicenseActivityFingerprint.kt | 9 + .../ThemeConstructorFingerprint.kt | 12 + .../fingerprints/ThemeSetterAppFingerprint.kt | 22 + .../ThemeSetterSystemFingerprint.kt | 16 + .../settings/bytecode/patch/SettingsPatch.kt | 160 + .../resource/patch/SettingsResourcePatch.kt | 169 + .../VideoInformationCompatibility.kt | 13 + .../CreateVideoPlayerSeekbarFingerprint.kt | 9 + ...erControllerSetTimeReferenceFingerprint.kt | 11 + .../fingerprints/PlayerInitFingerprint.kt | 10 + .../fingerprints/SeekFingerprint.kt | 8 + .../fingerprints/VideoLengthFingerprint.kt | 23 + .../fingerprints/VideoTimeFingerprint.kt | 8 + .../patch/VideoInformationPatch.kt | 178 + .../RememberVideoQualityCompatibility.kt | 13 + .../VideoQualityReferenceFingerprint.kt | 12 + .../VideoQualitySetterFingerprint.kt | 20 + .../VideoUserQualityChangeFingerprint.kt | 20 + .../patch/RememberVideoQualityPatch.kt | 88 + .../CustomPlaybackSpeedCompatibility.kt | 13 + .../SpeedArrayGeneratorFingerprint.kt | 22 + .../fingerprints/SpeedLimiterFingerprint.kt | 22 + .../VideoSpeedPatchFingerprint.kt | 11 + .../custom/patch/CustomVideoSpeedPatch.kt | 195 + .../annotation/VideoIdCompatibility.kt | 13 + .../videoid/fingerprint/VideoIdFingerprint.kt | 16 + .../misc/video/videoid/patch/VideoIdPatch.kt | 58 + .../CustomVideoBufferCompatibility.kt | 13 + .../fingerprints/MaxBufferFingerprint.kt | 19 + .../fingerprints/PlaybackBufferFingerprint.kt | 18 + .../fingerprints/ReBufferFingerprint.kt | 18 + .../patch/CustomVideoBufferPatch.kt | 137 + .../annotations/ZoomHapticsCompatibility.kt | 9 + .../fingerprints/ZoomHapticsFingerprint.kt | 8 + .../zoomhaptics/patch/ZoomHapticsPatch.kt | 54 + .../app/revanced/util/microg/Constants.kt | 130 + .../util/microg/MicroGBytecodeHelper.kt | 241 + .../util/microg/MicroGManifestHelper.kt | 58 + .../util/microg/MicroGResourceHelper.kt | 59 + .../revanced/util/resources/ResourceUtils.kt | 99 + ...veproduct_youtube_background_color_108.png | Bin 0 -> 3044 bytes ...veproduct_youtube_foreground_color_108.png | Bin 0 -> 5129 bytes .../branding/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4571 bytes .../mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4571 bytes ...veproduct_youtube_background_color_108.png | Bin 0 -> 2937 bytes ...veproduct_youtube_foreground_color_108.png | Bin 0 -> 4366 bytes .../branding/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3919 bytes .../mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3919 bytes ...veproduct_youtube_background_color_108.png | Bin 0 -> 3188 bytes ...veproduct_youtube_foreground_color_108.png | Bin 0 -> 6199 bytes .../branding/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5255 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5255 bytes ...veproduct_youtube_background_color_108.png | Bin 0 -> 3481 bytes ...veproduct_youtube_foreground_color_108.png | Bin 0 -> 7429 bytes .../branding/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6719 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 6719 bytes ...veproduct_youtube_background_color_108.png | Bin 0 -> 3893 bytes ...veproduct_youtube_foreground_color_108.png | Bin 0 -> 7945 bytes .../branding/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 8086 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 8086 bytes .../drawable/revanced_yt_download_button.xml | 4 + .../youtube_controls_bottom_ui_container.xml | 4 + .../downloads/host/values/strings.xml | 4 + .../resources/microg/host/values/strings.xml | 4 + .../host/values/strings.xml | 12 + .../quantum_ic_arrow_back_white_24.png | Bin 0 -> 200 bytes .../quantum_ic_arrow_back_white_24.png | Bin 0 -> 194 bytes .../layout/revanced_settings_toolbar.xml | 4 + .../layout/revanced_settings_with_toolbar.xml | 4 + .../revanced_settings_with_toolbar_layout.xml | 5 + .../resources/settings/xml/revanced_prefs.xml | 4 + .../quantum_ic_skip_next_white_24.png | Bin 0 -> 357 bytes .../sponsorblock/drawable/ic_sb_adjust.xml | 10 + .../sponsorblock/drawable/ic_sb_compare.xml | 10 + .../sponsorblock/drawable/ic_sb_edit.xml | 10 + .../sponsorblock/drawable/ic_sb_logo.xml | 19 + .../sponsorblock/drawable/ic_sb_publish.xml | 10 + .../sponsorblock/drawable/ic_sb_voting.xml | 10 + .../host/layout/youtube_controls_layout.xml | 4 + .../sponsorblock/host/values/strings.xml | 142 + .../layout/inline_sponsor_overlay.xml | 5 + .../sponsorblock/layout/new_segment.xml | 107 + .../layout/skip_sponsor_button.xml | 7 + .../drawable/ic_sc_brightness_auto.xml | 5 + .../drawable/ic_sc_brightness_manual.xml | 5 + .../drawable/ic_sc_volume_mute.xml | 5 + .../drawable/ic_sc_volume_normal.xml | 5 + .../theme/values-night-v31/styles.xml | 8 + .../twitch/settings/xml/revanced_prefs.xml | 4 + 506 files changed, 25088 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/git_toolbox_prj.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 .releaserc create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 patches.json create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/app/revanced/extensions/Extensions.kt create mode 100644 src/main/kotlin/app/revanced/meta/Meta.kt create mode 100644 src/main/kotlin/app/revanced/meta/json/Generator.kt create mode 100644 src/main/kotlin/app/revanced/meta/json/JsonPatch.kt create mode 100644 src/main/kotlin/app/revanced/meta/readme/Extensions.kt create mode 100644 src/main/kotlin/app/revanced/meta/readme/Generator.kt create mode 100644 src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt create mode 100644 src/main/kotlin/app/revanced/meta/readme/Template.kt create mode 100644 src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt create mode 100644 src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/Constants.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt create mode 100644 src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt create mode 100644 src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt create mode 100644 src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_background_color_108.png create mode 100644 src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_foreground_color_108.png create mode 100644 src/main/resources/branding/mipmap-hdpi/ic_launcher.png create mode 100644 src/main/resources/branding/mipmap-hdpi/ic_launcher_round.png create mode 100644 src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_background_color_108.png create mode 100644 src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_foreground_color_108.png create mode 100644 src/main/resources/branding/mipmap-mdpi/ic_launcher.png create mode 100644 src/main/resources/branding/mipmap-mdpi/ic_launcher_round.png create mode 100644 src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_background_color_108.png create mode 100644 src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_foreground_color_108.png create mode 100644 src/main/resources/branding/mipmap-xhdpi/ic_launcher.png create mode 100644 src/main/resources/branding/mipmap-xhdpi/ic_launcher_round.png create mode 100644 src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_background_color_108.png create mode 100644 src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_foreground_color_108.png create mode 100644 src/main/resources/branding/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/main/resources/branding/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_background_color_108.png create mode 100644 src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_foreground_color_108.png create mode 100644 src/main/resources/branding/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/main/resources/branding/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 src/main/resources/downloads/drawable/revanced_yt_download_button.xml create mode 100644 src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml create mode 100644 src/main/resources/downloads/host/values/strings.xml create mode 100644 src/main/resources/microg/host/values/strings.xml create mode 100644 src/main/resources/returnyoutubedislike/host/values/strings.xml create mode 100644 src/main/resources/settings/drawable-ldrtl-xxxhdpi/quantum_ic_arrow_back_white_24.png create mode 100644 src/main/resources/settings/drawable-xxxhdpi/quantum_ic_arrow_back_white_24.png create mode 100644 src/main/resources/settings/layout/revanced_settings_toolbar.xml create mode 100644 src/main/resources/settings/layout/revanced_settings_with_toolbar.xml create mode 100644 src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml create mode 100644 src/main/resources/settings/xml/revanced_prefs.xml create mode 100644 src/main/resources/sponsorblock/drawable-xxxhdpi/quantum_ic_skip_next_white_24.png create mode 100644 src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml create mode 100644 src/main/resources/sponsorblock/drawable/ic_sb_compare.xml create mode 100644 src/main/resources/sponsorblock/drawable/ic_sb_edit.xml create mode 100644 src/main/resources/sponsorblock/drawable/ic_sb_logo.xml create mode 100644 src/main/resources/sponsorblock/drawable/ic_sb_publish.xml create mode 100644 src/main/resources/sponsorblock/drawable/ic_sb_voting.xml create mode 100644 src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml create mode 100644 src/main/resources/sponsorblock/host/values/strings.xml create mode 100644 src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml create mode 100644 src/main/resources/sponsorblock/layout/new_segment.xml create mode 100644 src/main/resources/sponsorblock/layout/skip_sponsor_button.xml create mode 100644 src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml create mode 100644 src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml create mode 100644 src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml create mode 100644 src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml create mode 100644 src/main/resources/theme/values-night-v31/styles.xml create mode 100644 src/main/resources/twitch/settings/xml/revanced_prefs.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a877008 --- /dev/null +++ b/.gitignore @@ -0,0 +1,123 @@ +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Gradle template +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# Potentially copyrighted test APK +*.apk + +# Ignore vscode config +.vscode/ + +# Dependency directories +node_modules/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..877c6d9 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,9 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ +/kotlinc.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml new file mode 100644 index 0000000..02b915b --- /dev/null +++ b/.idea/git_toolbox_prj.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f1854e4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..7ddfc9e --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..8fd4c5b --- /dev/null +++ b/.releaserc @@ -0,0 +1,45 @@ +{ + "branches": [ + "main", + { + "name": "dev", + "prerelease": true + } + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "gradle-semantic-release-plugin", + [ + "@semantic-release/git", + { + "assets": [ + "README.md", + "CHANGELOG.md", + "gradle.properties", + "patches.json" + ] + } + ], + [ + "@semantic-release/github", + { + "assets": [ + { + "path": "build/libs/*.jar" + }, + { + "path": "patches.json" + } + ] + } + ], + [ + "@saithodev/semantic-release-backmerge", + { + "branches": [{from: "main", to: "dev"}] + } + ] + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8637ee9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2751 @@ +# [2.146.0](https://github.com/revanced/revanced-patches/compare/v2.145.0...v2.146.0) (2022-12-18) + + +### Features + +* **predictive-back-gesture:** exclude by default ([176d34b](https://github.com/revanced/revanced-patches/commit/176d34b2ff92d0eb627dad86ad87f559df3db815)) +* **twitter/timeline-ads:** add clarifying notice to description ([0eca2af](https://github.com/revanced/revanced-patches/commit/0eca2af1a4a688903c61ff6469ffea90c60f06a6)) +* **youtube:** bump patches compatibility to v17.49.37 ([83ebc73](https://github.com/revanced/revanced-patches/commit/83ebc731130d3d216ac4b815382d8f13548cf665)) + +# [2.146.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.145.0...v2.146.0-dev.1) (2022-12-18) + + +### Features + +* **predictive-back-gesture:** exclude by default ([176d34b](https://github.com/revanced/revanced-patches/commit/176d34b2ff92d0eb627dad86ad87f559df3db815)) +* **twitter/timeline-ads:** add clarifying notice to description ([0eca2af](https://github.com/revanced/revanced-patches/commit/0eca2af1a4a688903c61ff6469ffea90c60f06a6)) +* **youtube:** bump patches compatibility to v17.49.37 ([83ebc73](https://github.com/revanced/revanced-patches/commit/83ebc731130d3d216ac4b815382d8f13548cf665)) + +# [2.145.0](https://github.com/revanced/revanced-patches/compare/v2.144.0...v2.145.0) (2022-12-18) + + +### Bug Fixes + +* **tiktok/settings:** make compatible with newer versions ([c88740d](https://github.com/revanced/revanced-patches/commit/c88740dc2bf040747d9704d5bed52a7b533c22d6)) + + +### Features + +* **crunchyroll:** remove `enable-downloads` patch ([2ac1546](https://github.com/revanced/revanced-patches/commit/2ac15460f5734fc2c6d2d78d34083a8e6b2f7012)) +* **youtube-music:** bump compatibility to 5.36.51 ([c4a3099](https://github.com/revanced/revanced-patches/commit/c4a3099c1ca39431fba9beb94cc2b43e260561d9)) + +# [2.145.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.144.0...v2.145.0-dev.1) (2022-12-18) + + +### Bug Fixes + +* **tiktok/settings:** make compatible with newer versions ([c88740d](https://github.com/revanced/revanced-patches/commit/c88740dc2bf040747d9704d5bed52a7b533c22d6)) + + +### Features + +* **crunchyroll:** remove `enable-downloads` patch ([2ac1546](https://github.com/revanced/revanced-patches/commit/2ac15460f5734fc2c6d2d78d34083a8e6b2f7012)) +* **youtube-music:** bump compatibility to 5.36.51 ([c4a3099](https://github.com/revanced/revanced-patches/commit/c4a3099c1ca39431fba9beb94cc2b43e260561d9)) + +# [2.144.0](https://github.com/revanced/revanced-patches/compare/v2.143.0...v2.144.0) (2022-12-15) + + +### Features + +* **citra:** add compatibility for canary package ([#1256](https://github.com/revanced/revanced-patches/issues/1256)) ([42ec021](https://github.com/revanced/revanced-patches/commit/42ec0218d829ea15759f83562d24588ce97cb646)) +* link packages to Google PlayStore ([#1272](https://github.com/revanced/revanced-patches/issues/1272)) ([97892e0](https://github.com/revanced/revanced-patches/commit/97892e01044c74916375aeebcc094d3304e14f4e)) + +# [2.144.0-dev.2](https://github.com/revanced/revanced-patches/compare/v2.144.0-dev.1...v2.144.0-dev.2) (2022-12-15) + + +### Features + +* link packages to Google PlayStore ([#1272](https://github.com/revanced/revanced-patches/issues/1272)) ([97892e0](https://github.com/revanced/revanced-patches/commit/97892e01044c74916375aeebcc094d3304e14f4e)) + +# [2.144.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.143.0...v2.144.0-dev.1) (2022-12-15) + + +### Features + +* **citra:** add compatibility for canary package ([#1256](https://github.com/revanced/revanced-patches/issues/1256)) ([42ec021](https://github.com/revanced/revanced-patches/commit/42ec0218d829ea15759f83562d24588ce97cb646)) + +# [2.143.0](https://github.com/revanced/revanced-patches/compare/v2.142.0...v2.143.0) (2022-12-14) + + +### Bug Fixes + +* **predictive-back-gesture:** create attribute, if it does not exist ([c00e771](https://github.com/revanced/revanced-patches/commit/c00e7717053f806e3b5b3f0bf0ca9c2da07c289b)) + + +### Features + +* **sleepasandroid:** remove `unlock-premium` patch ([#1253](https://github.com/revanced/revanced-patches/issues/1253)) ([414f503](https://github.com/revanced/revanced-patches/commit/414f503e493f2dc27edf134f5db723da77636d81)) + +# [2.143.0-dev.2](https://github.com/revanced/revanced-patches/compare/v2.143.0-dev.1...v2.143.0-dev.2) (2022-12-14) + + +### Bug Fixes + +* **predictive-back-gesture:** create attribute, if it does not exist ([c00e771](https://github.com/revanced/revanced-patches/commit/c00e7717053f806e3b5b3f0bf0ca9c2da07c289b)) + +# [2.143.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.142.0...v2.143.0-dev.1) (2022-12-13) + + +### Features + +* **sleepasandroid:** remove `unlock-premium` patch ([#1253](https://github.com/revanced/revanced-patches/issues/1253)) ([414f503](https://github.com/revanced/revanced-patches/commit/414f503e493f2dc27edf134f5db723da77636d81)) + +# [2.142.0](https://github.com/revanced/revanced-patches/compare/v2.141.0...v2.142.0) (2022-12-11) + + +### Features + +* `predictive-back-gesture` patch ([#1236](https://github.com/revanced/revanced-patches/issues/1236)) ([11b59f7](https://github.com/revanced/revanced-patches/commit/11b59f767c94514d49a84593618ff9d2aace2636)) +* **twitch/block-embedded-ads:** `block-embedded-ads` patch ([#1166](https://github.com/revanced/revanced-patches/issues/1166)) ([018606e](https://github.com/revanced/revanced-patches/commit/018606e9832c43836b6b8f4fbd623adb8876d162)) + +# [2.141.0](https://github.com/revanced/revanced-patches/compare/v2.140.0...v2.141.0) (2022-12-09) + + +### Bug Fixes + +* inconsistencies in preference switches ([#1207](https://github.com/revanced/revanced-patches/issues/1207)) ([b6ea5a4](https://github.com/revanced/revanced-patches/commit/b6ea5a43b3eec6a06c7514cd79569b97a2b7d333)) + + +### Features + +* **youtube/return-youtube-dislike:** simplify preference descriptions ([#1216](https://github.com/revanced/revanced-patches/issues/1216)) ([514b5da](https://github.com/revanced/revanced-patches/commit/514b5da9a7de6f8d0b0d00ec6269f836df3e9333)) + +# [2.140.0](https://github.com/revanced/revanced-patches/compare/v2.139.0...v2.140.0) (2022-12-05) + + +### Features + +* **music:** bump compatibility to v5.34.51 ([fc0c92e](https://github.com/revanced/revanced-patches/commit/fc0c92e2b68a2d93a02f4577aeb864955b1b5701)) + +# [2.139.0](https://github.com/revanced/revanced-patches/compare/v2.138.0...v2.139.0) (2022-12-05) + + +### Features + +* **youtube/return-youtube-dislike:** improve settings labels ([#1203](https://github.com/revanced/revanced-patches/issues/1203)) ([fb4a612](https://github.com/revanced/revanced-patches/commit/fb4a612592d82857b0e892a6f5d135dbd657c44d)) + +# [2.138.0](https://github.com/revanced/revanced-patches/compare/v2.137.0...v2.138.0) (2022-12-05) + + +### Features + +* **youtube/debugging:** print stack traces ([9cea382](https://github.com/revanced/revanced-patches/commit/9cea382750456434d0547351ee7d4013eac9de9c)) + +# [2.137.0](https://github.com/revanced/revanced-patches/compare/v2.136.0...v2.137.0) (2022-12-03) + + +### Features + +* **youtube/return-youtube-dislike:** show dislike as a percentage ([#1185](https://github.com/revanced/revanced-patches/issues/1185)) ([cbbab0b](https://github.com/revanced/revanced-patches/commit/cbbab0b4069534a2032258f9a09e9690fc68c291)) + +# [2.136.0](https://github.com/revanced/revanced-patches/compare/v2.135.0...v2.136.0) (2022-12-02) + + +### Bug Fixes + +* syntax error in compatiblity annotation ([#1181](https://github.com/revanced/revanced-patches/issues/1181)) ([0830c7e](https://github.com/revanced/revanced-patches/commit/0830c7ed1064757cf6354b426549275ece55408e)) + + +### Features + +* **youtube:** drop support for compatibility of old versions ([7413aad](https://github.com/revanced/revanced-patches/commit/7413aad6d215f24a66ba9a33917c73dbdb0b7bc3)) + +# [2.135.0](https://github.com/revanced/revanced-patches/compare/v2.134.1...v2.135.0) (2022-12-02) + + +### Features + +* sort README patch table by amount of patches per package ([40cb14e](https://github.com/revanced/revanced-patches/commit/40cb14e1838aee5b49b60094cd134ed7e1726c3f)) + +## [2.134.1](https://github.com/revanced/revanced-patches/compare/v2.134.0...v2.134.1) (2022-12-02) + + +### Bug Fixes + +* **youtube:** swipe gesture on home screen ([9294084](https://github.com/revanced/revanced-patches/commit/929408406b53d8b0db69f092c3e191791b9fa8ee)), closes [#610](https://github.com/revanced/revanced-patches/issues/610) + +# [2.134.0](https://github.com/revanced/revanced-patches/compare/v2.133.0...v2.134.0) (2022-12-02) + + +### Bug Fixes + +* **anytracker:** syntax error in fingerprint ([#1165](https://github.com/revanced/revanced-patches/issues/1165)) ([46ad786](https://github.com/revanced/revanced-patches/commit/46ad786b1488814494080ca48cff27c361c47ca1)) + + +### Features + +* **anytracker:** `unlock-premium` patch ([#1165](https://github.com/revanced/revanced-patches/issues/1165)) ([e7ed324](https://github.com/revanced/revanced-patches/commit/e7ed324bb5216037be0646a698e7e808fbf03e4e)) + +# [2.133.0](https://github.com/revanced/revanced-patches/compare/v2.132.0...v2.133.0) (2022-12-01) + + +### Features + +* **moneymanager:** `unlock-premium` patch ([#1173](https://github.com/revanced/revanced-patches/issues/1173)) ([438c1bc](https://github.com/revanced/revanced-patches/commit/438c1bca684c7d47bd5a401b4dccbaddef604650)) + +# [2.132.0](https://github.com/revanced/revanced-patches/compare/v2.131.0...v2.132.0) (2022-12-01) + + +### Features + +* **sleepasandroid:** `unlock-premium` patch ([#1172](https://github.com/revanced/revanced-patches/issues/1172)) ([580ddcd](https://github.com/revanced/revanced-patches/commit/580ddcdf1f8ef4a525f772630b3c085b706e4dc7)) +* **twitch:** remove `block-embedded-ads` patch ([#1164](https://github.com/revanced/revanced-patches/issues/1164)) ([ecc1eae](https://github.com/revanced/revanced-patches/commit/ecc1eaef4daa7dfecb7913c378188b2b4685e610)) + +# [2.131.0](https://github.com/revanced/revanced-patches/compare/v2.130.0...v2.131.0) (2022-11-30) + + +### Features + +* **twitch/block-embedded-ads:** `block-embedded-ads` patch support ([#1160](https://github.com/revanced/revanced-patches/issues/1160)) ([641d33f](https://github.com/revanced/revanced-patches/commit/641d33fc7e213031124491bb468fb382267769aa)) + +# [2.130.0](https://github.com/revanced/revanced-patches/compare/v2.129.0...v2.130.0) (2022-11-29) + + +### Features + +* **youtube/enable-wide-searchbar:** bump compatibility to v17.45.36 ([66ce54c](https://github.com/revanced/revanced-patches/commit/66ce54c9921e6fbf5bbe52d73b0182352181f2b8)) + +# [2.129.0](https://github.com/revanced/revanced-patches/compare/v2.128.0...v2.129.0) (2022-11-28) + + +### Features + +* **youtube/hide-create-button:** bump compatibility to v17.45.36 ([751f604](https://github.com/revanced/revanced-patches/commit/751f6047ba1589593d881ad523727d00dfb2609b)) +* **youtube/hide-shorts-button:** bump compatibility to v17.45.36 ([51bfa7a](https://github.com/revanced/revanced-patches/commit/51bfa7afd1720d958847a44ae636de722311a199)) + +# [2.128.0](https://github.com/revanced/revanced-patches/compare/v2.127.0...v2.128.0) (2022-11-28) + + +### Features + +* **twitch:** settings for patches ([#1142](https://github.com/revanced/revanced-patches/issues/1142)) ([ed56f94](https://github.com/revanced/revanced-patches/commit/ed56f94f12c3bee46ab370b2e3b7890d1181d53d)) + +# [2.127.0](https://github.com/revanced/revanced-patches/compare/v2.126.1...v2.127.0) (2022-11-28) + + +### Features + +* **twitch:** `settings` patch ([#1075](https://github.com/revanced/revanced-patches/issues/1075)) ([6e95b86](https://github.com/revanced/revanced-patches/commit/6e95b86e50cb09b60d82b456d4650218436ed154)) + +## [2.126.1](https://github.com/revanced/revanced-patches/compare/v2.126.0...v2.126.1) (2022-11-28) + + +### Bug Fixes + +* **youtube/custom-video-speed:** fuzzy scan on fingerprint ([#1135](https://github.com/revanced/revanced-patches/issues/1135)) ([7b2cf56](https://github.com/revanced/revanced-patches/commit/7b2cf5698b85f7e2a901f6085c53d042660dc5c7)) + +# [2.126.0](https://github.com/revanced/revanced-patches/compare/v2.125.0...v2.126.0) (2022-11-28) + + +### Features + +* **backdrops:** `pro-unlock` patch ([#1121](https://github.com/revanced/revanced-patches/issues/1121)) ([76b5449](https://github.com/revanced/revanced-patches/commit/76b5449c61c97e8ad9f87fb7173c9819815744ba)) + +# [2.125.0](https://github.com/revanced/revanced-patches/compare/v2.124.0...v2.125.0) (2022-11-26) + + +### Features + +* **myexpenses:** `unlock-pro` patch ([#1120](https://github.com/revanced/revanced-patches/issues/1120)) ([e1a327a](https://github.com/revanced/revanced-patches/commit/e1a327a15d4289d9708546cc40f423597f22a66d)) + +# [2.124.0](https://github.com/revanced/revanced-patches/compare/v2.123.0...v2.124.0) (2022-11-26) + + +### Features + +* **citra:** `premium-unlock` patch ([#1115](https://github.com/revanced/revanced-patches/issues/1115)) ([86a3def](https://github.com/revanced/revanced-patches/commit/86a3def0f63a0529e44302472afc03cb0c8d566b)) +* **crunchyroll:** `enable-downloads` patch ([#1119](https://github.com/revanced/revanced-patches/issues/1119)) ([84d7bfd](https://github.com/revanced/revanced-patches/commit/84d7bfdbf8a6bfc25f52653a1b243f1291eb0da9)) + +# [2.123.0](https://github.com/revanced/revanced-patches/compare/v2.122.0...v2.123.0) (2022-11-26) + + +### Features + +* **windyapp:** `unlock-pro` patch ([#1118](https://github.com/revanced/revanced-patches/issues/1118)) ([d866634](https://github.com/revanced/revanced-patches/commit/d866634f83976df44b26848e0b70e9952d5d06ae)) + +# [2.122.0](https://github.com/revanced/revanced-patches/compare/v2.121.0...v2.122.0) (2022-11-25) + + +### Features + +* **tiktok:** `sim-spoof` patch ([#1088](https://github.com/revanced/revanced-patches/issues/1088)) ([53bb547](https://github.com/revanced/revanced-patches/commit/53bb547cc1003859f1d42db819a897e120b17fbe)) + +# [2.121.0](https://github.com/revanced/revanced-patches/compare/v2.120.0...v2.121.0) (2022-11-23) + + +### Features + +* **anytracker:** remove `unlock-premium` patch ([#1094](https://github.com/revanced/revanced-patches/issues/1094)) ([c3818be](https://github.com/revanced/revanced-patches/commit/c3818befc6e342ff04bc8e9fde84a667c123d7ab)) + +# [2.120.0](https://github.com/revanced/revanced-patches/compare/v2.119.0...v2.120.0) (2022-11-23) + + +### Features + +* **youtube:** `remove-player-button-background` patch ([#855](https://github.com/revanced/revanced-patches/issues/855)) ([9e14817](https://github.com/revanced/revanced-patches/commit/9e14817fd2ef29b9f3ec161d74ab972ba19066b0)) + +# [2.119.0](https://github.com/revanced/revanced-patches/compare/v2.118.1...v2.119.0) (2022-11-23) + + +### Features + +* **twitch:** ˋintegrationsˋ dependency patch ([#1074](https://github.com/revanced/revanced-patches/issues/1074)) ([a84ca7e](https://github.com/revanced/revanced-patches/commit/a84ca7ec04389c3665358a7d239c615214fd327d)) + +## [2.118.1](https://github.com/revanced/revanced-patches/compare/v2.118.0...v2.118.1) (2022-11-22) + +# [2.118.0](https://github.com/revanced/revanced-patches/compare/v2.117.1...v2.118.0) (2022-11-22) + + +### Features + +* **youtube:** bump patches compatibility to v17.45.36 ([ab7dfd8](https://github.com/revanced/revanced-patches/commit/ab7dfd8ee1d902095811ed91b80aa0ed362ee9c5)) + +## [2.117.1](https://github.com/revanced/revanced-patches/compare/v2.117.0...v2.117.1) (2022-11-22) + + +### Bug Fixes + +* **youtube/general-ads:** hide reels shelf ([a436663](https://github.com/revanced/revanced-patches/commit/a436663e7755fc714c735df626d39bbd94f83dbb)) + +# [2.117.0](https://github.com/revanced/revanced-patches/compare/v2.116.0...v2.117.0) (2022-11-21) + + +### Features + +* **tiktok:** `tiktok-web-login` patch ([#593](https://github.com/revanced/revanced-patches/issues/593)) ([233e578](https://github.com/revanced/revanced-patches/commit/233e578b4d1cd5e783369e0c855e8246d158f8ed)) + +# [2.116.0](https://github.com/revanced/revanced-patches/compare/v2.115.0...v2.116.0) (2022-11-21) + + +### Features + +* **youtube/theme:** make amoled the default color for dark theme background" ([2b68ac7](https://github.com/revanced/revanced-patches/commit/2b68ac7796e54ed07c697909a84b3847f4ed7f01)) + +# [2.115.0](https://github.com/revanced/revanced-patches/compare/v2.114.2...v2.115.0) (2022-11-21) + + +### Features + +* **youtube/general-ads:** normalize switch names ([7636180](https://github.com/revanced/revanced-patches/commit/763618002ab6ccd9cd23889275c0e9b23642763e)) +* **youtube/theme:** exclude the patch by default ([57358ed](https://github.com/revanced/revanced-patches/commit/57358edc4cf3ae6b05cfb51359f66fc94c71cb5c)) +* **youtube/theme:** make dark the default color for dark theme background ([db44fa6](https://github.com/revanced/revanced-patches/commit/db44fa6f5adad19384970dad58784b2bf5fe1007)) + +## [2.114.2](https://github.com/revanced/revanced-patches/compare/v2.114.1...v2.114.2) (2022-11-20) + + +### Bug Fixes + +* **youtube/general-ads:** hide bytecode home ad view ([4976ad4](https://github.com/revanced/revanced-patches/commit/4976ad44b1867c93e976d91075b5bcead8d69c90)) +* **youtube/general-ads:** remove unused switch ([43cddcc](https://github.com/revanced/revanced-patches/commit/43cddcc85830524cd88d87ae1178f11451f158d8)) + +## [2.114.1](https://github.com/revanced/revanced-patches/compare/v2.114.0...v2.114.1) (2022-11-20) + + +### Bug Fixes + +* **tiktok/settings:** make compatible with newer versions ([#1057](https://github.com/revanced/revanced-patches/issues/1057)) ([e1d5d64](https://github.com/revanced/revanced-patches/commit/e1d5d6492ed660f43c87c796a59e766ea6b8ead0)) + +# [2.114.0](https://github.com/revanced/revanced-patches/compare/v2.113.0...v2.114.0) (2022-11-20) + + +### Features + +* **youtube:** `disable-zoom-haptics` patch ([#1079](https://github.com/revanced/revanced-patches/issues/1079)) ([a7cfba5](https://github.com/revanced/revanced-patches/commit/a7cfba54fbb3ee0ee5511a45d0b3c4620eb0c861)) + +# [2.113.0](https://github.com/revanced/revanced-patches/compare/v2.112.0...v2.113.0) (2022-11-19) + + +### Bug Fixes + +* **youtube/general-ads:** hide ads with buttons ([97fd3fd](https://github.com/revanced/revanced-patches/commit/97fd3fda79ff4bb2fadd0e6c4aa80b96d71f46da)) + + +### Features + +* **youtube/general-ads:** custom component filter ([b5e81d0](https://github.com/revanced/revanced-patches/commit/b5e81d0eaff3ab67a59ccd45eac6bf9414ed1380)) +* **youtube/general-ads:** hide gray separators ([59693f0](https://github.com/revanced/revanced-patches/commit/59693f007192c195258cea3a222d7fb94b6385c2)) + +# [2.112.0](https://github.com/revanced/revanced-patches/compare/v2.111.3...v2.112.0) (2022-11-19) + + +### Features + +* **anytracker:** `unlock-premium` patch ([#1076](https://github.com/revanced/revanced-patches/issues/1076)) ([52b3d90](https://github.com/revanced/revanced-patches/commit/52b3d909bbe59dafaea0cb9da1c27bdd0cda502f)) + +## [2.111.3](https://github.com/revanced/revanced-patches/compare/v2.111.2...v2.111.3) (2022-11-18) + + +### Bug Fixes + +* **youtube/litho-filter:** use correct type for switch case ([#1068](https://github.com/revanced/revanced-patches/issues/1068)) ([ab03511](https://github.com/revanced/revanced-patches/commit/ab03511e23d07c7c40b58eae5791fb2a798289de)) + +## [2.111.2](https://github.com/revanced/revanced-patches/compare/v2.111.1...v2.111.2) (2022-11-18) + + +### Bug Fixes + +* **youtube/general-ads:** import correct compatibility annotation ([#1065](https://github.com/revanced/revanced-patches/issues/1065)) ([db2085c](https://github.com/revanced/revanced-patches/commit/db2085c217311a8b7d45a01c03c6162bc60a9298)) + +## [2.111.1](https://github.com/revanced/revanced-patches/compare/v2.111.0...v2.111.1) (2022-11-18) + +# [2.111.0](https://github.com/revanced/revanced-patches/compare/v2.110.0...v2.111.0) (2022-11-15) + + +### Features + +* **iconpackstudio:** `unlock-pro` patch ([#1047](https://github.com/revanced/revanced-patches/issues/1047)) ([fcbca9c](https://github.com/revanced/revanced-patches/commit/fcbca9cf0ff3940c48df53a9e5e4d06d65f9a1b1)) + +# [2.110.0](https://github.com/revanced/revanced-patches/compare/v2.109.0...v2.110.0) (2022-11-15) + + +### Features + +* **youtube/debugging:** include by default & add option to debug on Android ([9d6d5b2](https://github.com/revanced/revanced-patches/commit/9d6d5b29227980aacb4c1331161afb3fa265bbe4)) +* **youtube/debugging:** use more suitable preference title ([d182351](https://github.com/revanced/revanced-patches/commit/d182351e789bcbfb7982774406449e8d973765b2)) +* **youtube/general-ads:** hide chapter teaser under videos ([9cf415a](https://github.com/revanced/revanced-patches/commit/9cf415a894ab5458134ef1cd5a4c5bf7579e592c)) +* **youtube/general-ads:** hide self sponsored cards ([b627d75](https://github.com/revanced/revanced-patches/commit/b627d751b49c228e52f7f9fb5d2bcacb9d243a64)) +* **youtube/general-ads:** hide shorts in search ([c01ba5c](https://github.com/revanced/revanced-patches/commit/c01ba5cbcc694f8c9ee83a4e9eb081cf5b45753c)) + +# [2.109.0](https://github.com/revanced/revanced-patches/compare/v2.108.0...v2.109.0) (2022-11-13) + + +### Features + +* **twitch:** `block-audio-ads` patch ([#1041](https://github.com/revanced/revanced-patches/issues/1041)) ([3419bae](https://github.com/revanced/revanced-patches/commit/3419baed489ce231f84b3e2d600cf123cdfa7ff7)) +* **twitch:** `block-video-ads` patch ([#1040](https://github.com/revanced/revanced-patches/issues/1040)) ([b47bf24](https://github.com/revanced/revanced-patches/commit/b47bf24d1b52cfa1883a942894a6216ae81b8325)) + +# [2.108.0](https://github.com/revanced/revanced-patches/compare/v2.107.0...v2.108.0) (2022-11-13) + + +### Features + +* **twitch:** `debug-mode` patch ([#1031](https://github.com/revanced/revanced-patches/issues/1031)) ([c514860](https://github.com/revanced/revanced-patches/commit/c514860bc4a46295b11f09df9ffc1c556b798eb3)) + +# [2.107.0](https://github.com/revanced/revanced-patches/compare/v2.106.1...v2.107.0) (2022-11-13) + + +### Features + +* **ticktick:** `unlock-themes` patch ([#1028](https://github.com/revanced/revanced-patches/issues/1028)) ([7f1fedc](https://github.com/revanced/revanced-patches/commit/7f1fedcddd4330a5f884b813a20f2f8e84e2c9da)) +* **twitch/show-deleted-messages:** `show-deleted-messages` patch ([#1030](https://github.com/revanced/revanced-patches/issues/1030)) ([7e6b453](https://github.com/revanced/revanced-patches/commit/7e6b4534013a03ddc7eb11a1f911fa0564372118)) + +## [2.106.1](https://github.com/revanced/revanced-patches/compare/v2.106.0...v2.106.1) (2022-11-11) + + +### Bug Fixes + +* **youtube/custom-branding:** use high resolution icons ([#1018](https://github.com/revanced/revanced-patches/issues/1018)) ([1c9d1ac](https://github.com/revanced/revanced-patches/commit/1c9d1acf2b7aff4cd52d17009ff01246f74d2214)) + +# [2.106.0](https://github.com/revanced/revanced-patches/compare/v2.105.0...v2.106.0) (2022-11-09) + + +### Features + +* **youtube:** `open-links-directly` patch ([#999](https://github.com/revanced/revanced-patches/issues/999)) ([bf85f62](https://github.com/revanced/revanced-patches/commit/bf85f62f30242f91bdaf53b44ae6a9a2b3b6e1b4)) + +# [2.105.0](https://github.com/revanced/revanced-patches/compare/v2.104.2...v2.105.0) (2022-11-09) + + +### Features + +* **nyx:** `unlock-pro` patch ([#1004](https://github.com/revanced/revanced-patches/issues/1004)) ([9dd8239](https://github.com/revanced/revanced-patches/commit/9dd82391f3f97247224e09e0143b2272a3d47053)) + +## [2.104.2](https://github.com/revanced/revanced-patches/compare/v2.104.1...v2.104.2) (2022-11-08) + + +### Bug Fixes + +* **youtube/hide-endscreen-cards:** restore functionality ([#993](https://github.com/revanced/revanced-patches/issues/993)) ([eb1bae2](https://github.com/revanced/revanced-patches/commit/eb1bae2c55e65ad29030dce6746a18662dd0fe25)) + +## [2.104.1](https://github.com/revanced/revanced-patches/compare/v2.104.0...v2.104.1) (2022-11-07) + + +### Bug Fixes + +* **youtube/general-ads:** incorrect description for info panels ([77b029e](https://github.com/revanced/revanced-patches/commit/77b029e82e481a13516b1c8a888c42817507cdea)) +* **youtube/hide-info-cards:** remove initial popup of info-cards ([#992](https://github.com/revanced/revanced-patches/issues/992)) ([19f3f03](https://github.com/revanced/revanced-patches/commit/19f3f038585c313a969adf3d4095a60ab4c83ede)) + +# [2.104.0](https://github.com/revanced/revanced-patches/compare/v2.103.0...v2.104.0) (2022-11-06) + + +### Features + +* **hexeditor:** `disable-ads` patch ([#973](https://github.com/revanced/revanced-patches/issues/973)) ([a48e5fd](https://github.com/revanced/revanced-patches/commit/a48e5fd50dcf9ee061ffd5c5ed0b997067f40652)) + +# [2.103.0](https://github.com/revanced/revanced-patches/compare/v2.102.1...v2.103.0) (2022-11-05) + + +### Bug Fixes + +* apply multiple changes from integrations, refactor package structure, class names and implementations ([e756fac](https://github.com/revanced/revanced-patches/commit/e756face362e4abf2c7b17afa536feed9b0cd631)) +* **youtube/video-information:** use correct compatibility annotation ([b42e048](https://github.com/revanced/revanced-patches/commit/b42e04884bb9a60c3df30b668bfba55b1cf8eaa4)) + + +### Features + +* **debugging:** simplify patch name and description ([61762f7](https://github.com/revanced/revanced-patches/commit/61762f76c457f348ef599e146f3853e941d4c45c)) + +## [2.102.1](https://github.com/revanced/revanced-patches/compare/v2.102.0...v2.102.1) (2022-11-04) + + +### Bug Fixes + +* use original app name ([#977](https://github.com/revanced/revanced-patches/issues/977)) ([ffc3c08](https://github.com/revanced/revanced-patches/commit/ffc3c08d96734893121397d506a97bd50b06bc1d)) + +# [2.102.0](https://github.com/revanced/revanced-patches/compare/v2.101.0...v2.102.0) (2022-11-03) + + +### Features + +* **youtube-music:** bump patches compatibility to v5.31.50 ([#962](https://github.com/revanced/revanced-patches/issues/962)) ([476fcc8](https://github.com/revanced/revanced-patches/commit/476fcc87ca0d33cfbf251e4307755f02ed8f8baa)) + +# [2.101.0](https://github.com/revanced/revanced-patches/compare/v2.100.3...v2.101.0) (2022-11-01) + + +### Features + +* **youtube:** bump patches compatibility to v17.43.36 ([#924](https://github.com/revanced/revanced-patches/issues/924)) ([3867514](https://github.com/revanced/revanced-patches/commit/38675144dcb616474a047bcf8f7e1bf1f668ea46)) + +## [2.100.3](https://github.com/revanced/revanced-patches/compare/v2.100.2...v2.100.3) (2022-11-01) + + +### Bug Fixes + +* **youtube/video-ads:** add switch to temporarily fix buffering issues ([7aa3bce](https://github.com/revanced/revanced-patches/commit/7aa3bce6ccd669a66de10ef6ffe2151f27b40365)) + +## [2.100.2](https://github.com/revanced/revanced-patches/compare/v2.100.1...v2.100.2) (2022-10-31) + + +### Bug Fixes + +* **youtube/theme-patch:** respect app specific theme ([#946](https://github.com/revanced/revanced-patches/issues/946)) ([fe8d6c7](https://github.com/revanced/revanced-patches/commit/fe8d6c7b2c10be5cca0717b6a03cfa1bef10e2c0)) + +## [2.100.1](https://github.com/revanced/revanced-patches/compare/v2.100.0...v2.100.1) (2022-10-31) + +# [2.100.0](https://github.com/revanced/revanced-patches/compare/v2.99.0...v2.100.0) (2022-10-30) + + +### Features + +* `hide-watch-in-vr` patch ([#911](https://github.com/revanced/revanced-patches/issues/911)) ([8ea0b20](https://github.com/revanced/revanced-patches/commit/8ea0b20e96df53211039df0468b2d4a735a381f1)) + +# [2.99.0](https://github.com/revanced/revanced-patches/compare/v2.98.0...v2.99.0) (2022-10-29) + + +### Features + +* **youtube/sponsorblock:** skip segments once automatically ([#907](https://github.com/revanced/revanced-patches/issues/907)) ([afc7588](https://github.com/revanced/revanced-patches/commit/afc75882062a9316dbc28a9dc4f990cf88b52654)) + +# [2.98.0](https://github.com/revanced/revanced-patches/compare/v2.97.0...v2.98.0) (2022-10-29) + + +### Features + +* **youtube/comments:** hide shorts comments button ([#866](https://github.com/revanced/revanced-patches/issues/866)) ([c78a74e](https://github.com/revanced/revanced-patches/commit/c78a74e21b4521034f44e9bfd226c5a362c21e6c)) + +# [2.97.0](https://github.com/revanced/revanced-patches/compare/v2.96.0...v2.97.0) (2022-10-29) + + +### Features + +* **youtube/microg-support:** handle availability of Vanced MicroG ([bfd0d14](https://github.com/revanced/revanced-patches/commit/bfd0d1418fb68f1d37687ec2072d3b64a9c3c6ee)) + +# [2.96.0](https://github.com/revanced/revanced-patches/compare/v2.95.0...v2.96.0) (2022-10-28) + + +### Features + +* **youtube/return-youtube-dislike:** compatibility for old and new button layout ([6629bd7](https://github.com/revanced/revanced-patches/commit/6629bd71716a753be06617724e536b8ab4c3b69a)) + +# [2.95.0](https://github.com/revanced/revanced-patches/compare/v2.94.0...v2.95.0) (2022-10-28) + + +### Features + +* **youtube:** bump patches compatibility to v17.41.37 ([#888](https://github.com/revanced/revanced-patches/issues/888)) ([11ed0c0](https://github.com/revanced/revanced-patches/commit/11ed0c0fb3236d87284806d2fa957699e908cc61)) + +# [2.94.0](https://github.com/revanced/revanced-patches/compare/v2.93.0...v2.94.0) (2022-10-28) + + +### Features + +* remove compatibility for YouTube v17.41.37 ([#886](https://github.com/revanced/revanced-patches/issues/886)) ([0f4bc19](https://github.com/revanced/revanced-patches/commit/0f4bc197455574b84dd1cb1a43f03af90858bc39)) + +# [2.93.0](https://github.com/revanced/revanced-patches/compare/v2.92.3...v2.93.0) (2022-10-27) + + +### Features + +* bump YouTube patches to v17.41.37 ([#878](https://github.com/revanced/revanced-patches/issues/878)) ([9072986](https://github.com/revanced/revanced-patches/commit/9072986f99e624386ff51c7eeb1d65158bd9249a)) + +## [2.92.3](https://github.com/revanced/revanced-patches/compare/v2.92.2...v2.92.3) (2022-10-27) + + +### Bug Fixes + +* **youtube:** resolve fingerprints on mutable methods ([10e4af3](https://github.com/revanced/revanced-patches/commit/10e4af386652d51c621519f7a67aac8c22ed8c18)) + +## [2.92.2](https://github.com/revanced/revanced-patches/compare/v2.92.1...v2.92.2) (2022-10-27) + + +### Bug Fixes + +* **youtube/theme:** theme missing gray color ([#873](https://github.com/revanced/revanced-patches/issues/873)) ([5267d56](https://github.com/revanced/revanced-patches/commit/5267d56a05b4053d556171ffd2d2870f3f932e8e)) + +## [2.92.1](https://github.com/revanced/revanced-patches/compare/v2.92.0...v2.92.1) (2022-10-26) + + +### Bug Fixes + +* **youtube/custom-branding:** use proper scaled icons ([24b5bcd](https://github.com/revanced/revanced-patches/commit/24b5bcdd703474c940fd436a37f0ae924d8b8c74)) + +# [2.92.0](https://github.com/revanced/revanced-patches/compare/v2.91.0...v2.92.0) (2022-10-26) + + +### Features + +* **youtube/hide-mix-playlists:** hide in video suggestions ([#854](https://github.com/revanced/revanced-patches/issues/854)) ([acde5f0](https://github.com/revanced/revanced-patches/commit/acde5f066053ca3d58ca09fc2a6a1381ba27d84b)) + +# [2.91.0](https://github.com/revanced/revanced-patches/compare/v2.90.0...v2.91.0) (2022-10-26) + + +### Features + +* remove partially complete patch ([d212e19](https://github.com/revanced/revanced-patches/commit/d212e19c3276c2ba6550f03dfd9bba47b7773524)) + +# [2.90.0](https://github.com/revanced/revanced-patches/compare/v2.89.0...v2.90.0) (2022-10-25) + + +### Features + +* `comment` patch ([#858](https://github.com/revanced/revanced-patches/issues/858)) ([472fb4f](https://github.com/revanced/revanced-patches/commit/472fb4f3747c835be2c5069a0f65017ab42f8d7e)) + +# [2.89.0](https://github.com/revanced/revanced-patches/compare/v2.88.0...v2.89.0) (2022-10-25) + + +### Features + +* `hide-album-cards` patch ([#857](https://github.com/revanced/revanced-patches/issues/857)) ([bce9d0c](https://github.com/revanced/revanced-patches/commit/bce9d0c9405d7afd5c51c2a9c2c247f3ff7581f5)) + +# [2.88.0](https://github.com/revanced/revanced-patches/compare/v2.87.0...v2.88.0) (2022-10-25) + + +### Features + +* `hide-artist-card` patch ([#859](https://github.com/revanced/revanced-patches/issues/859)) ([1f76246](https://github.com/revanced/revanced-patches/commit/1f7624680b86e3a7afd45521bfef86f88f52c976)) + +# [2.87.0](https://github.com/revanced/revanced-patches/compare/v2.86.0...v2.87.0) (2022-10-25) + + +### Bug Fixes + +* **metanav/fix-scaling:** use semantic versioning in package versions ([a9445a8](https://github.com/revanced/revanced-patches/commit/a9445a823e3a4885764cea9d08b51a1584d3238f)) +* **youtube/theme:** theme litho ui components & use correct theme for settings ([#791](https://github.com/revanced/revanced-patches/issues/791)) ([91c03c5](https://github.com/revanced/revanced-patches/commit/91c03c5624ca28ac13ee761261dea423f0ac42d7)) + + +### Features + +* `fix-metanav-scaling` patch ([#831](https://github.com/revanced/revanced-patches/issues/831)) ([4808e09](https://github.com/revanced/revanced-patches/commit/4808e099856e50a6f7e66834a92e2210cc84c8bc)) +* `hide-crowdfunding-box` patch ([#856](https://github.com/revanced/revanced-patches/issues/856)) ([3704ce2](https://github.com/revanced/revanced-patches/commit/3704ce22dbbff02b2e2d6dbdf9a74254a2511d3c)) + +# [2.86.0](https://github.com/revanced/revanced-patches/compare/v2.85.2...v2.86.0) (2022-10-25) + + +### Features + +* **youtube/theme:** extend theming to splash screen ([#769](https://github.com/revanced/revanced-patches/issues/769)) ([f7bb937](https://github.com/revanced/revanced-patches/commit/f7bb937ef2374d1042ea3772f03627d7f0111b78)) + +## [2.85.2](https://github.com/revanced/revanced-patches/compare/v2.85.1...v2.85.2) (2022-10-22) + + +### Bug Fixes + +* **youtube/integrations:** set context for remaining activities ([#828](https://github.com/revanced/revanced-patches/issues/828)) ([b2b6a3d](https://github.com/revanced/revanced-patches/commit/b2b6a3d1492bc2d5a6e27c68c74e3904764dda4b)) + +## [2.85.1](https://github.com/revanced/revanced-patches/compare/v2.85.0...v2.85.1) (2022-10-21) + + +### Bug Fixes + +* **youtube/theme:** extend dark mode theming ([#827](https://github.com/revanced/revanced-patches/issues/827)) ([6d803c5](https://github.com/revanced/revanced-patches/commit/6d803c5386e1f6f6fb6a7fa0a9f0bb81ee1022ac)) + +# [2.85.0](https://github.com/revanced/revanced-patches/compare/v2.84.1...v2.85.0) (2022-10-20) + + +### Features + +* **youtube:** `hide-captions-button` patch ([#770](https://github.com/revanced/revanced-patches/issues/770)) ([478af6a](https://github.com/revanced/revanced-patches/commit/478af6ad54ffdf6f99abab35a84a4f50b59d28c8)) + +## [2.84.1](https://github.com/revanced/revanced-patches/compare/v2.84.0...v2.84.1) (2022-10-20) + + +### Bug Fixes + +* **youtube/hide-mix-playlists:** correct switch title, summary and defaults ([#821](https://github.com/revanced/revanced-patches/issues/821)) ([f9eea33](https://github.com/revanced/revanced-patches/commit/f9eea332cea0b8c3d5c3dfff645b60861e925b5a)) + +# [2.84.0](https://github.com/revanced/revanced-patches/compare/v2.83.3...v2.84.0) (2022-10-20) + + +### Features + +* **youtube:** `hide-mix-playlists` patch ([#816](https://github.com/revanced/revanced-patches/issues/816)) ([e76c73b](https://github.com/revanced/revanced-patches/commit/e76c73b455c65aa605e418f19d88b64e0e244901)) + +## [2.83.3](https://github.com/revanced/revanced-patches/compare/v2.83.2...v2.83.3) (2022-10-20) + + +### Bug Fixes + +* **youtube/video-ads:** revert faulty method patch ([#820](https://github.com/revanced/revanced-patches/issues/820)) ([666248a](https://github.com/revanced/revanced-patches/commit/666248ae65d1ee84d1a278714c0dcd2528c5dd14)) + +## [2.83.2](https://github.com/revanced/revanced-patches/compare/v2.83.1...v2.83.2) (2022-10-19) + + +### Bug Fixes + +* **youtube/video-ads:** add back initial method to block ads ([#818](https://github.com/revanced/revanced-patches/issues/818)) ([8b02acd](https://github.com/revanced/revanced-patches/commit/8b02acd3b33c4702d81ec46dccb35604ec5fbe02)) + +## [2.83.1](https://github.com/revanced/revanced-patches/compare/v2.83.0...v2.83.1) (2022-10-19) + + +### Bug Fixes + +* **youtube/video-ads:** block earlier in call hirarchy ([#813](https://github.com/revanced/revanced-patches/issues/813)) ([f9517d2](https://github.com/revanced/revanced-patches/commit/f9517d2058c7561cf89b59ed36f690417dc076f7)) + +# [2.83.0](https://github.com/revanced/revanced-patches/compare/v2.82.1...v2.83.0) (2022-10-18) + + +### Features + +* bump YouTube Music patches compatibility ([#809](https://github.com/revanced/revanced-patches/issues/809)) ([09f2d8e](https://github.com/revanced/revanced-patches/commit/09f2d8e14937b582bc4bfb7d3a8bb2b7622f772d)) + +## [2.82.1](https://github.com/revanced/revanced-patches/compare/v2.82.0...v2.82.1) (2022-10-17) + + +### Bug Fixes + +* **youtube/video-ads:** return empty ad list earlier ([a47ce9b](https://github.com/revanced/revanced-patches/commit/a47ce9ba4f75673de23eb6054f5652224a48f43e)) + +# [2.82.0](https://github.com/revanced/revanced-patches/compare/v2.81.11...v2.82.0) (2022-10-13) + + +### Features + +* **youtube/remember-video-quality:** remember the quality until changed by default ([1aebabe](https://github.com/revanced/revanced-patches/commit/1aebabefdb0b163e0020fd3b52d7d1fa2404764b)) + +## [2.81.11](https://github.com/revanced/revanced-patches/compare/v2.81.10...v2.81.11) (2022-10-11) + + +### Bug Fixes + +* **youtube/theme:** add missing theme condition check ([#771](https://github.com/revanced/revanced-patches/issues/771)) ([40b70b9](https://github.com/revanced/revanced-patches/commit/40b70b9f9a24cfbf76bcd69f562b87e5cfade66f)) + +## [2.81.10](https://github.com/revanced/revanced-patches/compare/v2.81.9...v2.81.10) (2022-10-11) + + +### Bug Fixes + +* **spotify/disable-capture-restriction:** dynamically find indices ([#759](https://github.com/revanced/revanced-patches/issues/759)) ([2cc64c6](https://github.com/revanced/revanced-patches/commit/2cc64c61d1bc18a1717354085ddc60a251173837)) + +## [2.81.9](https://github.com/revanced/revanced-patches/compare/v2.81.8...v2.81.9) (2022-10-11) + + +### Bug Fixes + +* **youtube/general-ads:** hide ads on wide screens ([#765](https://github.com/revanced/revanced-patches/issues/765)) ([f0d2f3e](https://github.com/revanced/revanced-patches/commit/f0d2f3e01b90bbe6d2b55b941eaf96be2295cd3c)) +* **youtube/theme:** colore the comment action bar ([#762](https://github.com/revanced/revanced-patches/issues/762)) ([4b3abe0](https://github.com/revanced/revanced-patches/commit/4b3abe0d45ac8c0c5ae6cf4955681ba76ce10309)) + +## [2.81.8](https://github.com/revanced/revanced-patches/compare/v2.81.7...v2.81.8) (2022-10-10) + + +### Bug Fixes + +* **youtube/video-ads:** invert condition to hide ads ([e353ed0](https://github.com/revanced/revanced-patches/commit/e353ed0b0fe78feae65f1ad2f6567c1e9aa9c71d)) + +## [2.81.7](https://github.com/revanced/revanced-patches/compare/v2.81.6...v2.81.7) (2022-10-10) + + +### Bug Fixes + +* **video-ads:** invert the condition to hide ads ([a32af95](https://github.com/revanced/revanced-patches/commit/a32af951566a7187ae2618ef08f9298f78f8ea8c)) + +## [2.81.6](https://github.com/revanced/revanced-patches/compare/v2.81.5...v2.81.6) (2022-10-10) + + +### Bug Fixes + +* **video-ads:** return empty list in correct method ([bbcf050](https://github.com/revanced/revanced-patches/commit/bbcf0501d4be0c43eddb7a9b3c32394222efc8f0)) + +## [2.81.5](https://github.com/revanced/revanced-patches/compare/v2.81.4...v2.81.5) (2022-10-10) + + +### Bug Fixes + +* **video-ads:** clobber unused register ([0feb607](https://github.com/revanced/revanced-patches/commit/0feb6073dd3de5dc179ffa9fde2da24439986431)) + +## [2.81.4](https://github.com/revanced/revanced-patches/compare/v2.81.3...v2.81.4) (2022-10-10) + + +### Bug Fixes + +* **general-ads:** invalid smali syntax ([86224c1](https://github.com/revanced/revanced-patches/commit/86224c1f601c1bd22c0ce4dcf2a8e79ea3b8b2ae)) + +## [2.81.3](https://github.com/revanced/revanced-patches/compare/v2.81.2...v2.81.3) (2022-10-10) + + +### Bug Fixes + +* **video-ads:** return empty ad list instead of null ([f6c5027](https://github.com/revanced/revanced-patches/commit/f6c5027981a2b55bbcd2e55d294ebfd5e6f0b527)) + +## [2.81.2](https://github.com/revanced/revanced-patches/compare/v2.81.1...v2.81.2) (2022-10-08) + + +### Bug Fixes + +* **theme:** include coloring the playlist action bar ([#742](https://github.com/revanced/revanced-patches/issues/742)) ([6766ec8](https://github.com/revanced/revanced-patches/commit/6766ec8f31da6dd71fff0ae4b7044af5ec4a5dfd)) + +## [2.81.1](https://github.com/revanced/revanced-patches/compare/v2.81.0...v2.81.1) (2022-10-07) + + +### Bug Fixes + +* **video-ads:** block remaining video ads ([fbc83a2](https://github.com/revanced/revanced-patches/commit/fbc83a22d37012394311a87d27202b7cc86e047d)) + +# [2.81.0](https://github.com/revanced/revanced-patches/compare/v2.80.0...v2.81.0) (2022-10-06) + + +### Features + +* **youtube/general-ads:** do not hide community posts by default ([6269638](https://github.com/revanced/revanced-patches/commit/626963819c183cbfeca4ff4ab0a16fc39ed9d8a8)) + +# [2.80.0](https://github.com/revanced/revanced-patches/compare/v2.79.2...v2.80.0) (2022-10-04) + + +### Features + +* **youtube/remember-video-quality:** remember current quality option ([9c510d4](https://github.com/revanced/revanced-patches/commit/9c510d472048f5bce127eb0329e53aaf259e9b1b)) + +## [2.79.2](https://github.com/revanced/revanced-patches/compare/v2.79.1...v2.79.2) (2022-10-04) + + +### Bug Fixes + +* **youtube/disable-startup-shorts-player:** don't affect functionality of navigation bar ([#716](https://github.com/revanced/revanced-patches/issues/716)) ([05f5cc1](https://github.com/revanced/revanced-patches/commit/05f5cc17e3fcbf255c6fe5b503c9fd20c218f5a1)) + +## [2.79.1](https://github.com/revanced/revanced-patches/compare/v2.79.0...v2.79.1) (2022-10-03) + + +### Bug Fixes + +* **youtube/disable-startup-shorts-player:** do not prevent playing videos on startup ([#714](https://github.com/revanced/revanced-patches/issues/714)) ([9a0d379](https://github.com/revanced/revanced-patches/commit/9a0d3798afd75bba5c63e1b8803dc25814586be9)) + +# [2.79.0](https://github.com/revanced/revanced-patches/compare/v2.78.0...v2.79.0) (2022-10-03) + + +### Features + +* `hide-video-buttons` patch ([584803a](https://github.com/revanced/revanced-patches/commit/584803aa900f94de1b2b2a0c41fb0884fa9270d3)) + +# [2.78.0](https://github.com/revanced/revanced-patches/compare/v2.77.1...v2.78.0) (2022-10-02) + + +### Features + +* **youtube/general-ads:** check for ad in identifier ([fd0506e](https://github.com/revanced/revanced-patches/commit/fd0506eb95dbf398eb7d6bfa8732f517b9867545)) + +## [2.77.1](https://github.com/revanced/revanced-patches/compare/v2.77.0...v2.77.1) (2022-10-01) + + +### Bug Fixes + +* **tiktok/tiktok-downloads:** add missing patch dependencies ([#686](https://github.com/revanced/revanced-patches/issues/686)) ([5c3b4ed](https://github.com/revanced/revanced-patches/commit/5c3b4ed0530e89d50db3b5bb9893e1ef4b463bae)) + +# [2.77.0](https://github.com/revanced/revanced-patches/compare/v2.76.3...v2.77.0) (2022-10-01) + + +### Features + +* **youtube/hdr-auto-brightness:** increase strength of fingerprint ([2379b58](https://github.com/revanced/revanced-patches/commit/2379b5892b3505c8780f475bbc56f520613fa31d)) + +## [2.76.3](https://github.com/revanced/revanced-patches/compare/v2.76.2...v2.76.3) (2022-10-01) + + +### Performance Improvements + +* **general-ads:** hook pathBuilder ([b9bad0b](https://github.com/revanced/revanced-patches/commit/b9bad0bb922bbd44ddf2acbfae87243beb28851a)) + +## [2.76.2](https://github.com/revanced/revanced-patches/compare/v2.76.1...v2.76.2) (2022-09-30) + + +### Bug Fixes + +* **twitter/monochrome-icon:** add missing `File.write` call ([#682](https://github.com/revanced/revanced-patches/issues/682)) ([de22e3f](https://github.com/revanced/revanced-patches/commit/de22e3f03ef1d6db08f9446f02e687721d9017d7)) + +## [2.76.1](https://github.com/revanced/revanced-patches/compare/v2.76.0...v2.76.1) (2022-09-30) + + +### Bug Fixes + +* **disable-startup-shorts-player:** remove redundant opcode pattern ([#679](https://github.com/revanced/revanced-patches/issues/679)) ([5197a24](https://github.com/revanced/revanced-patches/commit/5197a24cc5ecc1b51cdc5c00c77c873b86394994)) + +# [2.76.0](https://github.com/revanced/revanced-patches/compare/v2.75.2...v2.76.0) (2022-09-30) + + +### Features + +* bump YouTube Music patches compatibility to v5.26.52 ([#681](https://github.com/revanced/revanced-patches/issues/681)) ([f195734](https://github.com/revanced/revanced-patches/commit/f195734925dd41596d058f9cbf2b7e619a8d5833)) + +## [2.75.2](https://github.com/revanced/revanced-patches/compare/v2.75.1...v2.75.2) (2022-09-30) + + +### Bug Fixes + +* **twitter patches.:** use wider compatible Java API for writing to file ([#678](https://github.com/revanced/revanced-patches/issues/678)) ([4448de8](https://github.com/revanced/revanced-patches/commit/4448de80a6277862d81c7a29b9489e4fbec48496)) + +## [2.75.1](https://github.com/revanced/revanced-patches/compare/v2.75.0...v2.75.1) (2022-09-29) + + +### Bug Fixes + +* **disable-startup-shorts-player:** incorrect offsets, invert branch condition ([#672](https://github.com/revanced/revanced-patches/issues/672)) ([bfe1e38](https://github.com/revanced/revanced-patches/commit/bfe1e3808ece85fbb0785a4378ee95591115ac33)) + +# [2.75.0](https://github.com/revanced/revanced-patches/compare/v2.74.0...v2.75.0) (2022-09-29) + + +### Features + +* `disable-capture-restriction` patch ([#655](https://github.com/revanced/revanced-patches/issues/655)) ([3cc26c3](https://github.com/revanced/revanced-patches/commit/3cc26c31d378c27ca7f768f777daa00e3f849dff)) + +# [2.74.0](https://github.com/revanced/revanced-patches/compare/v2.73.0...v2.74.0) (2022-09-29) + + +### Features + +* `disable-startup-shorts-player` patch ([#670](https://github.com/revanced/revanced-patches/issues/670)) ([feb3bd0](https://github.com/revanced/revanced-patches/commit/feb3bd02aaf67379733d10988fd58b0c3924f88e)) + +# [2.73.0](https://github.com/revanced/revanced-patches/compare/v2.72.1...v2.73.0) (2022-09-29) + + +### Features + +* `dynamic-color` patch ([#652](https://github.com/revanced/revanced-patches/issues/652)) ([a16575b](https://github.com/revanced/revanced-patches/commit/a16575b984354138b9ab175307be8d15de60b6a6)) + +## [2.72.1](https://github.com/revanced/revanced-patches/compare/v2.72.0...v2.72.1) (2022-09-29) + + +### Bug Fixes + +* **hdr-brightness:** trim list of compatible versions ([#602](https://github.com/revanced/revanced-patches/issues/602)) ([3209d90](https://github.com/revanced/revanced-patches/commit/3209d902e4d1aa73b59ce059cf3f76ef2095af70)) + +# [2.72.0](https://github.com/revanced/revanced-patches/compare/v2.71.2...v2.72.0) (2022-09-29) + + +### Features + +* `tiktok-speed` patch ([#668](https://github.com/revanced/revanced-patches/issues/668)) ([23fff16](https://github.com/revanced/revanced-patches/commit/23fff16e6ab02bf281d46d8b5f93788425d8b525)) +* bump YouTube Music patches to v5.25.51 ([#669](https://github.com/revanced/revanced-patches/issues/669)) ([563c846](https://github.com/revanced/revanced-patches/commit/563c8466568d578de7c8a8e869fb1aa74370784a)) + +## [2.71.2](https://github.com/revanced/revanced-patches/compare/v2.71.1...v2.71.2) (2022-09-28) + + +### Bug Fixes + +* **hide-email-address:** invalid instruction offsets ([#654](https://github.com/revanced/revanced-patches/issues/654)) ([1a3db44](https://github.com/revanced/revanced-patches/commit/1a3db44b5bd9628b7b25cc113a3a53bf8a85bd2b)) + +## [2.71.1](https://github.com/revanced/revanced-patches/compare/v2.71.0...v2.71.1) (2022-09-28) + + +### Bug Fixes + +* **disable-auto-player-popup-panels:** swap switch toggle state description ([#653](https://github.com/revanced/revanced-patches/issues/653)) ([f881301](https://github.com/revanced/revanced-patches/commit/f88130143689c7a63fd67df3dff37caec5db9548)) + +# [2.71.0](https://github.com/revanced/revanced-patches/compare/v2.70.0...v2.71.0) (2022-09-28) + + +### Features + +* `hide-email-address` patch ([#578](https://github.com/revanced/revanced-patches/issues/578)) ([82cb632](https://github.com/revanced/revanced-patches/commit/82cb6321beace1e5feed248d3f3d6ae56cf0d96b)) + +# [2.70.0](https://github.com/revanced/revanced-patches/compare/v2.69.4...v2.70.0) (2022-09-28) + + +### Features + +* `monochrome-icon` patch ([#643](https://github.com/revanced/revanced-patches/issues/643)) ([127c8e5](https://github.com/revanced/revanced-patches/commit/127c8e54e54709c2716029044ae337ad53daafad)) + +## [2.69.4](https://github.com/revanced/revanced-patches/compare/v2.69.3...v2.69.4) (2022-09-27) + + +### Bug Fixes + +* **sponsorblock:** dynamically insert `setSponsorBarRect` call ([#644](https://github.com/revanced/revanced-patches/issues/644)) ([998a249](https://github.com/revanced/revanced-patches/commit/998a249a23d09eb752b35c4da731f4223be40a3b)) + +## [2.69.3](https://github.com/revanced/revanced-patches/compare/v2.69.2...v2.69.3) (2022-09-27) + + +### Bug Fixes + +* **sponsorblock:** correct class name for field reference ([#582](https://github.com/revanced/revanced-patches/issues/582)) ([7cd585c](https://github.com/revanced/revanced-patches/commit/7cd585cd78e284a82cee17d09f8049f0a5cdf2e8)) + +## [2.69.3](https://github.com/revanced/revanced-patches/compare/v2.69.2...v2.69.3) (2022-09-27) + + +### Bug Fixes + +* **sponsorblock:** correct class name for field reference ([#582](https://github.com/revanced/revanced-patches/issues/582)) ([7cd585c](https://github.com/revanced/revanced-patches/commit/7cd585cd78e284a82cee17d09f8049f0a5cdf2e8)) + +## [2.69.2](https://github.com/revanced/revanced-patches/compare/v2.69.1...v2.69.2) (2022-09-26) + + +### Performance Improvements + +* **resource-mapping:** map resources with multiple threads ([a7e4da0](https://github.com/revanced/revanced-patches/commit/a7e4da018bf939accdf6d406b471ac74f9078095)) + +## [2.69.1](https://github.com/revanced/revanced-patches/compare/v2.69.0...v2.69.1) (2022-09-24) + + +### Bug Fixes + +* **sponsorblock:** reflect changes to strings ([#585](https://github.com/revanced/revanced-patches/issues/585)) ([d03568a](https://github.com/revanced/revanced-patches/commit/d03568aa39195a07bec62f43d929923825c67d3f)) + +# [2.69.0](https://github.com/revanced/revanced-patches/compare/v2.68.3...v2.69.0) (2022-09-24) + + +### Features + +* `spotify-theme` patch ([#608](https://github.com/revanced/revanced-patches/issues/608)) ([7ee1b78](https://github.com/revanced/revanced-patches/commit/7ee1b78e8048698ef6490445dd012e2d88b4d332)) + +## [2.68.3](https://github.com/revanced/revanced-patches/compare/v2.68.2...v2.68.3) (2022-09-23) + + +### Bug Fixes + +* **seekbar-tapping:** do not disable seekbar when hiding it ([#600](https://github.com/revanced/revanced-patches/issues/600)) ([68a9457](https://github.com/revanced/revanced-patches/commit/68a9457464c786a61b756eb18ca5f1ce05316636)) + +## [2.68.2](https://github.com/revanced/revanced-patches/compare/v2.68.1...v2.68.2) (2022-09-23) + + +### Bug Fixes + +* **hide-time-and-seekbar:** don't draw the seekbar ([#594](https://github.com/revanced/revanced-patches/issues/594)) ([46e0195](https://github.com/revanced/revanced-patches/commit/46e0195f760dc11ec8d2068ffb57997eccab1aff)) + +## [2.68.1](https://github.com/revanced/revanced-patches/compare/v2.68.0...v2.68.1) (2022-09-23) + + +### Bug Fixes + +* **sponsorblock:** resolve unresolved fingerprint ([e7296f3](https://github.com/revanced/revanced-patches/commit/e7296f3424bed78a0149cca9bf6e29243e962150)) + +# [2.68.0](https://github.com/revanced/revanced-patches/compare/v2.67.1...v2.68.0) (2022-09-23) + + +### Bug Fixes + +* **hide-premium-navbar:** remove the correct instructions ([#591](https://github.com/revanced/revanced-patches/issues/591)) ([b4b0c97](https://github.com/revanced/revanced-patches/commit/b4b0c972d28cf7591ece04d154d1183c8431cb77)) + + +### Features + +* **tiktok:** adapt `tiktok-download` with `tiktok-settings`. ([#586](https://github.com/revanced/revanced-patches/issues/586)) ([876b726](https://github.com/revanced/revanced-patches/commit/876b7266109b099597dd19d6ed3fa3dac098b73b)) + +## [2.67.1](https://github.com/revanced/revanced-patches/compare/v2.67.0...v2.67.1) (2022-09-23) + +# [2.67.0](https://github.com/revanced/revanced-patches/compare/v2.66.2...v2.67.0) (2022-09-23) + + +### Bug Fixes + +* **hide-premium-nav-bar:** invalid import ([#590](https://github.com/revanced/revanced-patches/issues/590)) ([37ad223](https://github.com/revanced/revanced-patches/commit/37ad22360e6ac5e1b1dce474fe6a4e3813e03330)) + + +### Features + +* `hide-premium-nav-bar` patch ([#589](https://github.com/revanced/revanced-patches/issues/589)) ([1811416](https://github.com/revanced/revanced-patches/commit/18114168d7f8c3dd41d1f124c3524c7ca850d33d)) + +## [2.66.2](https://github.com/revanced/revanced-patches/compare/v2.66.1...v2.66.2) (2022-09-22) + + +### Bug Fixes + +* **custom-playback-speed:** implement own method instead of `takeWhile` ([8522d4c](https://github.com/revanced/revanced-patches/commit/8522d4cd705118bf1108ec88bbed542a0cb15943)) + +## [2.66.1](https://github.com/revanced/revanced-patches/compare/v2.66.0...v2.66.1) (2022-09-22) + + +### Bug Fixes + +* **sponsorblock:** broken fingerprint and invert setting `shorts_playing` ([#579](https://github.com/revanced/revanced-patches/issues/579)) ([59fb674](https://github.com/revanced/revanced-patches/commit/59fb674437207aeabed5b92ba32bda120820fb8f)) + +# [2.66.0](https://github.com/revanced/revanced-patches/compare/v2.65.0...v2.66.0) (2022-09-22) + + +### Features + +* **custom-playback-speed:** max, min, granularity option ([b1e423d](https://github.com/revanced/revanced-patches/commit/b1e423d9cdca51e54c154cc39a24c508ca322f60)) + +# [2.65.0](https://github.com/revanced/revanced-patches/compare/v2.64.2...v2.65.0) (2022-09-21) + + +### Features + +* `tiktok-feed-filter` , `tiktok-settings` and `tiktok-force-login` patch ([#501](https://github.com/revanced/revanced-patches/issues/501)) ([10e0286](https://github.com/revanced/revanced-patches/commit/10e028626f2b9d9d01279682198daf135bc82c20)) +* **theme:** arbitrary background color for light theme ([#565](https://github.com/revanced/revanced-patches/issues/565)) ([da40e7e](https://github.com/revanced/revanced-patches/commit/da40e7e0cf266edc89353e62b9821b450f8f0295)) + +## [2.64.2](https://github.com/revanced/revanced-patches/compare/v2.64.1...v2.64.2) (2022-09-21) + + +### Bug Fixes + +* **custom-video-buffer:** use correct offset for `getMaxBuffer` call injection ([6773122](https://github.com/revanced/revanced-patches/commit/6773122d0b1598af78d4619b8af2811be11f6b95)) + +## [2.64.1](https://github.com/revanced/revanced-patches/compare/v2.64.0...v2.64.1) (2022-09-21) + +# [2.64.0](https://github.com/revanced/revanced-patches/compare/v2.63.0...v2.64.0) (2022-09-21) + + +### Features + +* explicit support for YouTube v17.34.35 and v17.34.36 ([#527](https://github.com/revanced/revanced-patches/issues/527)) ([1b6a64b](https://github.com/revanced/revanced-patches/commit/1b6a64b49c54ab53df463981a9ad7ec67729939f)) + +# [2.63.0](https://github.com/revanced/revanced-patches/compare/v2.62.0...v2.63.0) (2022-09-21) + + +### Features + +* `disable-auto-player-popup-panels` ([#543](https://github.com/revanced/revanced-patches/issues/543)) ([d1fa2c3](https://github.com/revanced/revanced-patches/commit/d1fa2c3bc359018146f1f90b1ed4448b7f19b247)) +* disable sponsorblock on shorts ([#439](https://github.com/revanced/revanced-patches/issues/439)) ([47fbcba](https://github.com/revanced/revanced-patches/commit/47fbcba3f845b5c3ef35c4dded8d6f9ffafc5d9e)) + +# [2.62.0](https://github.com/revanced/revanced-patches/compare/v2.61.1...v2.62.0) (2022-09-21) + + +### Features + +* `hide-time-and-seekbar` patch ([#544](https://github.com/revanced/revanced-patches/issues/544)) ([ef7afc7](https://github.com/revanced/revanced-patches/commit/ef7afc7231f8fa6b9b7f84c2b5b9a9fda80aa0f4)) + +## [2.61.1](https://github.com/revanced/revanced-patches/compare/v2.61.0...v2.61.1) (2022-09-21) + + +### Bug Fixes + +* **sponsorblock:** reflect strings from official guidelines ([#523](https://github.com/revanced/revanced-patches/issues/523)) ([1c83959](https://github.com/revanced/revanced-patches/commit/1c83959e69bfbf5140edbf7ef4d2c8901975bfd4)) + +# [2.61.0](https://github.com/revanced/revanced-patches/compare/v2.60.0...v2.61.0) (2022-09-20) + + +### Features + +* section `acknowledgements` for issue templates ([f663935](https://github.com/revanced/revanced-patches/commit/f663935382036cfdccf04627ca77306c5f951b4c)) + +# [2.60.0](https://github.com/revanced/revanced-patches/compare/v2.59.0...v2.60.0) (2022-09-19) + + +### Features + +* **downloads:** missing package installation request ([bc9e81a](https://github.com/revanced/revanced-patches/commit/bc9e81a6c343a912cab6fe3c2560d759fbf1be8a)) + +# [2.59.0](https://github.com/revanced/revanced-patches/compare/v2.58.0...v2.59.0) (2022-09-18) + + +### Features + +* **sponsorblock:** string for response error code 400 ([#512](https://github.com/revanced/revanced-patches/issues/512)) ([d42569f](https://github.com/revanced/revanced-patches/commit/d42569fcc4367fa65c5c88ddc75098d4e208f7f3)) + +# [2.58.0](https://github.com/revanced/revanced-patches/compare/v2.57.0...v2.58.0) (2022-09-18) + + +### Bug Fixes + +* **hdr-auto-brightness:** increase patching compatibility across versions ([bc5c8c1](https://github.com/revanced/revanced-patches/commit/bc5c8c118d5f95905fb7f0fb6afaf97bdcb753bf)) +* **hide-create-button:** increase patching compatibility across versions ([b217a1e](https://github.com/revanced/revanced-patches/commit/b217a1e89f4b9e3ad030ece08a1b81e507c7cc02)) +* **hide-shorts-button:** increase patching compatibility across versions ([7f17934](https://github.com/revanced/revanced-patches/commit/7f1793490f25b9fa16539e80cdc417b3b3e0e639)) + + +### Features + +* bump patches compatibility to v17.36.37 ([4493d7e](https://github.com/revanced/revanced-patches/commit/4493d7e6c7d76760cf4bc93968bf425b23c994de)) + +# [2.57.0](https://github.com/revanced/revanced-patches/compare/v2.56.1...v2.57.0) (2022-09-16) + + +### Features + +* **amoled:** remove in favour of `theme` patch ([0032301](https://github.com/revanced/revanced-patches/commit/00323010ed7356a639e4e86916dcbae96a53f7f6)) +* **theme:** arbitrary background color for dark theme ([afd5502](https://github.com/revanced/revanced-patches/commit/afd5502afdb207d5f52516653869b26fae7e5d21)) + +## [2.56.1](https://github.com/revanced/revanced-patches/compare/v2.56.0...v2.56.1) (2022-09-15) + + +### Bug Fixes + +* `auto-captions` patch switch description ([#488](https://github.com/revanced/revanced-patches/issues/488)) ([57824fb](https://github.com/revanced/revanced-patches/commit/57824fb16c56889d8f18299e9bab2563bb13e5c2)) + +# [2.56.0](https://github.com/revanced/revanced-patches/compare/v2.55.0...v2.56.0) (2022-09-15) + + +### Features + +* **tiktok-download:** custom download path ([#479](https://github.com/revanced/revanced-patches/issues/479)) ([944aa48](https://github.com/revanced/revanced-patches/commit/944aa485cec513faa30a21b91887dcacf9d70b45)) + +# [2.55.0](https://github.com/revanced/revanced-patches/compare/v2.54.1...v2.55.0) (2022-09-15) + + +### Features + +* `disable-auto-captions` patch ([#435](https://github.com/revanced/revanced-patches/issues/435)) ([8ee12d0](https://github.com/revanced/revanced-patches/commit/8ee12d0beadfd0a8fdce0a331646e8786527e84d)) + +## [2.54.1](https://github.com/revanced/revanced-patches/compare/v2.54.0...v2.54.1) (2022-09-14) + + +### Bug Fixes + +* alignment of download button icon ([#477](https://github.com/revanced/revanced-patches/issues/477)) ([2093f11](https://github.com/revanced/revanced-patches/commit/2093f11e762c471a0987fb57f03c7f48d565fdce)) + +# [2.54.0](https://github.com/revanced/revanced-patches/compare/v2.53.0...v2.54.0) (2022-09-14) + + +### Features + +* bump YouTube Music patches to v5.23.50 ([#462](https://github.com/revanced/revanced-patches/issues/462)) ([15c10b1](https://github.com/revanced/revanced-patches/commit/15c10b1e0368a021b0f2ec46bc2e9ea84a5d5afd)) + +# [2.53.0](https://github.com/revanced/revanced-patches/compare/v2.52.3...v2.53.0) (2022-09-13) + + +### Features + +* patch requests issue template ([#467](https://github.com/revanced/revanced-patches/issues/467)) ([d8bb354](https://github.com/revanced/revanced-patches/commit/d8bb35472258c0634599102f4f4bf4cea587a817)), closes [revanced/revanced-patches#463](https://github.com/revanced/revanced-patches/issues/463) + +## [2.52.3](https://github.com/revanced/revanced-patches/compare/v2.52.2...v2.52.3) (2022-09-11) + + +### Bug Fixes + +* path for download icon ([#465](https://github.com/revanced/revanced-patches/issues/465)) ([bff22c3](https://github.com/revanced/revanced-patches/commit/bff22c3a154fd8059f4f8995a303354633a50517)) + +## [2.52.2](https://github.com/revanced/revanced-patches/compare/v2.52.1...v2.52.2) (2022-09-11) + + +### Bug Fixes + +* design language for download icon ([#464](https://github.com/revanced/revanced-patches/issues/464)) ([c24dc17](https://github.com/revanced/revanced-patches/commit/c24dc175f705fd5364b8bcf01f27f3ac8ecc0e64)) + +## [2.52.1](https://github.com/revanced/revanced-patches/compare/v2.52.0...v2.52.1) (2022-09-08) + + +### Bug Fixes + +* multiple compatible pkgs on readme ([3f82a13](https://github.com/revanced/revanced-patches/commit/3f82a1349fb11b1152ac10d76deb930f7325b7c9)) + +# [2.52.0](https://github.com/revanced/revanced-patches/compare/v2.51.3...v2.52.0) (2022-09-08) + + +### Features + +* add deprecated & options to patches.json ([759315b](https://github.com/revanced/revanced-patches/commit/759315be1b23a6b3655fc87b56a8974ebac648cc)) + +## [2.51.3](https://github.com/revanced/revanced-patches/compare/v2.51.2...v2.51.3) (2022-09-08) + + +### Bug Fixes + +* cleanup & trigger release for ThemePatch ([8d25b21](https://github.com/revanced/revanced-patches/commit/8d25b214e297797092432b5fe1a4ede1f42115c8)), closes [#447](https://github.com/revanced/revanced-patches/issues/447) [revanced/revanced-patcher#99](https://github.com/revanced/revanced-patcher/issues/99) + +## [2.51.2](https://github.com/revanced/revanced-patches/compare/v2.51.1...v2.51.2) (2022-09-08) + +## [2.51.1](https://github.com/revanced/revanced-patches/compare/v2.51.0...v2.51.1) (2022-09-08) + + +### Bug Fixes + +* wrap theme option ([150c173](https://github.com/revanced/revanced-patches/commit/150c173adce31d9984d6feca694b472649233056)) + +# [2.51.0](https://github.com/revanced/revanced-patches/compare/v2.50.8...v2.51.0) (2022-09-08) + + +### Features + +* Theme Patch ([#440](https://github.com/revanced/revanced-patches/issues/440)) ([f3b92ca](https://github.com/revanced/revanced-patches/commit/f3b92cab5a057ce605b6d9e75e3b99161bf1422c)) + +## [2.50.8](https://github.com/revanced/revanced-patches/compare/v2.50.7...v2.50.8) (2022-09-08) + +## [2.50.7](https://github.com/revanced/revanced-patches/compare/v2.50.6...v2.50.7) (2022-09-08) + +## [2.50.6](https://github.com/revanced/revanced-patches/compare/v2.50.5...v2.50.6) (2022-09-07) + +## [2.50.5](https://github.com/revanced/revanced-patches/compare/v2.50.4...v2.50.5) (2022-09-07) + +## [2.50.4](https://github.com/revanced/revanced-patches/compare/v2.50.3...v2.50.4) (2022-09-03) + + +### Bug Fixes + +* don't respect primary color for the download button icon ([#424](https://github.com/revanced/revanced-patches/issues/424)) ([3fa70f4](https://github.com/revanced/revanced-patches/commit/3fa70f48a206c56f6ca8aed88baed722e76281e2)) + +## [2.50.3](https://github.com/revanced/revanced-patches/compare/v2.50.2...v2.50.3) (2022-09-01) + + +### Bug Fixes + +* partial ad blockage in `tiktok-ads` patch ([#420](https://github.com/revanced/revanced-patches/issues/420)) ([bc6d192](https://github.com/revanced/revanced-patches/commit/bc6d19205940e3b4b228a9b5de627a2260abd00e)) + +## [2.50.2](https://github.com/revanced/revanced-patches/compare/v2.50.1...v2.50.2) (2022-08-31) + + +### Bug Fixes + +* wrong dependency version ([1e63a57](https://github.com/revanced/revanced-patches/commit/1e63a57b86475670442e6a24a7f24c02dabee239)) + +## [2.50.1](https://github.com/revanced/revanced-patches/compare/v2.50.0...v2.50.1) (2022-08-31) + + +### Bug Fixes + +* resolve fingerprint in `premium-icon-reddit` patch ([#413](https://github.com/revanced/revanced-patches/issues/413)) ([60ec2c6](https://github.com/revanced/revanced-patches/commit/60ec2c6f8a29cfc36313617a92e976f01213ce00)) + +# [2.50.0](https://github.com/revanced/revanced-patches/compare/v2.49.0...v2.50.0) (2022-08-31) + + +### Features + +* `tiktok-download` and `tiktok-seekbar` patch ([#405](https://github.com/revanced/revanced-patches/issues/405)) ([f36e000](https://github.com/revanced/revanced-patches/commit/f36e0007c52f4e925a3a2370cdb51bd45ee00a1b)) + +# [2.49.0](https://github.com/revanced/revanced-patches/compare/v2.48.2...v2.49.0) (2022-08-31) + + +### Features + +* bump YouTube Music patches to v5.22.54 ([#409](https://github.com/revanced/revanced-patches/issues/409)) ([859176e](https://github.com/revanced/revanced-patches/commit/859176e3ece415243ba887424513a961fe4a5ad8)) + +## [2.48.2](https://github.com/revanced/revanced-patches/compare/v2.48.1...v2.48.2) (2022-08-30) + + +### Bug Fixes + +* do not constrain `amoled` patch to versions ([#408](https://github.com/revanced/revanced-patches/issues/408)) ([4af1714](https://github.com/revanced/revanced-patches/commit/4af1714dae4da40fe7488e8b734da91c978f1bdf)) + +## [2.48.1](https://github.com/revanced/revanced-patches/compare/v2.48.0...v2.48.1) (2022-08-30) + + +### Bug Fixes + +* don't overwrite register in `compact-header` patch ([#406](https://github.com/revanced/revanced-patches/issues/406)) ([3da8ab5](https://github.com/revanced/revanced-patches/commit/3da8ab5702f5c2a0384ce85fcb3df3b837cdc90e)) + +# [2.48.0](https://github.com/revanced/revanced-patches/compare/v2.47.0...v2.48.0) (2022-08-30) + + +### Features + +* bump YouTube Music patches to v5.21.52 ([#404](https://github.com/revanced/revanced-patches/issues/404)) ([0abb196](https://github.com/revanced/revanced-patches/commit/0abb1960c10f03149e45e9e5074cafcc1801fc32)) + +# [2.47.0](https://github.com/revanced/revanced-patches/compare/v2.46.0...v2.47.0) (2022-08-29) + + +### Features + +* bump youtube version to v17.33.42 ([#398](https://github.com/revanced/revanced-patches/issues/398)) ([769aa25](https://github.com/revanced/revanced-patches/commit/769aa25a6fffbff4c895ae8a291d97836ba03ed9)) + +# [2.46.0](https://github.com/revanced/revanced-patches/compare/v2.45.3...v2.46.0) (2022-08-29) + + +### Bug Fixes + +* `seekbar-tapping` toggling ([#401](https://github.com/revanced/revanced-patches/issues/401)) ([5550244](https://github.com/revanced/revanced-patches/commit/55502446ce5967922f2e9711a5fea2758fe31e83)) + + +### Features + +* `premium-icon-reddit` patch ([#333](https://github.com/revanced/revanced-patches/issues/333)) ([6739e7b](https://github.com/revanced/revanced-patches/commit/6739e7b94c18200bb3afbc9a8465d97bd18d1945)) +* blacklist `.vsc` folder in `.gitignore` ([#331](https://github.com/revanced/revanced-patches/issues/331)) ([1922253](https://github.com/revanced/revanced-patches/commit/1922253118e670aa66b33186f5cee2ffb3c55718)) + +## [2.45.3](https://github.com/revanced/revanced-patches/compare/v2.45.2...v2.45.3) (2022-08-29) + + +### Bug Fixes + +* release `patches.json` file ([#402](https://github.com/revanced/revanced-patches/issues/402)) ([17e351c](https://github.com/revanced/revanced-patches/commit/17e351c99a69652f20d89e579f43c1ce110cd81d)) + +## [2.45.2](https://github.com/revanced/revanced-patches/compare/v2.45.1...v2.45.2) (2022-08-28) + + +### Bug Fixes + +* add `patches.json` as a release asset ([a0b2c89](https://github.com/revanced/revanced-patches/commit/a0b2c89071d64efa7629a8d6d6bc0492c9772aa9)) + +## [2.45.1](https://github.com/revanced/revanced-patches/compare/v2.45.0...v2.45.1) (2022-08-28) + + +### Bug Fixes + +* run meta generator ([70a2b45](https://github.com/revanced/revanced-patches/commit/70a2b456b4031b3da38a18a71ede7bf84099acd0)) + +# [2.45.0](https://github.com/revanced/revanced-patches/compare/v2.44.0...v2.45.0) (2022-08-28) + + +### Features + +* Add JSON meta ([4c229d3](https://github.com/revanced/revanced-patches/commit/4c229d350859903a782c43b13457628739572f09)) + +# [2.44.0](https://github.com/revanced/revanced-patches/compare/v2.43.2...v2.44.0) (2022-08-27) + + +### Features + +* add debug switch ([#391](https://github.com/revanced/revanced-patches/issues/391)) ([ee4b776](https://github.com/revanced/revanced-patches/commit/ee4b7761d8bb4549f2f100f721c90c664eedfe4a)) + +## [2.43.2](https://github.com/revanced/revanced-patches/compare/v2.43.1...v2.43.2) (2022-08-26) + + +### Bug Fixes + +* package name for Vanced ([#390](https://github.com/revanced/revanced-patches/issues/390)) ([85206b9](https://github.com/revanced/revanced-patches/commit/85206b99e1399d00e5eaebded8aadc5fb982aaa4)) + +## [2.43.1](https://github.com/revanced/revanced-patches/compare/v2.43.0...v2.43.1) (2022-08-26) + + +### Bug Fixes + +* `Patch` annotation for `client-spoof` patch ([6a8af47](https://github.com/revanced/revanced-patches/commit/6a8af47f2dfb319a53db9b5f9deb0392f10f4185)) + +# [2.43.0](https://github.com/revanced/revanced-patches/compare/v2.42.1...v2.43.0) (2022-08-26) + + +### Features + +* `client-spoof` patch ([5512c07](https://github.com/revanced/revanced-patches/commit/5512c072fa4b047849dbea0d2d382dd85e3a0827)) + +## [2.42.1](https://github.com/revanced/revanced-patches/compare/v2.42.0...v2.42.1) (2022-08-26) + + +### Bug Fixes + +* changed default value for autorepeat setting ([#386](https://github.com/revanced/revanced-patches/issues/386)) ([d43add7](https://github.com/revanced/revanced-patches/commit/d43add7c21f0d9f45830476704985755e37d33ef)) + +# [2.42.0](https://github.com/revanced/revanced-patches/compare/v2.41.0...v2.42.0) (2022-08-22) + + +### Bug Fixes + +* default values for settings ([834c4ad](https://github.com/revanced/revanced-patches/commit/834c4add71570d36b645246621ba24da3869d613)) + + +### Features + +* setting for downloader package name ([965d05c](https://github.com/revanced/revanced-patches/commit/965d05cfa55d7a51f64a11f0219e2867568ba852)) +* v17.29.34 compatibility for `downloads` patch ([#374](https://github.com/revanced/revanced-patches/issues/374)) ([d81f1af](https://github.com/revanced/revanced-patches/commit/d81f1af327e0d7471f410811af46da34ddfb1bb4)) + +# [2.41.0](https://github.com/revanced/revanced-patches/compare/v2.40.2...v2.41.0) (2022-08-22) + + +### Features + +* `downloads` patch ([#215](https://github.com/revanced/revanced-patches/issues/215)) ([304fbac](https://github.com/revanced/revanced-patches/commit/304fbacab22538fb0945f564e061783b47120120)) + +## [2.40.2](https://github.com/revanced/revanced-patches/compare/v2.40.1...v2.40.2) (2022-08-22) + + +### Bug Fixes + +* correct title for `tablet-miniplayer` setting switch ([6af6c02](https://github.com/revanced/revanced-patches/commit/6af6c02154d170153093bd846dfcb7a6205dd6fa)) + +## [2.40.1](https://github.com/revanced/revanced-patches/compare/v2.40.0...v2.40.1) (2022-08-22) + + +### Bug Fixes + +* add missing switch for `tablet-mini-player` patch ([244a1b2](https://github.com/revanced/revanced-patches/commit/244a1b2cb9f77272dc62287a4a34a487b0289295)) + +# [2.40.0](https://github.com/revanced/revanced-patches/compare/v2.39.1...v2.40.0) (2022-08-22) + + +### Features + +* `settings` patch framework ([#266](https://github.com/revanced/revanced-patches/issues/266)) ([084a99b](https://github.com/revanced/revanced-patches/commit/084a99bc6f5ed67c0b270e219c2dd75a30f302f1)) + +## [2.39.1](https://github.com/revanced/revanced-patches/compare/v2.39.0...v2.39.1) (2022-08-21) + + +### Bug Fixes + +* make `custom-branding` cross-platform ([#366](https://github.com/revanced/revanced-patches/issues/366)) ([02ac62b](https://github.com/revanced/revanced-patches/commit/02ac62b0ea7e47ff3aa5078ce4645421f410b154)) + +# [2.39.0](https://github.com/revanced/revanced-patches/compare/v2.38.0...v2.39.0) (2022-08-19) + + +### Features + +* bundle `dex` file into `jar` file ([#359](https://github.com/revanced/revanced-patches/issues/359)) ([f419252](https://github.com/revanced/revanced-patches/commit/f4192526eab1e3e0208e7460847b892e077fcf5a)) + +# [2.38.0](https://github.com/revanced/revanced-patches/compare/v2.37.0...v2.38.0) (2022-08-17) + + +### Features + +* bump YouTube patches to `v17.32.35` ([#347](https://github.com/revanced/revanced-patches/issues/347)) ([5292a0e](https://github.com/revanced/revanced-patches/commit/5292a0e973953225f94eed887a5d8f1ead17bb97)) + +# [2.37.0](https://github.com/revanced/revanced-patches/compare/v2.36.2...v2.37.0) (2022-08-15) + + +### Features + +* ˋpflotsh-ecmwf-subscription-unlockˋ patch ([#332](https://github.com/revanced/revanced-patches/issues/332)) ([ae2a1d8](https://github.com/revanced/revanced-patches/commit/ae2a1d8362e388032c3521101ff60698c4af1583)) + +## [2.36.2](https://github.com/revanced/revanced-patches/compare/v2.36.1...v2.36.2) (2022-08-14) + + +### Bug Fixes + +* disable cast modules in YouTube Music ([#337](https://github.com/revanced/revanced-patches/issues/337)) ([86eaba8](https://github.com/revanced/revanced-patches/commit/86eaba8248100987f46540a224956099bcf9da2c)) + +## [2.36.1](https://github.com/revanced/revanced-patches/compare/v2.36.0...v2.36.1) (2022-08-14) + + +### Bug Fixes + +* add missing switch for tablet mini-player ([b824d35](https://github.com/revanced/revanced-patches/commit/b824d35960df9e99c2a2d248356c3c9342cfe130)) + +# [2.36.0](https://github.com/revanced/revanced-patches/compare/v2.35.0...v2.36.0) (2022-08-14) + + +### Features + +* `swipe-controls` override volume button behaviour ([#285](https://github.com/revanced/revanced-patches/issues/285)) ([69465f3](https://github.com/revanced/revanced-patches/commit/69465f3a9934973a2449605a248a462510e10ff6)) + +# [2.35.0](https://github.com/revanced/revanced-patches/compare/v2.34.0...v2.35.0) (2022-08-14) + + +### Features + +* `tiktok-ads` patch ([#335](https://github.com/revanced/revanced-patches/issues/335)) ([81bef57](https://github.com/revanced/revanced-patches/commit/81bef57742bfaefcdfe02fd290cca6923341149a)) + +# [2.34.0](https://github.com/revanced/revanced-patches/compare/v2.33.2...v2.34.0) (2022-08-14) + + +### Features + +* `tablet-mini-player` patch ([dae41f1](https://github.com/revanced/revanced-patches/commit/dae41f1c59d26d00b1ba821dbe1521e68226f47c)) + +## [2.33.2](https://github.com/revanced/revanced-patches/compare/v2.33.1...v2.33.2) (2022-08-07) + +## [2.33.1](https://github.com/revanced/revanced-patches/compare/v2.33.0...v2.33.1) (2022-08-07) + + +### Bug Fixes + +* spoof `X-Android-Cert` of Firebase `authToken` api request ([#315](https://github.com/revanced/revanced-patches/issues/315)) ([c81d61f](https://github.com/revanced/revanced-patches/commit/c81d61f685449590473fa5205e7709f81872a9b9)) + +# [2.33.0](https://github.com/revanced/revanced-patches/compare/v2.32.1...v2.33.0) (2022-08-06) + + +### Features + +* bump music patches to 5.17.51 ([#307](https://github.com/revanced/revanced-patches/issues/307)) ([c303589](https://github.com/revanced/revanced-patches/commit/c303589bd8d1956ce2e3c4c285d45a648b5db30f)) + +## [2.32.1](https://github.com/revanced/revanced-patches/compare/v2.32.0...v2.32.1) (2022-08-04) + + +### Bug Fixes + +* incorrect compatibilty attribute ([#296](https://github.com/revanced/revanced-patches/issues/296)) ([0ab3e97](https://github.com/revanced/revanced-patches/commit/0ab3e9724157c628555964273c6b65f9f48f9664)) + +# [2.32.0](https://github.com/revanced/revanced-patches/compare/v2.31.0...v2.32.0) (2022-08-04) + + +### Features + +* `promo-code-unlock` patch ([#292](https://github.com/revanced/revanced-patches/issues/292)) ([d5df2e6](https://github.com/revanced/revanced-patches/commit/d5df2e68f47cbb3d374b8ce24769872959014051)) + +# [2.31.0](https://github.com/revanced/revanced-patches/compare/v2.30.1...v2.31.0) (2022-08-03) + + +### Features + +* remove `force-vp9-codec` patch ([#287](https://github.com/revanced/revanced-patches/issues/287)) ([e428978](https://github.com/revanced/revanced-patches/commit/e42897801b20e7439969954753e2c15c85eb41b5)) + +## [2.30.1](https://github.com/revanced/revanced-patches/compare/v2.30.0...v2.30.1) (2022-08-03) + + +### Bug Fixes + +* migrate to new patcher api ([f43446e](https://github.com/revanced/revanced-patches/commit/f43446ed189e583302fcd899f5eb7517f2a77144)) + +# [2.30.0](https://github.com/revanced/revanced-patches/compare/v2.29.0...v2.30.0) (2022-08-03) + + +### Bug Fixes + +* incorrect fingerprint version [skip ci] ([f8c62ae](https://github.com/revanced/revanced-patches/commit/f8c62ae16b088ff6d0e96a4ef62dc707cd83beb9)) +* rollback to `Dependencies` annotation ([36a2ae8](https://github.com/revanced/revanced-patches/commit/36a2ae886c63f1d22bb1cca9e7110af6c3f6f2d3)) + + +### Features + +* set the correct theme of the settings screen ([70d850c](https://github.com/revanced/revanced-patches/commit/70d850cf295d306b0de9419efe6a4bbf3857d3a0)) + +# [2.29.0](https://github.com/revanced/revanced-patches/compare/v2.28.2...v2.29.0) (2022-08-02) + + +### Features + +* add "Application Icon Path" option to branding ([1748d1e](https://github.com/revanced/revanced-patches/commit/1748d1e5badf61213d793ff7ae1d090ea2ea82f0)) +* add "Application Name" option to branding and move renaming to CustomBrandingPatch.kt ([8dafe05](https://github.com/revanced/revanced-patches/commit/8dafe05b2c5e2b0291d46b02717d910721673712)) + +## [2.28.2](https://github.com/revanced/revanced-patches/compare/v2.28.1...v2.28.2) (2022-08-02) + + +### Bug Fixes + +* remove requirement for solution [skip ci] ([#271](https://github.com/revanced/revanced-patches/issues/271)) ([553fad3](https://github.com/revanced/revanced-patches/commit/553fad3fe1bb79bdf34e9f91c0e1cbfda78e1054)) + +## [2.28.1](https://github.com/revanced/revanced-patches/compare/v2.28.0...v2.28.1) (2022-07-31) + + +### Bug Fixes + +* add missing permission to reboot app ([#260](https://github.com/revanced/revanced-patches/issues/260)) ([6ced6df](https://github.com/revanced/revanced-patches/commit/6ced6df8ed7642dea51e1acd1c12f4de4874b972)) + +# [2.28.0](https://github.com/revanced/revanced-patches/compare/v2.27.0...v2.28.0) (2022-07-31) + + +### Features + +* add `custom-video-buffer` patch ([9f117c7](https://github.com/revanced/revanced-patches/commit/9f117c74cdbdcf98eae97cf4c37f0baca451d695)) + +# [2.27.0](https://github.com/revanced/revanced-patches/compare/v2.26.0...v2.27.0) (2022-07-31) + + +### Features + +* `settings` patch ([0e229a4](https://github.com/revanced/revanced-patches/commit/0e229a46cb5b8b74183c47a6eae08d667f941406)) + +# [2.26.0](https://github.com/revanced/revanced-patches/compare/v2.25.3...v2.26.0) (2022-07-31) + + +### Features + +* `ResourceUtils` helper class ([e0e1144](https://github.com/revanced/revanced-patches/commit/e0e11447a7ac184d43c75955854c52c6992ff667)) + +## [2.25.3](https://github.com/revanced/revanced-patches/compare/v2.25.2...v2.25.3) (2022-07-29) + + +### Bug Fixes + +* actually call `VideoInformation.setCurrentVideoId` first ([5c62d0a](https://github.com/revanced/revanced-patches/commit/5c62d0a2e0217de1b9563a41b4e94ed63230440f)) + +## [2.25.2](https://github.com/revanced/revanced-patches/compare/v2.25.1...v2.25.2) (2022-07-26) + + +### Bug Fixes + +* rename `default-video-quality` to `remember-video-quality` ([7f6cdfd](https://github.com/revanced/revanced-patches/commit/7f6cdfd7c2b5e72742bbb92e4d584f722cb82cae)) + +## [2.25.1](https://github.com/revanced/revanced-patches/compare/v2.25.0...v2.25.1) (2022-07-26) + + +### Bug Fixes + +* `default-video-quality` patch crashing ([#227](https://github.com/revanced/revanced-patches/issues/227)) ([379327a](https://github.com/revanced/revanced-patches/commit/379327a6b2325ef93c7107472343dd9fd85a0f56)) + +# [2.25.0](https://github.com/revanced/revanced-patches/compare/v2.24.0...v2.25.0) (2022-07-26) + + +### Features + +* `general-reddit-ads` patch ([#235](https://github.com/revanced/revanced-patches/issues/235)) ([d63c016](https://github.com/revanced/revanced-patches/commit/d63c016d38d2a5b1d152abccdb582990987a1911)) + +# [2.24.0](https://github.com/revanced/revanced-patches/compare/v2.23.0...v2.24.0) (2022-07-26) + + +### Features + +* `music-microg-support` patch ([#208](https://github.com/revanced/revanced-patches/issues/208)) ([50863e9](https://github.com/revanced/revanced-patches/commit/50863e97cc8eba7e1ad6d11b4821baa45fdd29c4)) + +# [2.23.0](https://github.com/revanced/revanced-patches/compare/v2.22.2...v2.23.0) (2022-07-26) + + +### Features + +* bump compatibility of YouTube Music patches to v5.16.51 ([#238](https://github.com/revanced/revanced-patches/issues/238)) ([4fa2a12](https://github.com/revanced/revanced-patches/commit/4fa2a127d715f953a1e4efacaecbc218209a01f3)) + +## [2.22.2](https://github.com/revanced/revanced-patches/compare/v2.22.1...v2.22.2) (2022-07-26) + + +### Bug Fixes + +* bump youtube version to 17.29.34 ([#236](https://github.com/revanced/revanced-patches/issues/236)) ([be6494a](https://github.com/revanced/revanced-patches/commit/be6494a80989044cba961dc22fc58ffb2e8591f0)) + +## [2.22.1](https://github.com/revanced/revanced-patches/compare/v2.22.0...v2.22.1) (2022-07-24) + + +### Bug Fixes + +* remove broken video-quality patch ([e8d516a](https://github.com/revanced/revanced-patches/commit/e8d516af896971a09bb9aca8b00e7b7bb57c3dd9)) + +# [2.22.0](https://github.com/revanced/revanced-patches/compare/v2.21.3...v2.22.0) (2022-07-24) + + +### Features + +* `default-video-quality` patch ([#141](https://github.com/revanced/revanced-patches/issues/141)) ([609ad7d](https://github.com/revanced/revanced-patches/commit/609ad7dee7e29b2ecc4e95a93e9dab1646b4b050)) + +# [2.12.0](https://github.com/Canny1913/revanced-patches/compare/v2.11.0...v2.12.0) (2022-07-24) + + +### Bug Fixes + +* `autorepeat-by-default` patch ([#148](https://github.com/Canny1913/revanced-patches/issues/148)) ([fe628ba](https://github.com/Canny1913/revanced-patches/commit/fe628ba909d89ea0bf3d95fe94ca78ef819677da)) +* `codecs-unlock` patch and update Music patches to `5.14.53` ([2437d30](https://github.com/Canny1913/revanced-patches/commit/2437d3070f6a630d353619f642cefebd47abee20)) +* `exclusive-audio-playback` patch ([#153](https://github.com/Canny1913/revanced-patches/issues/153)) ([9beff95](https://github.com/Canny1913/revanced-patches/commit/9beff9567f1586e5c58690c1f1d2f7f204025ab7)) +* `hrd-auto-brightness` ([#152](https://github.com/Canny1913/revanced-patches/issues/152)) ([5f2e9ba](https://github.com/Canny1913/revanced-patches/commit/5f2e9ba30b7432be04bdc9f9f7ec7ac75fdc4b34)) +* `minimized-playback` patch for YouTube Kids videos ([#201](https://github.com/Canny1913/revanced-patches/issues/201)) ([31e3b42](https://github.com/Canny1913/revanced-patches/commit/31e3b42c6bbce0d00f049b8a69bafc94900bb3b4)) +* `old-quality-layout` patch ([2497425](https://github.com/Canny1913/revanced-patches/commit/2497425c9f11b8b14c861c2f0f34ff47bdbfac53)) +* `swipe-controls` with active engagement panel ([#177](https://github.com/Canny1913/revanced-patches/issues/177)) ([000ec6d](https://github.com/Canny1913/revanced-patches/commit/000ec6d8f6ecbb910a06ec852564ee9e5f03dcf8)) +* add v17.28.34 compatiblity for the `hide-shorts-button` patch ([#224](https://github.com/Canny1913/revanced-patches/issues/224)) ([76166bb](https://github.com/Canny1913/revanced-patches/commit/76166bb35f940ef661e2802f5bf93ed91f2e2913)) +* autoplay not working. ([586eed5](https://github.com/Canny1913/revanced-patches/commit/586eed515fc7ff8e3b1b150b0d34610b39480bb5)) +* autoplay still enabled when using patch ([4f96129](https://github.com/Canny1913/revanced-patches/commit/4f961298f6cb6417ee3f3d6f8ac7ce96594ed03b)) +* bump youtube version for swipe-controls patch ([ff207a5](https://github.com/Canny1913/revanced-patches/commit/ff207a57af7d3c15a8127f4465e97da23878b0d6)) +* bump youtube version to 17.27.39 ([b93401a](https://github.com/Canny1913/revanced-patches/commit/b93401a391c0ed4887dd1376ac253f74f98a8d7e)) +* bump youtube version to 17.28.34 ([#225](https://github.com/Canny1913/revanced-patches/issues/225)) ([738cb6a](https://github.com/Canny1913/revanced-patches/commit/738cb6af177e92bbff8c02d6808fe416c0ad2582)) +* check if node has attributes before accessing them ([2d2ed87](https://github.com/Canny1913/revanced-patches/commit/2d2ed870dacfe092eb6acbcaae5e51775c611322)) +* compatibility of `force-vp9-codec-parent-fingerprint` fingerprint with version `17.27.39` ([523fd86](https://github.com/Canny1913/revanced-patches/commit/523fd8627bc965a724267f725c28fba5e7a25a04)) +* constrain `old-quality-layout` to older version ([add7232](https://github.com/Canny1913/revanced-patches/commit/add72326199e90f677b450b553b9d88c0bb4c490)) +* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/Canny1913/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97)) +* disable-fullscreen-panels patch not working ([#213](https://github.com/Canny1913/revanced-patches/issues/213)) ([5372105](https://github.com/Canny1913/revanced-patches/commit/5372105e72607f289b779e4c2a3c13f3458842ce)) +* display codename for patch names ([10c53f7](https://github.com/Canny1913/revanced-patches/commit/10c53f720df3e70b9d59e8bc3219d56b996f03db)) +* exclude `swipe-controls` by default due to instability ([bdeb8e0](https://github.com/Canny1913/revanced-patches/commit/bdeb8e04609a0ca94d9e2921bf7d486b9e229cec)) +* incorrect package name in gradle task ([152b2c9](https://github.com/Canny1913/revanced-patches/commit/152b2c90cf102170648fcc168da10f46743bdc63)) +* invalid regex ([26bf1d8](https://github.com/Canny1913/revanced-patches/commit/26bf1d818f953abc061126d8b91f17cd9008ba1d)) +* listing of wrong fingerprint class ([#147](https://github.com/Canny1913/revanced-patches/issues/147)) ([95c2bbd](https://github.com/Canny1913/revanced-patches/commit/95c2bbdd1deb1d76f1177b48286fa6a3bc9f7663)) +* make all patches toggleable with settings ([#202](https://github.com/Canny1913/revanced-patches/issues/202)) ([7e1d82f](https://github.com/Canny1913/revanced-patches/commit/7e1d82f1161237632c3a57f109db6b8903006b33)) +* make the patch work w/o settings ([7f0be5c](https://github.com/Canny1913/revanced-patches/commit/7f0be5c08b738d7baeef7ad3e167aab623c2d08e)) +* references to integrations in `return-youtube-dislike` patch ([5824c2c](https://github.com/Canny1913/revanced-patches/commit/5824c2cdfb1a2d7b8d68044388e5e0746ef2ca09)) +* remove "dependency" ([b93cb9b](https://github.com/Canny1913/revanced-patches/commit/b93cb9b8dd4b78c53bbf0298db0f532a42d46f7d)) +* rename autorepeat-by-default patch to always-autorepeat ([3606015](https://github.com/Canny1913/revanced-patches/commit/3606015d715f150cc51fbb29adf3be252b96faab)) +* stop using manually entered index ([ed0520d](https://github.com/Canny1913/revanced-patches/commit/ed0520d85c74729d7daabbfbd6cb77ad8cd62f15)) +* update patches to `17.26.35` ([#142](https://github.com/Canny1913/revanced-patches/issues/142)) ([b04112c](https://github.com/Canny1913/revanced-patches/commit/b04112c8562a7b95e7555e894b665913094b33eb)) +* use dependency in correct patch ([a2a1ee8](https://github.com/Canny1913/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637)) +* wording [skip ci] ([ba64d9e](https://github.com/Canny1913/revanced-patches/commit/ba64d9efc3ee606e9bda30ad7f8017af34b1dc3f)) + + +### Features + +* `compact-header` patch ([83753ba](https://github.com/Canny1913/revanced-patches/commit/83753bacf8f56ad16f8abafc02034f1595c12532)) +* `default-video-quality` patch ([9d30d37](https://github.com/Canny1913/revanced-patches/commit/9d30d372dd13663564286820f3e7685c0a0cee8a)) +* `force-vp9-codec` patch ([#157](https://github.com/Canny1913/revanced-patches/issues/157)) ([07806a1](https://github.com/Canny1913/revanced-patches/commit/07806a16e5368118949e0e476e901987433ad403)) +* `hide-get-premium` patch ([#195](https://github.com/Canny1913/revanced-patches/issues/195)) ([639aab4](https://github.com/Canny1913/revanced-patches/commit/639aab411ee514b50840c15c8e9a1a889854403f)) +* `minimize-playback-music` patch ([#200](https://github.com/Canny1913/revanced-patches/issues/200)) ([d4fd18b](https://github.com/Canny1913/revanced-patches/commit/d4fd18bc74c92e17cdfb21b627b2f6a8919ff2dc)) +* `music-video-ads` patch ([#172](https://github.com/Canny1913/revanced-patches/issues/172)) ([cbb71b5](https://github.com/Canny1913/revanced-patches/commit/cbb71b5faf1386ed31dd9e0387f6cf0509d92d31)) +* `return-youtube-dislikes` patch ([#175](https://github.com/Canny1913/revanced-patches/issues/175)) ([18a66d8](https://github.com/Canny1913/revanced-patches/commit/18a66d8454cf6e7cfdd4183631a6870c80d16b90)) +* `sponsorblock` patch ([#101](https://github.com/Canny1913/revanced-patches/issues/101)) ([36af4cc](https://github.com/Canny1913/revanced-patches/commit/36af4cc14ff8057c10b99019cb23acc6e5aec3f2)), closes [#89](https://github.com/Canny1913/revanced-patches/issues/89) [#90](https://github.com/Canny1913/revanced-patches/issues/90) +* `swipe-controls` rewrite ([#131](https://github.com/Canny1913/revanced-patches/issues/131)) ([b7dba09](https://github.com/Canny1913/revanced-patches/commit/b7dba09927ba15a9eacb06dcb4bf1f268560c96e)) +* 1 line of code ([e2d28bd](https://github.com/Canny1913/revanced-patches/commit/e2d28bd576f74f1792dc959b8ed3064ec6a4cb68)) +* add version 17.26.35 ([25cd66c](https://github.com/Canny1913/revanced-patches/commit/25cd66cc6d2cd56a4170c5c69a708011fe7eab1d)) +* complete `default-video-quality` patch ([f7e153b](https://github.com/Canny1913/revanced-patches/commit/f7e153bd08b6dfe07591678195bfa6b06606f6a2)) +* issue templates ([b82b0aa](https://github.com/Canny1913/revanced-patches/commit/b82b0aad88b7ab9d86f1bcc8e007f6a76a9aa1a5)) +* twitter `timeline-ads` patch ([#222](https://github.com/Canny1913/revanced-patches/issues/222)) ([f16e67f](https://github.com/Canny1913/revanced-patches/commit/f16e67fc75c9c3505ff875a216ce7e868022075e)) + +## [2.21.3](https://github.com/revanced/revanced-patches/compare/v2.21.2...v2.21.3) (2022-07-24) + + +### Bug Fixes + +* rename autorepeat-by-default patch to always-autorepeat ([3606015](https://github.com/revanced/revanced-patches/commit/3606015d715f150cc51fbb29adf3be252b96faab)) + +## [2.21.2](https://github.com/revanced/revanced-patches/compare/v2.21.1...v2.21.2) (2022-07-23) + + +### Bug Fixes + +* bump youtube version to 17.28.34 ([#225](https://github.com/revanced/revanced-patches/issues/225)) ([738cb6a](https://github.com/revanced/revanced-patches/commit/738cb6af177e92bbff8c02d6808fe416c0ad2582)) + +## [2.21.1](https://github.com/revanced/revanced-patches/compare/v2.21.0...v2.21.1) (2022-07-23) + + +### Bug Fixes + +* add v17.28.34 compatiblity for the `hide-shorts-button` patch ([#224](https://github.com/revanced/revanced-patches/issues/224)) ([76166bb](https://github.com/revanced/revanced-patches/commit/76166bb35f940ef661e2802f5bf93ed91f2e2913)) + +# [2.21.0](https://github.com/revanced/revanced-patches/compare/v2.20.5...v2.21.0) (2022-07-23) + + +### Features + +* twitter `timeline-ads` patch ([#222](https://github.com/revanced/revanced-patches/issues/222)) ([f16e67f](https://github.com/revanced/revanced-patches/commit/f16e67fc75c9c3505ff875a216ce7e868022075e)) + +## [2.20.5](https://github.com/revanced/revanced-patches/compare/v2.20.4...v2.20.5) (2022-07-22) + + +### Bug Fixes + +* exclude `swipe-controls` by default due to instability ([bdeb8e0](https://github.com/revanced/revanced-patches/commit/bdeb8e04609a0ca94d9e2921bf7d486b9e229cec)) + +## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21) + + +### Bug Fixes + +* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/revanced/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97)) +* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637)) + +## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21) + + +### Bug Fixes + +* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/revanced/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97)) +* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637)) + +## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21) + + +### Bug Fixes + +* crash when using force-vp9-codec patch ([7a35e5c](https://github.com/revanced/revanced-patches/commit/7a35e5c985b412d5a84083d1416d3207a40b3e97)) +* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637)) + +## [2.20.4](https://github.com/revanced/revanced-patches/compare/v2.20.3...v2.20.4) (2022-07-21) + + +### Bug Fixes + +* use dependency in correct patch ([a2a1ee8](https://github.com/revanced/revanced-patches/commit/a2a1ee8eb5e059b30fe58c918a80976ef4d7b637)) + +## [2.20.3](https://github.com/revanced/revanced-patches/compare/v2.20.2...v2.20.3) (2022-07-20) + + +### Bug Fixes + +* disable-fullscreen-panels patch not working ([#213](https://github.com/revanced/revanced-patches/issues/213)) ([5372105](https://github.com/revanced/revanced-patches/commit/5372105e72607f289b779e4c2a3c13f3458842ce)) + +## [2.20.2](https://github.com/revanced/revanced-patches/compare/v2.20.1...v2.20.2) (2022-07-20) + + +### Bug Fixes + +* autoplay not working. ([586eed5](https://github.com/revanced/revanced-patches/commit/586eed515fc7ff8e3b1b150b0d34610b39480bb5)) + +## [2.20.1](https://github.com/revanced/revanced-patches/compare/v2.20.0...v2.20.1) (2022-07-20) + + +### Bug Fixes + +* autoplay still enabled when using patch ([4f96129](https://github.com/revanced/revanced-patches/commit/4f961298f6cb6417ee3f3d6f8ac7ce96594ed03b)) + +# [2.20.0](https://github.com/revanced/revanced-patches/compare/v2.19.0...v2.20.0) (2022-07-18) + + +### Features + +* `minimize-playback-music` patch ([#200](https://github.com/revanced/revanced-patches/issues/200)) ([d4fd18b](https://github.com/revanced/revanced-patches/commit/d4fd18bc74c92e17cdfb21b627b2f6a8919ff2dc)) + +# [2.19.0](https://github.com/revanced/revanced-patches/compare/v2.18.3...v2.19.0) (2022-07-18) + + +### Features + +* `compact-header` patch ([83753ba](https://github.com/revanced/revanced-patches/commit/83753bacf8f56ad16f8abafc02034f1595c12532)) + +## [2.18.3](https://github.com/revanced/revanced-patches/compare/v2.18.2...v2.18.3) (2022-07-18) + + +### Bug Fixes + +* compatibility of `force-vp9-codec-parent-fingerprint` fingerprint with version `17.27.39` ([523fd86](https://github.com/revanced/revanced-patches/commit/523fd8627bc965a724267f725c28fba5e7a25a04)) + +## [2.18.2](https://github.com/revanced/revanced-patches/compare/v2.18.1...v2.18.2) (2022-07-18) + + +### Bug Fixes + +* make all patches toggleable with settings ([#202](https://github.com/revanced/revanced-patches/issues/202)) ([7e1d82f](https://github.com/revanced/revanced-patches/commit/7e1d82f1161237632c3a57f109db6b8903006b33)) + +## [2.18.1](https://github.com/revanced/revanced-patches/compare/v2.18.0...v2.18.1) (2022-07-18) + + +### Bug Fixes + +* `minimized-playback` patch for YouTube Kids videos ([#201](https://github.com/revanced/revanced-patches/issues/201)) ([31e3b42](https://github.com/revanced/revanced-patches/commit/31e3b42c6bbce0d00f049b8a69bafc94900bb3b4)) + +# [2.18.0](https://github.com/revanced/revanced-patches/compare/v2.17.0...v2.18.0) (2022-07-18) + + +### Features + +* `hide-get-premium` patch ([#195](https://github.com/revanced/revanced-patches/issues/195)) ([639aab4](https://github.com/revanced/revanced-patches/commit/639aab411ee514b50840c15c8e9a1a889854403f)) + +# [2.17.0](https://github.com/revanced/revanced-patches/compare/v2.16.1...v2.17.0) (2022-07-17) + + +### Bug Fixes + +* check if node has attributes before accessing them ([2d2ed87](https://github.com/revanced/revanced-patches/commit/2d2ed870dacfe092eb6acbcaae5e51775c611322)) + + +### Features + +* `sponsorblock` patch ([#101](https://github.com/revanced/revanced-patches/issues/101)) ([36af4cc](https://github.com/revanced/revanced-patches/commit/36af4cc14ff8057c10b99019cb23acc6e5aec3f2)), closes [#89](https://github.com/revanced/revanced-patches/issues/89) [#90](https://github.com/revanced/revanced-patches/issues/90) + +## [2.16.1](https://github.com/revanced/revanced-patches/compare/v2.16.0...v2.16.1) (2022-07-17) + + +### Bug Fixes + +* bump youtube version to 17.27.39 ([b93401a](https://github.com/revanced/revanced-patches/commit/b93401a391c0ed4887dd1376ac253f74f98a8d7e)) + +# [2.16.0](https://github.com/revanced/revanced-patches/compare/v2.15.1...v2.16.0) (2022-07-17) + + +### Features + +* `force-vp9-codec` patch ([#157](https://github.com/revanced/revanced-patches/issues/157)) ([07806a1](https://github.com/revanced/revanced-patches/commit/07806a16e5368118949e0e476e901987433ad403)) + +## [2.15.1](https://github.com/revanced/revanced-patches/compare/v2.15.0...v2.15.1) (2022-07-17) + + +### Bug Fixes + +* `old-quality-layout` patch ([2497425](https://github.com/revanced/revanced-patches/commit/2497425c9f11b8b14c861c2f0f34ff47bdbfac53)) +* references to integrations in `return-youtube-dislike` patch ([5824c2c](https://github.com/revanced/revanced-patches/commit/5824c2cdfb1a2d7b8d68044388e5e0746ef2ca09)) + +# [2.15.0](https://github.com/revanced/revanced-patches/compare/v2.14.1...v2.15.0) (2022-07-16) + + +### Features + +* `return-youtube-dislikes` patch ([#175](https://github.com/revanced/revanced-patches/issues/175)) ([18a66d8](https://github.com/revanced/revanced-patches/commit/18a66d8454cf6e7cfdd4183631a6870c80d16b90)) + +## [2.14.1](https://github.com/revanced/revanced-patches/compare/v2.14.0...v2.14.1) (2022-07-15) + + +### Bug Fixes + +* `codecs-unlock` patch and update Music patches to `5.14.53` ([2437d30](https://github.com/revanced/revanced-patches/commit/2437d3070f6a630d353619f642cefebd47abee20)) + +# [2.14.0](https://github.com/revanced/revanced-patches/compare/v2.13.6...v2.14.0) (2022-07-15) + + +### Features + +* `music-video-ads` patch ([#172](https://github.com/revanced/revanced-patches/issues/172)) ([cbb71b5](https://github.com/revanced/revanced-patches/commit/cbb71b5faf1386ed31dd9e0387f6cf0509d92d31)) + +## [2.13.6](https://github.com/revanced/revanced-patches/compare/v2.13.5...v2.13.6) (2022-07-14) + +## [2.13.5](https://github.com/revanced/revanced-patches/compare/v2.13.4...v2.13.5) (2022-07-14) + + +### Bug Fixes + +* `swipe-controls` with active engagement panel ([#177](https://github.com/revanced/revanced-patches/issues/177)) ([000ec6d](https://github.com/revanced/revanced-patches/commit/000ec6d8f6ecbb910a06ec852564ee9e5f03dcf8)) + +## [2.13.4](https://github.com/revanced/revanced-patches/compare/v2.13.3...v2.13.4) (2022-07-13) + + +### Bug Fixes + +* constrain `old-quality-layout` to older version ([add7232](https://github.com/revanced/revanced-patches/commit/add72326199e90f677b450b553b9d88c0bb4c490)) + +## [2.13.3](https://github.com/revanced/revanced-patches/compare/v2.13.2...v2.13.3) (2022-07-11) + + +### Bug Fixes + +* `exclusive-audio-playback` patch ([#153](https://github.com/revanced/revanced-patches/issues/153)) ([9beff95](https://github.com/revanced/revanced-patches/commit/9beff9567f1586e5c58690c1f1d2f7f204025ab7)) + +## [2.13.2](https://github.com/revanced/revanced-patches/compare/v2.13.1...v2.13.2) (2022-07-11) + + +### Bug Fixes + +* `hrd-auto-brightness` ([#152](https://github.com/revanced/revanced-patches/issues/152)) ([5f2e9ba](https://github.com/revanced/revanced-patches/commit/5f2e9ba30b7432be04bdc9f9f7ec7ac75fdc4b34)) + +## [2.13.1](https://github.com/revanced/revanced-patches/compare/v2.13.0...v2.13.1) (2022-07-11) + + +### Bug Fixes + +* bump youtube version for swipe-controls patch ([ff207a5](https://github.com/revanced/revanced-patches/commit/ff207a57af7d3c15a8127f4465e97da23878b0d6)) + +# [2.13.0](https://github.com/revanced/revanced-patches/compare/v2.12.4...v2.13.0) (2022-07-11) + + +### Features + +* `swipe-controls` rewrite ([#131](https://github.com/revanced/revanced-patches/issues/131)) ([b7dba09](https://github.com/revanced/revanced-patches/commit/b7dba09927ba15a9eacb06dcb4bf1f268560c96e)) + +## [2.12.4](https://github.com/revanced/revanced-patches/compare/v2.12.3...v2.12.4) (2022-07-11) + + +### Bug Fixes + +* `autorepeat-by-default` patch ([#148](https://github.com/revanced/revanced-patches/issues/148)) ([fe628ba](https://github.com/revanced/revanced-patches/commit/fe628ba909d89ea0bf3d95fe94ca78ef819677da)) + +## [2.12.3](https://github.com/revanced/revanced-patches/compare/v2.12.2...v2.12.3) (2022-07-11) + + +### Bug Fixes + +* listing of wrong fingerprint class ([#147](https://github.com/revanced/revanced-patches/issues/147)) ([95c2bbd](https://github.com/revanced/revanced-patches/commit/95c2bbdd1deb1d76f1177b48286fa6a3bc9f7663)) + +## [2.12.2](https://github.com/revanced/revanced-patches/compare/v2.12.1...v2.12.2) (2022-07-10) + + +### Bug Fixes + +* display codename for patch names ([10c53f7](https://github.com/revanced/revanced-patches/commit/10c53f720df3e70b9d59e8bc3219d56b996f03db)) +* incorrect package name in gradle task ([152b2c9](https://github.com/revanced/revanced-patches/commit/152b2c90cf102170648fcc168da10f46743bdc63)) +* invalid regex ([26bf1d8](https://github.com/revanced/revanced-patches/commit/26bf1d818f953abc061126d8b91f17cd9008ba1d)) + +## [2.12.1](https://github.com/revanced/revanced-patches/compare/v2.12.0...v2.12.1) (2022-07-10) + +# [2.12.0](https://github.com/revanced/revanced-patches/compare/v2.11.0...v2.12.0) (2022-07-10) + + +### Bug Fixes + +* update patches to `17.26.35` ([#142](https://github.com/revanced/revanced-patches/issues/142)) ([b04112c](https://github.com/revanced/revanced-patches/commit/b04112c8562a7b95e7555e894b665913094b33eb)) +* wording [skip ci] ([ba64d9e](https://github.com/revanced/revanced-patches/commit/ba64d9efc3ee606e9bda30ad7f8017af34b1dc3f)) + + +### Features + +* issue templates ([b82b0aa](https://github.com/revanced/revanced-patches/commit/b82b0aad88b7ab9d86f1bcc8e007f6a76a9aa1a5)) + +# [2.11.0](https://github.com/revanced/revanced-patches/compare/v2.10.2...v2.11.0) (2022-07-09) + + +### Features + +* `autorepeat-by-default` patch ([#106](https://github.com/revanced/revanced-patches/issues/106)) ([e0ac9f3](https://github.com/revanced/revanced-patches/commit/e0ac9f385fc360f4dd2451e26676633120356c10)) + +## [2.10.2](https://github.com/revanced/revanced-patches/compare/v2.10.1...v2.10.2) (2022-07-08) + + +### Bug Fixes + +* trigger release on `build` commits ([be8bd1b](https://github.com/revanced/revanced-patches/commit/be8bd1b2a4b91f9763448661a802a5dc4a6b1d1d)) + +## [2.10.1](https://github.com/revanced/revanced-patches/compare/v2.10.0...v2.10.1) (2022-07-08) + + +### Bug Fixes + +* patch description consistency ([#134](https://github.com/revanced/revanced-patches/issues/134)) ([da5896d](https://github.com/revanced/revanced-patches/commit/da5896dde0a2b2b9ffe65e486402e4ef92ec1ce9)) + +# [2.10.0](https://github.com/revanced/revanced-patches/compare/v2.9.4...v2.10.0) (2022-07-07) + + +### Features + +* implement `wide-searchbar` Patch ([#130](https://github.com/revanced/revanced-patches/issues/130)) ([332f4d1](https://github.com/revanced/revanced-patches/commit/332f4d12d06316d65db252a280fe1f263e65c3a8)) + +## [2.9.4](https://github.com/revanced/revanced-patches/compare/v2.9.3...v2.9.4) (2022-07-06) + + +### Bug Fixes + +* add 17.25.34 as supported version for swipe-controls patch ([4d84c19](https://github.com/revanced/revanced-patches/commit/4d84c1914f8ecf51cee25667219bc6cf635a6c1c)) + +## [2.9.3](https://github.com/revanced/revanced-patches/compare/v2.9.2...v2.9.3) (2022-07-06) + + +### Bug Fixes + +* Readd `swipe-controls` patch ([#123](https://github.com/revanced/revanced-patches/issues/123)) ([7f2a2b2](https://github.com/revanced/revanced-patches/commit/7f2a2b2ee4e6045d53aba4e7705431b643981107)) + +## [2.9.2](https://github.com/revanced/revanced-patches/compare/v2.9.1...v2.9.2) (2022-07-05) + + +### Bug Fixes + +* revert `swipe-controls` patch ([66e1f33](https://github.com/revanced/revanced-patches/commit/66e1f3384a58361737ba889d946be875b23f3163)) + +## [2.9.1](https://github.com/revanced/revanced-patches/compare/v2.9.0...v2.9.1) (2022-07-05) + + +### Bug Fixes + +* make `minimized-playback-manager-fingerprint` unique ([#120](https://github.com/revanced/revanced-patches/issues/120)) ([cd5e911](https://github.com/revanced/revanced-patches/commit/cd5e911f4ed9ad95b02c13c30cd9466d250e8904)) + +# [2.9.0](https://github.com/revanced/revanced-patches/compare/v2.8.2...v2.9.0) (2022-07-05) + + +### Features + +* `swipe-controls` patch ([#115](https://github.com/revanced/revanced-patches/issues/115)) ([1d0a7dc](https://github.com/revanced/revanced-patches/commit/1d0a7dcc0cc3ea2bcd8ce0221d5e2f53d6eb0ae5)) + +## [2.8.2](https://github.com/revanced/revanced-patches/compare/v2.8.1...v2.8.2) (2022-07-05) + + +### Bug Fixes + +* show minimized playback options in settings ([#118](https://github.com/revanced/revanced-patches/issues/118)) ([6e1a538](https://github.com/revanced/revanced-patches/commit/6e1a538d34291d75f19bf66a188bc69241de3a7a)) + +## [2.8.1](https://github.com/revanced/revanced-patches/compare/v2.8.0...v2.8.1) (2022-07-05) + + +### Bug Fixes + +* remove unnecessary version constraints ([#117](https://github.com/revanced/revanced-patches/issues/117)) ([1cddf8d](https://github.com/revanced/revanced-patches/commit/1cddf8d9063da3bbdba0fd7080c8c93768b83a4c)) + +# [2.8.0](https://github.com/revanced/revanced-patches/compare/v2.7.0...v2.8.0) (2022-07-04) + + +### Features + +* `enable-debugging` patch ([#116](https://github.com/revanced/revanced-patches/issues/116)) ([bb355e7](https://github.com/revanced/revanced-patches/commit/bb355e7b7e78e602a10b346fe7e5795463615a81)) + +# [2.7.0](https://github.com/revanced/revanced-patches/compare/v2.6.0...v2.7.0) (2022-07-03) + + +### Features + +* `hdr-max-brightness` patch ([#105](https://github.com/revanced/revanced-patches/issues/105)) ([1310573](https://github.com/revanced/revanced-patches/commit/131057366a777786d6016d3385584b4e17bc4a8b)) +* `hide-infocard-suggestions` patch ([#107](https://github.com/revanced/revanced-patches/issues/107)) ([31a767a](https://github.com/revanced/revanced-patches/commit/31a767adbb152906303ab0ae5250769fc38d0625)) + +# [2.6.0](https://github.com/revanced/revanced-patches/compare/v2.5.0...v2.6.0) (2022-07-02) + + +### Bug Fixes + +* freezing panels when watching video in fullscreen ([#89](https://github.com/revanced/revanced-patches/issues/89)) ([f5d4f6c](https://github.com/revanced/revanced-patches/commit/f5d4f6c3419916c6a9cf67babc6be8a64c854d3b)) +* invalid version in compatibility annotation ([#90](https://github.com/revanced/revanced-patches/issues/90)) ([df43547](https://github.com/revanced/revanced-patches/commit/df435475cdd0494a1e4ea9e2980c2998c9bc7048)) +* remove refreshing home screen not working ([6c24ebe](https://github.com/revanced/revanced-patches/commit/6c24ebef2fb4f0d58e369ac5bf63e4cab6ca0e80)) + + +### Features + +* migrate to breaking changes of patcher ([d9147cd](https://github.com/revanced/revanced-patches/commit/d9147cd60c0c25d0c5cc05409b8889dfacd89af9)) + +# [2.6.0-dev.2](https://github.com/revanced/revanced-patches/compare/v2.6.0-dev.1...v2.6.0-dev.2) (2022-07-02) + + +### Bug Fixes + +* remove refreshing home screen not working ([ec7ae90](https://github.com/revanced/revanced-patches/commit/ec7ae900181b6456c692adb3b5bb337e81bc5fea)) + +# [2.6.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.5.1-dev.1...v2.6.0-dev.1) (2022-07-02) + + +### Features + +* migrate to breaking changes of patcher ([a116852](https://github.com/revanced/revanced-patches/commit/a11685263fb2274c67684258e73c5247502cb010)) + +## [2.5.1-dev.1](https://github.com/revanced/revanced-patches/compare/v2.5.0...v2.5.1-dev.1) (2022-06-30) + + +### Bug Fixes + +* freezing panels when watching video in fullscreen ([#89](https://github.com/revanced/revanced-patches/issues/89)) ([f5d4f6c](https://github.com/revanced/revanced-patches/commit/f5d4f6c3419916c6a9cf67babc6be8a64c854d3b)) +* invalid version in compatibility annotation ([#90](https://github.com/revanced/revanced-patches/issues/90)) ([df43547](https://github.com/revanced/revanced-patches/commit/df435475cdd0494a1e4ea9e2980c2998c9bc7048)) + +# [2.5.0](https://github.com/revanced/revanced-patches/compare/v2.4.0...v2.5.0) (2022-06-30) + + +### Features + +* hide watermark support for 17.25.34 ([#87](https://github.com/revanced/revanced-patches/issues/87)) ([0cdb65b](https://github.com/revanced/revanced-patches/commit/0cdb65bbb3e7b9d75eb393ee87e3718bcd6af4b3)) + +# [2.4.0](https://github.com/revanced/revanced-patches/compare/v2.3.1...v2.4.0) (2022-06-30) + + +### Features + +* add youtube version 17.25.34 ([#85](https://github.com/revanced/revanced-patches/issues/85)) ([889c9d5](https://github.com/revanced/revanced-patches/commit/889c9d564d16c6e68a52095a4fc8e6d04346c9e9)) + +## [2.3.1](https://github.com/revanced/revanced-patches/compare/v2.3.0...v2.3.1) (2022-06-30) + + +### Bug Fixes + +* change fingerprint to work on latest youtube ([#80](https://github.com/revanced/revanced-patches/issues/80)) ([4dba323](https://github.com/revanced/revanced-patches/commit/4dba323ddf8980cd2b0908a0de41c4b4dea6b0d7)) + +# [2.3.0](https://github.com/revanced/revanced-patches/compare/v2.2.0...v2.3.0) (2022-06-30) + + +### Features + +* `disable-autoplay-button` patch ([#79](https://github.com/revanced/revanced-patches/issues/79)) ([0d6fb51](https://github.com/revanced/revanced-patches/commit/0d6fb51e025649aae37e230778ea367482fab0d7)) + +# [2.2.0](https://github.com/revanced/revanced-patches/compare/v2.1.0...v2.2.0) (2022-06-29) + + +### Features + +* make resource mapping patch aware of types ([#77](https://github.com/revanced/revanced-patches/issues/77)) ([188491a](https://github.com/revanced/revanced-patches/commit/188491a707abccc1164413f075d8a66c145a1455)) + +# [2.1.0](https://github.com/revanced/revanced-patches/compare/v2.0.3...v2.1.0) (2022-06-28) + + +### Features + +* `custom-playback-speed` patch ([#50](https://github.com/revanced/revanced-patches/issues/50)) ([224254b](https://github.com/revanced/revanced-patches/commit/224254bcce2b394bbfd2549089f0204ce4ed4a89)) + +## [2.0.3](https://github.com/revanced/revanced-patches/compare/v2.0.2...v2.0.3) (2022-06-27) + + +### Bug Fixes + +* check if resource files exist ([ba1f3af](https://github.com/revanced/revanced-patches/commit/ba1f3af99be58edc44ed1b8f1875508d5034efd8)) + +## [2.0.2](https://github.com/revanced/revanced-patches/compare/v2.0.1...v2.0.2) (2022-06-27) + + +### Bug Fixes + +* some more refactoring of integrations, add hide-watermark patch ([#63](https://github.com/revanced/revanced-patches/issues/63)) ([feb09c5](https://github.com/revanced/revanced-patches/commit/feb09c56f475e2537a67d3636b08737848158a8e)) + +## [2.0.1](https://github.com/revanced/revanced-patches/compare/v2.0.0...v2.0.1) (2022-06-26) + + +### Bug Fixes + +* invalid string in strings list ([f08b53b](https://github.com/revanced/revanced-patches/commit/f08b53b07d93bd8ac6e7da376ea6e6023e53076e)) +* migrate to new `proxy` api ([db32ffe](https://github.com/revanced/revanced-patches/commit/db32ffe56a8e73177bef724ee10eda9a28b367b8)) + +# [2.0.0](https://github.com/revanced/revanced-patches/compare/v1.11.0...v2.0.0) (2022-06-26) + + +### Code Refactoring + +* migrate from `Signature` to `Fingerprint` ([084078e](https://github.com/revanced/revanced-patches/commit/084078e7f1852ccd2045e3fa8aedc25a7fd5faa8)) + + +### BREAKING CHANGES + +* Not backwards compatible, since a lot of classes where renamed. + +# [1.11.0](https://github.com/revanced/revanced-patches/compare/v1.10.5...v1.11.0) (2022-06-26) + + +### Features + +* add youtube version 17.24.35 ([ec626cc](https://github.com/revanced/revanced-patches/commit/ec626ccfa2bcf14b722d08110382de009a1c12b4)) +* add youtube version 17.24.35 ([adc60a6](https://github.com/revanced/revanced-patches/commit/adc60a6fa09f6a21800ba51b8ca888d11a17e870)) + +## [1.10.5](https://github.com/revanced/revanced-patches/compare/v1.10.4...v1.10.5) (2022-06-25) + + +### Bug Fixes + +* put back proper variable ([#61](https://github.com/revanced/revanced-patches/issues/61)) ([d26c423](https://github.com/revanced/revanced-patches/commit/d26c4233031fd418eb37c8f05e9bc1857e0572e6)) + +## [1.10.4](https://github.com/revanced/revanced-patches/compare/v1.10.3...v1.10.4) (2022-06-23) + + +### Bug Fixes + +* missing brackets at inlining ([8936c8a](https://github.com/revanced/revanced-patches/commit/8936c8aaedb56817cda5eec5f4a8c32f433862aa)) + +## [1.10.3](https://github.com/revanced/revanced-patches/compare/v1.10.2...v1.10.3) (2022-06-23) + + +### Bug Fixes + +* get create button view register by more reliable means ([#59](https://github.com/revanced/revanced-patches/issues/59)) ([6ab821e](https://github.com/revanced/revanced-patches/commit/6ab821e377176f4e9f1b7ec2b58a924fa40299db)) + +## [1.10.2](https://github.com/revanced/revanced-patches/compare/v1.10.1...v1.10.2) (2022-06-23) + + +### Bug Fixes + +* `disable-create-button` not working with prebuilt jar file ([#55](https://github.com/revanced/revanced-patches/issues/55)) ([78be64a](https://github.com/revanced/revanced-patches/commit/78be64accc2023281c0c376849cdb0213622dc5c)) + +## [1.10.1](https://github.com/revanced/revanced-patches/compare/v1.10.0...v1.10.1) (2022-06-23) + + +### Bug Fixes + +* patcher not propagating dexlib ([980c486](https://github.com/revanced/revanced-patches/commit/980c48673259496d793bc7f864ad355188dcf7b6)) +* update patcher version ([e3d0bb7](https://github.com/revanced/revanced-patches/commit/e3d0bb7ee1923ea996cf637267c62d233a74c7fa)) + +# [1.10.0](https://github.com/revanced/revanced-patches/compare/v1.9.1...v1.10.0) (2022-06-23) + + +### Features + +* add compatibility for YouTube v17.23.36 and v17.24.34 ([1812bc3](https://github.com/revanced/revanced-patches/commit/1812bc39e0e88f1ab02ae8127e9000780eedf49c)) + +## [1.9.1](https://github.com/revanced/revanced-patches/compare/v1.9.0...v1.9.1) (2022-06-21) + + +### Bug Fixes + +* update patcher version ([5f54bc9](https://github.com/revanced/revanced-patches/commit/5f54bc9aa8fd8b83448141a9b05746e3e977369d)) + +# [1.9.0](https://github.com/revanced/revanced-patches/compare/v1.8.2...v1.9.0) (2022-06-21) + + +### Features + +* use `install` mode by default ([c2b2993](https://github.com/revanced/revanced-patches/commit/c2b299336a984d66a2d066e5ebe9c4f9bee5c2aa)) + +## [1.8.2](https://github.com/revanced/revanced-patches/compare/v1.8.1...v1.8.2) (2022-06-20) + + +### Bug Fixes + +* migrate to breaking changes from patcher ([2c0a419](https://github.com/revanced/revanced-patches/commit/2c0a4196fed2fbdcd454ed882b720898d3050c51)) +* old usage of `toInstructions` extension method ([65ddd52](https://github.com/revanced/revanced-patches/commit/65ddd522dca19e0590d9cb6fdb2d85ad7b98481e)) + +## [1.8.1](https://github.com/revanced/revanced-patches/compare/v1.8.0...v1.8.1) (2022-06-20) + + +### Bug Fixes + +* add execute permission to `./gradlew` file ([ff7a560](https://github.com/revanced/revanced-patches/commit/ff7a5602f68428111fea6c60cbea694592039ef1)) + +# [1.8.0](https://github.com/revanced/revanced-patches/compare/v1.7.0...v1.8.0) (2022-06-20) + + +### Features + +* bump compatibility of patches for Youtube to v17.23.35 ([3748d05](https://github.com/revanced/revanced-patches/commit/3748d0533e62a8871ab2202ce9b61170a90dae62)) + +# [1.7.0](https://github.com/revanced/revanced-patches/compare/v1.6.4...v1.7.0) (2022-06-20) + + +### Features + +* `disable-fullscreen-panels` patch ([3bf0561](https://github.com/revanced/revanced-patches/commit/3bf056163500b006d1a20c5f3a3e0c92fec13bd8)) + +## [1.6.4](https://github.com/revanced/revanced-patches/compare/v1.6.3...v1.6.4) (2022-06-19) + + +### Bug Fixes + +* update patcher version ([#35](https://github.com/revanced/revanced-patches/issues/35)) ([1a379df](https://github.com/revanced/revanced-patches/commit/1a379dfd974b9f92d4bd0d5d7a4711eb6d1060b3)), closes [#34](https://github.com/revanced/revanced-patches/issues/34) + +## [1.6.3](https://github.com/revanced/revanced-patches/compare/v1.6.2...v1.6.3) (2022-06-16) + + +### Bug Fixes + +* wrong dex path ([170fbbb](https://github.com/revanced/revanced-patches/commit/170fbbb99e4a2dbe3e0febe44d07a692aa9d7224)) + +## [1.6.2](https://github.com/revanced/revanced-patches/compare/v1.6.1...v1.6.2) (2022-06-16) + + +### Bug Fixes + +* broken gradle task ([91483a8](https://github.com/revanced/revanced-patches/commit/91483a8fbf92559d079dc52f846f5f871f5d6b5c)) + +## [1.6.1](https://github.com/revanced/revanced-patches/compare/v1.6.0...v1.6.1) (2022-06-16) + + +### Bug Fixes + +* broken gradle task ([4d07961](https://github.com/revanced/revanced-patches/commit/4d07961c8afd24da7f8879d11419147f2e100f05)) + +# [1.6.0](https://github.com/revanced/revanced-patches/compare/v1.5.3...v1.6.0) (2022-06-16) + + +### Bug Fixes + +* broken gradle task ([28e3f55](https://github.com/revanced/revanced-patches/commit/28e3f554ea6a7144416523fe48ce7adbb613b263)) + + +### Features + +* generate dex files using gradle task ([c34c1be](https://github.com/revanced/revanced-patches/commit/c34c1be21f50b4f720a7cd81e0dfe5ef6330caab)) + +## [1.5.3](https://github.com/revanced/revanced-patches/compare/v1.5.2...v1.5.3) (2022-06-16) + + +### Bug Fixes + +* forgot about this ([7102a25](https://github.com/revanced/revanced-patches/commit/7102a25dc618f19b324b01870d23f5418f375b2a)) + +## [1.5.2](https://github.com/revanced/revanced-patches/compare/v1.5.1...v1.5.2) (2022-06-16) + + +### Bug Fixes + +* dummy task for Gradle semantic-release plugin ([f6a8911](https://github.com/revanced/revanced-patches/commit/f6a8911906dfe52fcdb685daf7a02d6d0052cba9)) +* releases ([30d5c9a](https://github.com/revanced/revanced-patches/commit/30d5c9a67ccf88ca6ac00d0a9f2a2e330f8092dd)) + +## [1.5.1](https://github.com/revanced/revanced-patches/compare/v1.5.0...v1.5.1) (2022-06-16) + + +### Reverts + +* Revert "Changed app name to ReVanced (#21)" (#24) ([70a48c5](https://github.com/revanced/revanced-patches/commit/70a48c5f35cd236612352a1dbbf50487625e6e96)), closes [#21](https://github.com/revanced/revanced-patches/issues/21) [#24](https://github.com/revanced/revanced-patches/issues/24) + +# [1.5.0](https://github.com/revanced/revanced-patches/compare/v1.4.0...v1.5.0) (2022-06-15) + + +### Bug Fixes + +* sync version ([6170e36](https://github.com/revanced/revanced-patches/commit/6170e3689d9c8998be94a8464352af620cccd11b)) + + +### Features + +* `hide-cast-button` patch ([2cd531e](https://github.com/revanced/revanced-patches/commit/2cd531eb5a334f3cf91cba4556f07e863cd9ec1b)) + +# [1.4.0](https://github.com/revanced/revanced-patches/compare/v1.3.1...v1.4.0) (2022-06-15) + + +### Bug Fixes + +* add size `48px` for `custom-branding` patch ([f81872b](https://github.com/revanced/revanced-patches/commit/f81872b8e41da215517fdb59364130d8ce681607)) + + +### Features + +* `premium-heading` patch ([78913bf](https://github.com/revanced/revanced-patches/commit/78913bf1e80f5b91d0dee506fdfe3f875e8e6988)) + +## [1.3.1](https://github.com/revanced/revanced-patches/compare/v1.3.0...v1.3.1) (2022-06-15) + + +### Bug Fixes + +* `custom-branding` patch failing to get resources ([efb6d4c](https://github.com/revanced/revanced-patches/commit/efb6d4c2be515185fc9bd29c40ce202f0d684cee)) + +# [1.3.0](https://github.com/revanced/revanced-patches/compare/v1.2.2...v1.3.0) (2022-06-14) + + +### Features + +* `custom-branding` patch ([0d65ea8](https://github.com/revanced/revanced-patches/commit/0d65ea8cdb0e02287f6be6855cd3d28823a61e70)) + +## [1.2.2](https://github.com/revanced/revanced-patches/compare/v1.2.1...v1.2.2) (2022-06-13) + + +### Bug Fixes + +* environment variable not found in gradle build script ([0da15fb](https://github.com/revanced/revanced-patches/commit/0da15fb0effac0566d080d7b85e9fbe46c3dd34d)) + +## [1.2.1](https://github.com/revanced/revanced-patches/compare/v1.2.0...v1.2.1) (2022-06-12) + + +### Performance Improvements + +* fix high battery consumption due to chromecast not working with `microg` ([dd8b01a](https://github.com/revanced/revanced-patches/commit/dd8b01a5c5d75b00ea4d04ce35bc43942c1b0409)) + +# [1.2.0](https://github.com/revanced/revanced-patches/compare/v1.1.0...v1.2.0) (2022-06-11) + + +### Bug Fixes + +* migrate to `include` annotation ([110bbf1](https://github.com/revanced/revanced-patches/commit/110bbf143a9cec8dce1f0416cff40f8d93055e96)) + + +### Features + +* `microg-patch` ([48bbd57](https://github.com/revanced/revanced-patches/commit/48bbd574a52c8bf6834b26facfe7384b830f944a)) +* updated all patches to support v17.22.36 of `com.android.google.youtube` ([e12dc11](https://github.com/revanced/revanced-patches/commit/e12dc11b670c2b0c414741616e0a646e8421e418)) + +# [1.1.0](https://github.com/revanced/revanced-patches/compare/v1.0.0...v1.1.0) (2022-06-11) + + +### Features + +* generate & upload dex files ([#18](https://github.com/revanced/revanced-patches/issues/18)) ([e6f5355](https://github.com/revanced/revanced-patches/commit/e6f53553a98c164c4eb926fb273358ed506e00a4)) + +# 1.0.0 (2022-06-05) + + +### Bug Fixes + +* `create-button-signature` ([a173f6e](https://github.com/revanced/revanced-patches/commit/a173f6e5a7e65943657e2072e8a72a4a680e5277)) +* `enable-seekbar-tapping` patch ([52fd726](https://github.com/revanced/revanced-patches/commit/52fd726d9b0d2efbd0f9742fc84ad01ccdcff168)) +* `Index` in wrong package ([2f9360f](https://github.com/revanced/revanced-patches/commit/2f9360f57cc8415564534fbbd8bd5e2a83a1b629)) +* `minimized-playback` & `old-quality-layout` wrong opcodes ([b45d175](https://github.com/revanced/revanced-patches/commit/b45d175c6f1ece6da894ab16128c2644d262c9c7)) +* `minimized-playback` patch ([55677a4](https://github.com/revanced/revanced-patches/commit/55677a44ff965c0b92c3f1d771bd68c12c142ad4)) +* `SignatureChecker` not handling nullable field `methodMetadata` ([17bcf78](https://github.com/revanced/revanced-patches/commit/17bcf786a85ccf1f7d9f5a66a044a3c26def09bb)) +* accidentally removed code in refactor ([0077e26](https://github.com/revanced/revanced-patches/commit/0077e26d23cc112b671a41614a55348fac2c88ca)) +* add missing `trimIndent()` to string literals ([76d3c71](https://github.com/revanced/revanced-patches/commit/76d3c71b67edebd79f2cdb1bb28e4d2969d72223)) +* add missing opcode for `create-button-method` ([0a398ef](https://github.com/revanced/revanced-patches/commit/0a398ef364f91a0dd9608df1a036a2515476ccf2)) +* attempt on all patches ([3395d69](https://github.com/revanced/revanced-patches/commit/3395d69747103a4bdf314297aa0bfa6ef6a0fc36)) +* breaking changes by `revanced-patcher` dependency ([e12e484](https://github.com/revanced/revanced-patches/commit/e12e484e3796c5c9c8505b677838cdf8432f2e79)) +* breaking changes by `revanced-patcher` dependency ([7e485b4](https://github.com/revanced/revanced-patches/commit/7e485b4ffe204d724809aeb9bd9f693a35ded94d)) +* breaking changes of the patcher ([1a49bbd](https://github.com/revanced/revanced-patches/commit/1a49bbdbc4ff6f427934259536218e161908b449)) +* breaking patcher changes ([50f9cc5](https://github.com/revanced/revanced-patches/commit/50f9cc52acfd5bc23330ecd23d8d85678a9d3eee)) +* breaking patcher changes ([cbb9e2c](https://github.com/revanced/revanced-patches/commit/cbb9e2cd1fa829e1d1dd92dbd40131b11ae6a05b)) +* breaking patcher changes ([581d1b0](https://github.com/revanced/revanced-patches/commit/581d1b0ca7d15adcdb1ab6116ef035acfe701757)) +* bugfixes in `microg` ([a43b33b](https://github.com/revanced/revanced-patches/commit/a43b33bdbb2b36e0a8f991fa11dfeeec34de01f9)) +* clean after building ([a2df3fb](https://github.com/revanced/revanced-patches/commit/a2df3fbc9761b07f3010542fa8684ade00e4dc91)) +* disable `hide-suggestions-patch` patch until fixed ([99099ea](https://github.com/revanced/revanced-patches/commit/99099ea0bc12f5f25896967db642442df69d0c4f)) +* incorrect endIndex (fixed in Patcher) ([424788e](https://github.com/revanced/revanced-patches/commit/424788edd777110cdaff97500556d18628f33385)) +* loop in `amoled` patch ([c4c86b6](https://github.com/revanced/revanced-patches/commit/c4c86b65fd8b2463c1d86ad2e46ec9f08e60d47c)) +* migrate patches to latest patcher api changes ([8a0ee03](https://github.com/revanced/revanced-patches/commit/8a0ee03a71cf4a000c9a7246d0e64ed8291a5127)) +* missing extension method `doRecursively` ([e9c9460](https://github.com/revanced/revanced-patches/commit/e9c946008ee912652d288e515b83b52ae2d239d8)) +* modified opcode for `show-video-ads-constructor` ([a0dcea3](https://github.com/revanced/revanced-patches/commit/a0dcea3a13f68cae449dfaf445b542e339c83ff0)) +* multiple bugs in patches ([e37201d](https://github.com/revanced/revanced-patches/commit/e37201d0ceef474696857a0d8845950c888194d0)) +* name for `IntegrationsPatch` ([e46ef02](https://github.com/revanced/revanced-patches/commit/e46ef02302825d62b57912b2747a25f858036bb7)) +* print instruction index of warning ([9e29aee](https://github.com/revanced/revanced-patches/commit/9e29aeeeff222412f6c45cf7e4879f8ec53ca6ee)) +* publish releases ([83916f9](https://github.com/revanced/revanced-patches/commit/83916f96d27989dcbb35c0ba6ef326a16b470501)) +* remove `HideSuggestionsPatch` from `Index` ([f32e474](https://github.com/revanced/revanced-patches/commit/f32e4747b512c675b807ff5eebfd0b8e66173fba)) +* remove unused patches ([d12e92a](https://github.com/revanced/revanced-patches/commit/d12e92aead677fefa9dcb48748d783225b65fab1)) +* signature checker with changes of patcher ([e82459d](https://github.com/revanced/revanced-patches/commit/e82459d37759e1a5a860d3e7fcdf69d95b06858e)) +* spelling mistake ([52f9147](https://github.com/revanced/revanced-patches/commit/52f9147ee8d591f786397d174dc02a141d9250a9)) +* tests failing ([102793f](https://github.com/revanced/revanced-patches/commit/102793f24f8bf7c7fd254968b29d65da7b2b962f)) +* update `HomeAdsPatch` ([62f1801](https://github.com/revanced/revanced-patches/commit/62f1801e9cbee53c0be3413c245161bd941e4aec)) +* update patches ([91b8ec8](https://github.com/revanced/revanced-patches/commit/91b8ec81f33417798546c32db708fe09ada3930c)) +* use the latest version of patcher dependency ([fe4a439](https://github.com/revanced/revanced-patches/commit/fe4a439cb2bc5e385ae13e8e155f25bb15e74633)) +* version in package metadata of music ([b299205](https://github.com/revanced/revanced-patches/commit/b299205aa7cde82f1f55fc598de3ff8d80b8bcb0)) +* **VideoAds:** remove `istore1` opcode ([dc4ec57](https://github.com/revanced/revanced-patches/commit/dc4ec574414c5df959efa0ca8f1cd39a812fedf8)) +* write while reading resources and remove checking for "." in resource extensions ([7bc6094](https://github.com/revanced/revanced-patches/commit/7bc60943cb2350e89dac091ec9c98c5effd0b8a9)) +* wrong access flag in signature for `Create button patch` ([9fbb89d](https://github.com/revanced/revanced-patches/commit/9fbb89d05336a256a0759eea6095e073946c45e5)) +* wrong annotation and signature in patches ([a0fdee8](https://github.com/revanced/revanced-patches/commit/a0fdee81a6d6773603520e7c3040ae8637642d58)) +* wrong opcode for `create-button-method` ([3214650](https://github.com/revanced/revanced-patches/commit/32146506f139aebc44cd5faffb7706b8b9c21c3d)) +* wrong opcode pattern for `create-button-method` ([f4d8a85](https://github.com/revanced/revanced-patches/commit/f4d8a8525bc64b90748b21979d463977a21dcd85)) +* wrong opcode pattern for `enable-seekbar-tapping-signature` ([1d83395](https://github.com/revanced/revanced-patches/commit/1d833957ed3e01188770c85e3d84e483419bd797)) +* wrong opcode pattern for signature in `Hide suggestions patch` ([535aee0](https://github.com/revanced/revanced-patches/commit/535aee08408b990c80f5966c13fa84666a8b35d0)) +* wrong patches in `upgrade-tab-remover` ([5182290](https://github.com/revanced/revanced-patches/commit/518229031ceca049ad790f7b77b19405d39f0ce1)) +* wrong signature for `hide-reels-signature` ([2d9ff2a](https://github.com/revanced/revanced-patches/commit/2d9ff2af0a991d7721f3741187716a3b08bb4029)) +* wrong signatures for patch `Old Quality Layout Patch` ([823e503](https://github.com/revanced/revanced-patches/commit/823e503d84037bdf27b09f17e63383f963c76854)) +* wrong versions of patches ([a112b22](https://github.com/revanced/revanced-patches/commit/a112b22ce6e685204caab6f95f511e26ef95806b)) + + +### Code Refactoring + +* Rename `net.revanced` to `app.revanced` ([68ea89f](https://github.com/revanced/revanced-patches/commit/68ea89f15e9ea077df0d0ac20a40b735bb5ae26c)) + + +### Features + +* `Dependencies` annotation ([85806bb](https://github.com/revanced/revanced-patches/commit/85806bb355e342ecb33d4ee1e76f9edf89b2c2ee)) +* `GeneralBytecodeAds` and `GeneralResourceAds` patch ([f99bbef](https://github.com/revanced/revanced-patches/commit/f99bbef4c911ac2492166c7a3792ea11ffceffab)) +* `hide-shorts-button` patch ([88352ee](https://github.com/revanced/revanced-patches/commit/88352ee6ecd23faa4a7fd9f7495e67fa1d3e33bd)) +* `tastebuilder-remover` for music ([a6aeca3](https://github.com/revanced/revanced-patches/commit/a6aeca31bd80b8c4a8acd071e22faca6e136bdb0)) +* Add (WIP) Signature Checker ([ae4c7b2](https://github.com/revanced/revanced-patches/commit/ae4c7b29f211c461de460f97f3a8656e795adafb)) +* add `amoled` patch ([d61bac4](https://github.com/revanced/revanced-patches/commit/d61bac4f8243d0ef72ca91c7c1d5facd858d515e)) +* add home ads patch ([36cddd1](https://github.com/revanced/revanced-patches/commit/36cddd1488683e19e2b927e34c80a4f0f3cace35)) +* add patches compatibility to the newest version ([799401d](https://github.com/revanced/revanced-patches/commit/799401ddf99da0aaa5f52c39d3d3d4061370fd75)) +* add publishing to package registry ([b475e09](https://github.com/revanced/revanced-patches/commit/b475e09577db4dda7bbb45dbf170d78772834a6d)) +* add semantic-release ([d60f1d0](https://github.com/revanced/revanced-patches/commit/d60f1d06798d312b158b71691ecc87e828dccbc1)) +* added `codecs-unlock` patch ([e5fd7ce](https://github.com/revanced/revanced-patches/commit/e5fd7cece94b1ff5342178f59b29576db806e0f6)) +* begin `MicroG Patch` ([91474ba](https://github.com/revanced/revanced-patches/commit/91474ba07376c13e7a71685dfd8b6e6913ed5ee9)) +* display metadata for each signature in `SignatureChecker` ([736a71f](https://github.com/revanced/revanced-patches/commit/736a71fac21a32dbb1eef9c3a9f0d3005e7d9ca0)) +* get required register dynamically ([0924ca2](https://github.com/revanced/revanced-patches/commit/0924ca2ad30ae865dcc0fd484cb0da517e827352)) +* Initial commit ([bee5f2f](https://github.com/revanced/revanced-patches/commit/bee5f2faed882271ed059b0435e6e1aa91f93dbd)) +* integrations patch ([19c0b0d](https://github.com/revanced/revanced-patches/commit/19c0b0d194bb97c7248ea7a9b081176961653b9d)) +* migrate to dalvik patches ([e088c67](https://github.com/revanced/revanced-patches/commit/e088c671081bcf75586ccc1c4bdbed9366e93874)) +* MinimizedPlayback, CreateButtonRemover ([cc08c6c](https://github.com/revanced/revanced-patches/commit/cc08c6c3d38879dd4672ec671631b34aa2e3cc77)) +* OldQualityLayout, HideSuggestions, HideReels, EnableSeekbarTapping ([04a7cff](https://github.com/revanced/revanced-patches/commit/04a7cfff20d2734b1c92713de4e7e08a3b93ee85)) +* Patches for YouTube Music ([b60c9d3](https://github.com/revanced/revanced-patches/commit/b60c9d33b611bb4d5b55bb419652bc14b0309792)) +* remove obsolete patch `Hide suggestions patch` ([e65c6f2](https://github.com/revanced/revanced-patches/commit/e65c6f240ed23a54271d20a90fc57ec65cafc02d)) +* update MicroG patch to latest version ([c24f806](https://github.com/revanced/revanced-patches/commit/c24f8063a04f89aea2d2f7087a435738de7dfeae)) +* update patches to latest version ([bad25de](https://github.com/revanced/revanced-patches/commit/bad25dec1d73137f8b7a1bf4daaceb2279b4d48c)) +* use supplier instead of KClass ([08af6e5](https://github.com/revanced/revanced-patches/commit/08af6e54af79ef9ef4fb3372a348ce9b6fba4d20)) +* use supplier instead of KClass ([91aa019](https://github.com/revanced/revanced-patches/commit/91aa019f8d3d87fbf7affeb7abc2b02ba87af5c3)) + + +### BREAKING CHANGES + +* signature of patches was changed +* signature of patches was changed +* Package name was changed from "net.revanced" to "app.revanced" + +# [1.0.0-dev.16](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.15...v1.0.0-dev.16) (2022-06-05) + + +### Features + +* add patches compatibility to the newest version ([799401d](https://github.com/revanced/revanced-patches/commit/799401ddf99da0aaa5f52c39d3d3d4061370fd75)) + +# [1.0.0-dev.15](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.14...v1.0.0-dev.15) (2022-06-05) + + +### Bug Fixes + +* remove unused patches ([d12e92a](https://github.com/revanced/revanced-patches/commit/d12e92aead677fefa9dcb48748d783225b65fab1)) + +# [1.0.0-dev.14](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.13...v1.0.0-dev.14) (2022-06-04) + + +### Bug Fixes + +* spelling mistake ([52f9147](https://github.com/revanced/revanced-patches/commit/52f9147ee8d591f786397d174dc02a141d9250a9)) + + +### Features + +* `Dependencies` annotation ([85806bb](https://github.com/revanced/revanced-patches/commit/85806bb355e342ecb33d4ee1e76f9edf89b2c2ee)) +* `GeneralBytecodeAds` and `GeneralResourceAds` patch ([f99bbef](https://github.com/revanced/revanced-patches/commit/f99bbef4c911ac2492166c7a3792ea11ffceffab)) + +# [1.0.0-dev.13](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.12...v1.0.0-dev.13) (2022-05-31) + + +### Bug Fixes + +* migrate patches to latest patcher api changes ([8a0ee03](https://github.com/revanced/revanced-patches/commit/8a0ee03a71cf4a000c9a7246d0e64ed8291a5127)) +* missing extension method `doRecursively` ([e9c9460](https://github.com/revanced/revanced-patches/commit/e9c946008ee912652d288e515b83b52ae2d239d8)) + +# [1.0.0-dev.12](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2022-05-28) + + +### Bug Fixes + +* wrong annotation and signature in patches ([a0fdee8](https://github.com/revanced/revanced-patches/commit/a0fdee81a6d6773603520e7c3040ae8637642d58)) + +# [1.0.0-dev.11](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-26) + + +### Bug Fixes + +* breaking changes by `revanced-patcher` dependency ([7e485b4](https://github.com/revanced/revanced-patches/commit/7e485b4ffe204d724809aeb9bd9f693a35ded94d)) +* breaking patcher changes ([50f9cc5](https://github.com/revanced/revanced-patches/commit/50f9cc52acfd5bc23330ecd23d8d85678a9d3eee)) +* breaking patcher changes ([cbb9e2c](https://github.com/revanced/revanced-patches/commit/cbb9e2cd1fa829e1d1dd92dbd40131b11ae6a05b)) +* bugfixes in `microg` ([a43b33b](https://github.com/revanced/revanced-patches/commit/a43b33bdbb2b36e0a8f991fa11dfeeec34de01f9)) +* write while reading resources and remove checking for "." in resource extensions ([7bc6094](https://github.com/revanced/revanced-patches/commit/7bc60943cb2350e89dac091ec9c98c5effd0b8a9)) + + +### Features + +* begin `MicroG Patch` ([91474ba](https://github.com/revanced/revanced-patches/commit/91474ba07376c13e7a71685dfd8b6e6913ed5ee9)) +* update MicroG patch to latest version ([c24f806](https://github.com/revanced/revanced-patches/commit/c24f8063a04f89aea2d2f7087a435738de7dfeae)) + +# [1.0.0-dev.11](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2022-05-26) + + +### Bug Fixes + +* breaking changes by `revanced-patcher` dependency ([7e485b4](https://github.com/revanced/revanced-patches/commit/7e485b4ffe204d724809aeb9bd9f693a35ded94d)) +* breaking patcher changes ([50f9cc5](https://github.com/revanced/revanced-patches/commit/50f9cc52acfd5bc23330ecd23d8d85678a9d3eee)) +* breaking patcher changes ([cbb9e2c](https://github.com/revanced/revanced-patches/commit/cbb9e2cd1fa829e1d1dd92dbd40131b11ae6a05b)) +* bugfixes in `microg` ([a43b33b](https://github.com/revanced/revanced-patches/commit/a43b33bdbb2b36e0a8f991fa11dfeeec34de01f9)) +* write while reading resources and remove checking for "." in resource extensions ([7bc6094](https://github.com/revanced/revanced-patches/commit/7bc60943cb2350e89dac091ec9c98c5effd0b8a9)) + + +### Features + +* begin `MicroG Patch` ([91474ba](https://github.com/revanced/revanced-patches/commit/91474ba07376c13e7a71685dfd8b6e6913ed5ee9)) +* update MicroG patch to latest version ([c24f806](https://github.com/revanced/revanced-patches/commit/c24f8063a04f89aea2d2f7087a435738de7dfeae)) + +# [1.0.0-dev.10](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2022-05-22) + + +### Bug Fixes + +* `create-button-signature` ([a173f6e](https://github.com/revanced/revanced-patches/commit/a173f6e5a7e65943657e2072e8a72a4a680e5277)) +* breaking changes by `revanced-patcher` dependency ([e12e484](https://github.com/revanced/revanced-patches/commit/e12e484e3796c5c9c8505b677838cdf8432f2e79)) +* loop in `amoled` patch ([c4c86b6](https://github.com/revanced/revanced-patches/commit/c4c86b65fd8b2463c1d86ad2e46ec9f08e60d47c)) + + +### Features + +* add `amoled` patch ([d61bac4](https://github.com/revanced/revanced-patches/commit/d61bac4f8243d0ef72ca91c7c1d5facd858d515e)) +* update patches to latest version ([bad25de](https://github.com/revanced/revanced-patches/commit/bad25dec1d73137f8b7a1bf4daaceb2279b4d48c)) + +# [1.0.0-dev.9](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2022-05-13) + + +### Bug Fixes + +* clean after building ([a2df3fb](https://github.com/revanced/revanced-patches/commit/a2df3fbc9761b07f3010542fa8684ade00e4dc91)) + +# [1.0.0-dev.8](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2022-05-13) + + +### Bug Fixes + +* publish releases ([83916f9](https://github.com/revanced/revanced-patches/commit/83916f96d27989dcbb35c0ba6ef326a16b470501)) + +# [1.0.0-dev.7](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2022-05-07) + + +### Bug Fixes + +* use the latest version of patcher dependency ([fe4a439](https://github.com/revanced/revanced-patches/commit/fe4a439cb2bc5e385ae13e8e155f25bb15e74633)) + +# [1.0.0-dev.6](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2022-05-07) + + +### Bug Fixes + +* update patches ([91b8ec8](https://github.com/revanced/revanced-patches/commit/91b8ec81f33417798546c32db708fe09ada3930c)) + +# [1.0.0-dev.5](https://github.com/revanced/revanced-patches/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2022-05-07) + + +### Bug Fixes + +* `enable-seekbar-tapping` patch ([52fd726](https://github.com/revanced/revanced-patches/commit/52fd726d9b0d2efbd0f9742fc84ad01ccdcff168)) +* `Index` in wrong package ([2f9360f](https://github.com/revanced/revanced-patches/commit/2f9360f57cc8415564534fbbd8bd5e2a83a1b629)) +* `minimized-playback` & `old-quality-layout` wrong opcodes ([b45d175](https://github.com/revanced/revanced-patches/commit/b45d175c6f1ece6da894ab16128c2644d262c9c7)) +* `minimized-playback` patch ([55677a4](https://github.com/revanced/revanced-patches/commit/55677a44ff965c0b92c3f1d771bd68c12c142ad4)) +* `SignatureChecker` not handling nullable field `methodMetadata` ([17bcf78](https://github.com/revanced/revanced-patches/commit/17bcf786a85ccf1f7d9f5a66a044a3c26def09bb)) +* accidentally removed code in refactor ([0077e26](https://github.com/revanced/revanced-patches/commit/0077e26d23cc112b671a41614a55348fac2c88ca)) +* add missing `trimIndent()` to string literals ([76d3c71](https://github.com/revanced/revanced-patches/commit/76d3c71b67edebd79f2cdb1bb28e4d2969d72223)) +* add missing opcode for `create-button-method` ([0a398ef](https://github.com/revanced/revanced-patches/commit/0a398ef364f91a0dd9608df1a036a2515476ccf2)) +* attempt on all patches ([3395d69](https://github.com/revanced/revanced-patches/commit/3395d69747103a4bdf314297aa0bfa6ef6a0fc36)) +* breaking changes of the patcher ([1a49bbd](https://github.com/revanced/revanced-patches/commit/1a49bbdbc4ff6f427934259536218e161908b449)) +* breaking patcher changes ([581d1b0](https://github.com/revanced/revanced-patches/commit/581d1b0ca7d15adcdb1ab6116ef035acfe701757)) +* disable `hide-suggestions-patch` patch until fixed ([99099ea](https://github.com/revanced/revanced-patches/commit/99099ea0bc12f5f25896967db642442df69d0c4f)) +* incorrect endIndex (fixed in Patcher) ([424788e](https://github.com/revanced/revanced-patches/commit/424788edd777110cdaff97500556d18628f33385)) +* modified opcode for `show-video-ads-constructor` ([a0dcea3](https://github.com/revanced/revanced-patches/commit/a0dcea3a13f68cae449dfaf445b542e339c83ff0)) +* multiple bugs in patches ([e37201d](https://github.com/revanced/revanced-patches/commit/e37201d0ceef474696857a0d8845950c888194d0)) +* name for `IntegrationsPatch` ([e46ef02](https://github.com/revanced/revanced-patches/commit/e46ef02302825d62b57912b2747a25f858036bb7)) +* print instruction index of warning ([9e29aee](https://github.com/revanced/revanced-patches/commit/9e29aeeeff222412f6c45cf7e4879f8ec53ca6ee)) +* remove `HideSuggestionsPatch` from `Index` ([f32e474](https://github.com/revanced/revanced-patches/commit/f32e4747b512c675b807ff5eebfd0b8e66173fba)) +* signature checker with changes of patcher ([e82459d](https://github.com/revanced/revanced-patches/commit/e82459d37759e1a5a860d3e7fcdf69d95b06858e)) +* tests failing ([102793f](https://github.com/revanced/revanced-patches/commit/102793f24f8bf7c7fd254968b29d65da7b2b962f)) +* update `HomeAdsPatch` ([62f1801](https://github.com/revanced/revanced-patches/commit/62f1801e9cbee53c0be3413c245161bd941e4aec)) +* version in package metadata of music ([b299205](https://github.com/revanced/revanced-patches/commit/b299205aa7cde82f1f55fc598de3ff8d80b8bcb0)) +* **VideoAds:** remove `istore1` opcode ([dc4ec57](https://github.com/revanced/revanced-patches/commit/dc4ec574414c5df959efa0ca8f1cd39a812fedf8)) +* wrong access flag in signature for `Create button patch` ([9fbb89d](https://github.com/revanced/revanced-patches/commit/9fbb89d05336a256a0759eea6095e073946c45e5)) +* wrong opcode for `create-button-method` ([3214650](https://github.com/revanced/revanced-patches/commit/32146506f139aebc44cd5faffb7706b8b9c21c3d)) +* wrong opcode pattern for `create-button-method` ([f4d8a85](https://github.com/revanced/revanced-patches/commit/f4d8a8525bc64b90748b21979d463977a21dcd85)) +* wrong opcode pattern for `enable-seekbar-tapping-signature` ([1d83395](https://github.com/revanced/revanced-patches/commit/1d833957ed3e01188770c85e3d84e483419bd797)) +* wrong opcode pattern for signature in `Hide suggestions patch` ([535aee0](https://github.com/revanced/revanced-patches/commit/535aee08408b990c80f5966c13fa84666a8b35d0)) +* wrong patches in `upgrade-tab-remover` ([5182290](https://github.com/revanced/revanced-patches/commit/518229031ceca049ad790f7b77b19405d39f0ce1)) +* wrong signature for `hide-reels-signature` ([2d9ff2a](https://github.com/revanced/revanced-patches/commit/2d9ff2af0a991d7721f3741187716a3b08bb4029)) +* wrong signatures for patch `Old Quality Layout Patch` ([823e503](https://github.com/revanced/revanced-patches/commit/823e503d84037bdf27b09f17e63383f963c76854)) +* wrong versions of patches ([a112b22](https://github.com/revanced/revanced-patches/commit/a112b22ce6e685204caab6f95f511e26ef95806b)) + + +### Features + +* `hide-shorts-button` patch ([88352ee](https://github.com/revanced/revanced-patches/commit/88352ee6ecd23faa4a7fd9f7495e67fa1d3e33bd)) +* `tastebuilder-remover` for music ([a6aeca3](https://github.com/revanced/revanced-patches/commit/a6aeca31bd80b8c4a8acd071e22faca6e136bdb0)) +* Add (WIP) Signature Checker ([ae4c7b2](https://github.com/revanced/revanced-patches/commit/ae4c7b29f211c461de460f97f3a8656e795adafb)) +* add home ads patch ([36cddd1](https://github.com/revanced/revanced-patches/commit/36cddd1488683e19e2b927e34c80a4f0f3cace35)) +* added `codecs-unlock` patch ([e5fd7ce](https://github.com/revanced/revanced-patches/commit/e5fd7cece94b1ff5342178f59b29576db806e0f6)) +* display metadata for each signature in `SignatureChecker` ([736a71f](https://github.com/revanced/revanced-patches/commit/736a71fac21a32dbb1eef9c3a9f0d3005e7d9ca0)) +* get required register dynamically ([0924ca2](https://github.com/revanced/revanced-patches/commit/0924ca2ad30ae865dcc0fd484cb0da517e827352)) +* integrations patch ([19c0b0d](https://github.com/revanced/revanced-patches/commit/19c0b0d194bb97c7248ea7a9b081176961653b9d)) +* migrate to dalvik patches ([e088c67](https://github.com/revanced/revanced-patches/commit/e088c671081bcf75586ccc1c4bdbed9366e93874)) +* Patches for YouTube Music ([b60c9d3](https://github.com/revanced/revanced-patches/commit/b60c9d33b611bb4d5b55bb419652bc14b0309792)) +* remove obsolete patch `Hide suggestions patch` ([e65c6f2](https://github.com/revanced/revanced-patches/commit/e65c6f240ed23a54271d20a90fc57ec65cafc02d)) + +# [1.0.0-dev.4](https://github.com/ReVancedTeam/revanced-patches/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2022-03-22) + + +### Features + +* use supplier instead of KClass ([08af6e5](https://github.com/ReVancedTeam/revanced-patches/commit/08af6e54af79ef9ef4fb3372a348ce9b6fba4d20)) +* use supplier instead of KClass ([91aa019](https://github.com/ReVancedTeam/revanced-patches/commit/91aa019f8d3d87fbf7affeb7abc2b02ba87af5c3)) + + +### BREAKING CHANGES + +* signature of patches was changed +* signature of patches was changed + +# [1.0.0-dev.3](https://github.com/ReVancedTeam/revanced-patches/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2022-03-21) + + +### Features + +* OldQualityLayout, HideSuggestions, HideReels, EnableSeekbarTapping ([04a7cff](https://github.com/ReVancedTeam/revanced-patches/commit/04a7cfff20d2734b1c92713de4e7e08a3b93ee85)) + +# [1.0.0-dev.2](https://github.com/ReVancedTeam/revanced-patches/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2022-03-21) + + +### Code Refactoring + +* Rename `net.revanced` to `app.revanced` ([68ea89f](https://github.com/ReVancedTeam/revanced-patches/commit/68ea89f15e9ea077df0d0ac20a40b735bb5ae26c)) + + +### BREAKING CHANGES + +* Package name was changed from "net.revanced" to "app.revanced" + +# 1.0.0-dev.1 (2022-03-21) + + +### Features + +* add publishing to package registry ([b475e09](https://github.com/ReVancedTeam/revanced-patches/commit/b475e09577db4dda7bbb45dbf170d78772834a6d)) +* add semantic-release ([d60f1d0](https://github.com/ReVancedTeam/revanced-patches/commit/d60f1d06798d312b158b71691ecc87e828dccbc1)) +* Initial commit ([bee5f2f](https://github.com/ReVancedTeam/revanced-patches/commit/bee5f2faed882271ed059b0435e6e1aa91f93dbd)) +* MinimizedPlayback, CreateButtonRemover ([cc08c6c](https://github.com/ReVancedTeam/revanced-patches/commit/cc08c6c3d38879dd4672ec671631b34aa2e3cc77)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..95fb049 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + kotlin("jvm") version "1.7.0" +} + +group = "app.revanced" + +val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR") +val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN") + +repositories { + mavenCentral() + mavenLocal() + maven { + url = uri("https://maven.pkg.github.com/revanced/revanced-patcher") + credentials { + username = githubUsername + password = githubPassword + } + } +} + +dependencies { + implementation("app.revanced:revanced-patcher:6.3.2") + implementation("app.revanced:multidexlib2:2.5.2.r2") + // Required for meta + implementation("com.google.code.gson:gson:2.10") +} + +tasks { + register("generateBundle") { + description = "Generate dex files from build and bundle them in the jar file" + dependsOn(build) + + doLast { + val androidHome = System.getenv("ANDROID_HOME") ?: throw GradleException("ANDROID_HOME not found") + val d8 = "${androidHome}/build-tools/33.0.1/d8" + val input = configurations.archives.get().allArtifacts.files.files.first().absolutePath + val work = File("${buildDir}/libs") + + exec { + workingDir = work + commandLine = listOf(d8, input) + } + + exec { + workingDir = work + commandLine = listOf("zip", "-u", input, "classes.dex") + } + } + } + register("generateMeta") { + description = "Generate metadata for this bundle" + dependsOn(build) + + classpath = sourceSets["main"].runtimeClasspath + mainClass.set("app.revanced.meta.Meta") + } + // Dummy task to fix the Gradle semantic-release plugin. + // Remove this if you forked it to support building only. + // Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 + register("publish") { + group = "publish" + description = "Dummy task" + dependsOn(named("generateBundle"), named("generateMeta")) + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..cd9bb66 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +kotlin.code.style = official +version = 2.146.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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�cCgb#m`a|c{XyiMM^|2OP(X?bxKl6T=|ov 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 JgTXZr4*< \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..074f583 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6107 @@ +{ + "name": "revanced-patches", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@semantic-release/changelog": "^6.0.2", + "@semantic-release/git": "^10.0.1", + "gradle-semantic-release-plugin": "^1.7.4", + "semantic-release": "^19.0.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz", + "integrity": "sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==", + "dev": true, + "dependencies": { + "@octokit/types": "^8.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz", + "integrity": "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^8.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz", + "integrity": "sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==", + "dev": true, + "dependencies": { + "@octokit/types": "^8.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz", + "integrity": "sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==", + "dev": true, + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^8.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz", + "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz", + "integrity": "sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw==", + "dev": true, + "dependencies": { + "@octokit/types": "^8.0.0" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz", + "integrity": "sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw==", + "dev": true, + "dependencies": { + "@octokit/types": "^8.0.0", + "deprecation": "^2.3.1" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz", + "integrity": "sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^8.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz", + "integrity": "sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg==", + "dev": true, + "dependencies": { + "@octokit/types": "^8.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/rest": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz", + "integrity": "sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow==", + "dev": true, + "dependencies": { + "@octokit/core": "^4.1.0", + "@octokit/plugin-paginate-rest": "^5.0.0", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^6.7.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/types": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz", + "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^14.0.0" + } + }, + "node_modules/@semantic-release/changelog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.2.tgz", + "integrity": "sha512-jHqfTkoPbDEOAgAP18mGP53IxeMwxTISN+GwTRy9uLu58UjARoZU8ScCgWGeO2WPkEsm57H8AkyY02W2ntIlIw==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz", + "integrity": "sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.2.3", + "debug": "^4.0.0", + "import-from": "^4.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0-beta.1" + } + }, + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/github": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.0.7.tgz", + "integrity": "sha512-VtgicRIKGvmTHwm//iqTh/5NGQwsncOMR5vQK9pMT92Aem7dv37JFKKRuulUsAnUOIlO4G8wH3gPiBAA0iW0ww==", + "dev": true, + "dependencies": { + "@octokit/rest": "^19.0.0", + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "bottleneck": "^2.18.1", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "fs-extra": "^11.0.0", + "globby": "^11.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "issue-parser": "^6.0.0", + "lodash": "^4.17.4", + "mime": "^3.0.0", + "p-filter": "^2.0.0", + "p-retry": "^4.0.0", + "url-join": "^4.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0-beta.1" + } + }, + "node_modules/@semantic-release/npm": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.1.tgz", + "integrity": "sha512-I5nVZklxBzfMFwemhRNbSrkiN/dsH3c7K9+KSk6jUnq0rdLFUuJt7EBsysq4Ir3moajQgFkfEryEHPqiKJj20g==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "execa": "^5.0.0", + "fs-extra": "^10.0.0", + "lodash": "^4.17.15", + "nerf-dart": "^1.0.0", + "normalize-url": "^6.0.0", + "npm": "^8.3.0", + "rc": "^1.2.8", + "read-pkg": "^5.0.0", + "registry-auth-token": "^4.0.0", + "semver": "^7.1.2", + "tempy": "^1.0.0" + }, + "engines": { + "node": ">=16 || ^14.17" + }, + "peerDependencies": { + "semantic-release": ">=19.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz", + "integrity": "sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.2.3", + "debug": "^4.0.0", + "get-stream": "^6.0.0", + "import-from": "^4.0.0", + "into-stream": "^6.0.0", + "lodash": "^4.17.4", + "read-pkg-up": "^7.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0-beta.1" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/chalk": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz", + "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/conventional-changelog-angular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", + "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", + "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-commits-parser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", + "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-commits-parser/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/conventional-commits-parser/node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/env-ci": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz", + "integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "fromentries": "^1.3.2", + "java-properties": "^1.0.0" + }, + "engines": { + "node": ">=10.17" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "dependencies": { + "semver-regex": "^3.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==", + "dev": true, + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + } + }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/git-log-parser/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/gradle-semantic-release-plugin": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.7.4.tgz", + "integrity": "sha512-Mm2JnmodHxQlCmn0GtSi5j8z4PS2+2VBY993b9GuNGGM+JaIrr8T3tF9uTLR2nGH/oJba6a9jx1ZqVEJdOXDPw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/KengoTODA" + } + ], + "dependencies": { + "promisified-properties": "^2.0.3", + "split2": "^4.1.0" + }, + "engines": { + "node": ">=16 || ^14.17" + }, + "peerDependencies": { + "semantic-release": "^19.0.2" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hook-std": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", + "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", + "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true, + "engines": { + "node": ">=12.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "dev": true, + "dependencies": { + "text-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz", + "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/marked-terminal": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", + "dev": true, + "dependencies": { + "ansi-escapes": "^5.0.0", + "cardinal": "^2.1.1", + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "8.19.3", + "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.3.tgz", + "integrity": "sha512-0QjmyPtDxSyMWWD8I91QGbrgx9KzbV6C9FK1liEb/K0zppiZkr5KxXc990G+LzPwBHDfRjUBlO9T1qZ08vl9mA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/ci-detect", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/run-script", + "abbrev", + "archy", + "cacache", + "chalk", + "chownr", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "mkdirp", + "mkdirp-infer-owner", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "opener", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "read-package-json", + "read-package-json-fast", + "readdir-scoped-modules", + "rimraf", + "semver", + "ssri", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^5.6.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/config": "^4.2.1", + "@npmcli/fs": "^2.1.0", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/package-json": "^2.0.0", + "@npmcli/run-script": "^4.2.1", + "abbrev": "~1.1.1", + "archy": "~1.0.0", + "cacache": "^16.1.3", + "chalk": "^4.1.2", + "chownr": "^2.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.2", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.12", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "graceful-fs": "^4.2.10", + "hosted-git-info": "^5.2.1", + "ini": "^3.0.1", + "init-package-json": "^3.0.2", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "libnpmaccess": "^6.0.4", + "libnpmdiff": "^4.0.5", + "libnpmexec": "^4.0.14", + "libnpmfund": "^3.0.5", + "libnpmhook": "^8.0.4", + "libnpmorg": "^4.0.4", + "libnpmpack": "^4.1.3", + "libnpmpublish": "^6.0.5", + "libnpmsearch": "^5.0.4", + "libnpmteam": "^4.0.4", + "libnpmversion": "^3.0.7", + "make-fetch-happen": "^10.2.0", + "minimatch": "^5.1.0", + "minipass": "^3.1.6", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^9.1.0", + "nopt": "^6.0.0", + "npm-audit-report": "^3.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.1.0", + "npm-pick-manifest": "^7.0.2", + "npm-profile": "^6.2.0", + "npm-registry-fetch": "^13.3.1", + "npm-user-validate": "^1.0.1", + "npmlog": "^6.0.2", + "opener": "^1.5.2", + "p-map": "^4.0.0", + "pacote": "^13.6.2", + "parse-conflict-json": "^2.0.2", + "proc-log": "^2.0.1", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^5.0.2", + "read-package-json-fast": "^2.0.3", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.1", + "tar": "^6.1.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^2.0.0", + "validate-npm-package-name": "^4.0.0", + "which": "^2.0.2", + "write-file-atomic": "^4.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@gar/promisify": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "5.6.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/metavuln-calculator": "^3.0.1", + "@npmcli/move-file": "^2.0.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/package-json": "^2.0.0", + "@npmcli/query": "^1.2.0", + "@npmcli/run-script": "^4.1.3", + "bin-links": "^3.0.3", + "cacache": "^16.1.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^5.2.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "minimatch": "^5.1.0", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^6.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.0.0", + "npm-pick-manifest": "^7.0.2", + "npm-registry-fetch": "^13.0.0", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.0", + "treeverse": "^2.0.0", + "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/ci-detect": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "4.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^2.0.2", + "ini": "^3.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^6.0.0", + "proc-log": "^2.0.0", + "read-package-json-fast": "^2.0.3", + "semver": "^7.3.5", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^8.0.1", + "minimatch": "^5.0.1", + "read-package-json-fast": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^16.0.0", + "json-parse-even-better-errors": "^2.3.1", + "pacote": "^13.0.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "1.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.1.0", + "postcss-selector-parser": "^6.0.10", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/asap": { + "version": "2.0.6", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^5.0.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0", + "read-cmd-shim": "^3.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/bin-links/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "16.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mkdirp-infer-owner": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/debuglog": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/depd": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/dezalgo": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.12", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "8.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.10", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "5.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.0.1", + "promzard": "^0.3.0", + "read": "^1.0.7", + "read-package-json": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.10.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "5.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.4.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "4.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^2.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^5.0.1", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1", + "tar": "^6.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "4.0.14", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.6.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/fs": "^2.1.1", + "@npmcli/run-script": "^4.2.0", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^9.0.1", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "proc-log": "^2.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "semver": "^7.3.7", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "3.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.6.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "8.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "4.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/run-script": "^4.1.3", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "6.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "normalize-package-data": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0", + "semver": "^7.3.7", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/run-script": "^4.1.3", + "json-parse-even-better-errors": "^2.3.1", + "proc-log": "^2.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "7.13.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "10.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "3.3.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/mkdirp-infer-owner": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "5.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^2.0.0", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "13.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/npmlog": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/opener": { + "version": "1.5.2", + "dev": true, + "inBundle": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "13.6.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1", + "just-diff": "^5.0.1", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "0.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "1" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/read-package-json/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "3.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.3.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.7.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.11", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.1.11", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parsimmon": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz", + "integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promisified-properties": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-2.0.25.tgz", + "integrity": "sha512-FtyMDSNf3IwVBjVq4uj5JD4lG+YmOMzt/Huuc+VwPHVqbzim3/U04iMBnVH3lYgOc9s5Jj9sC4G0OVGoo2KbWA==", + "dev": true, + "dependencies": { + "parsimmon": "^1.13.0" + }, + "engines": { + "node": ">=14.16", + "npm": ">=7.12" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", + "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "dev": true, + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semantic-release": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", + "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==", + "dev": true, + "dependencies": { + "@semantic-release/commit-analyzer": "^9.0.2", + "@semantic-release/error": "^3.0.0", + "@semantic-release/github": "^8.0.0", + "@semantic-release/npm": "^9.0.0", + "@semantic-release/release-notes-generator": "^10.0.0", + "aggregate-error": "^3.0.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.0.0", + "env-ci": "^5.0.0", + "execa": "^5.0.0", + "figures": "^3.0.0", + "find-versions": "^4.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^2.0.0", + "hosted-git-info": "^4.0.0", + "lodash": "^4.17.21", + "marked": "^4.0.10", + "marked-terminal": "^5.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "p-reduce": "^2.0.0", + "read-pkg-up": "^7.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^3.1.1", + "signale": "^1.2.1", + "yargs": "^16.2.0" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": ">=16 || ^14.17" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-regex": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", + "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/signale/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", + "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", + "dev": true, + "dependencies": { + "del": "^6.0.0", + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/traverse": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", + "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fbd68f2 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "devDependencies": { + "@semantic-release/changelog": "^6.0.2", + "@semantic-release/git": "^10.0.1", + "gradle-semantic-release-plugin": "^1.7.4", + "semantic-release": "^19.0.5" + } +} diff --git a/patches.json b/patches.json new file mode 100644 index 0000000..934ee20 --- /dev/null +++ b/patches.json @@ -0,0 +1 @@ +[{"name":"always-autorepeat","description":"Always repeats the playing video again.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"background-play","description":"Enables playing music in the background.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"block-audio-ads","description":"Blocks audio ads in streams and VODs.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"block-embedded-ads","description":"Blocks embedded steam ads using services like TTV.lol or PurpleAdBlocker.","version":"0.0.1","excluded":false,"options":[],"dependencies":["block-video-ads","integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"block-video-ads","description":"Blocks video ads in streams and VODs.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"client-spoof","description":"Spoofs the YouTube or Vanced client to prevent playback issues.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]},{"name":"com.vanced.android.youtube","versions":[]}]},{"name":"codecs-unlock","description":"Adds more audio codec options. The new audio codecs usually result in better audio quality.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"comments","description":"Hides components related to comments.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","comments-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"compact-header","description":"Hides the music category bar at the top of the homepage.","version":"0.0.1","excluded":true,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"custom-branding","description":"Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).","version":"0.0.1","excluded":false,"options":[{"key":"appName","title":"Application Name","description":"The name of the application it will show on your home screen.","required":true,"choices":null},{"key":"iconPath","title":"App Icon Path","description":"A path containing mipmap resource folders with icons.","required":false,"choices":null}],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"custom-video-buffer","description":"Lets you change the buffers of videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"custom-video-speed","description":"Adds more video speed options.","version":"0.0.1","excluded":false,"options":[{"key":"granularity","title":"Video speed granularity","description":"The granularity of the video speeds. The higher the value, the more speeds will be available.","required":true,"choices":null},{"key":"min","title":"Minimum video speed","description":"The minimum video speed.","required":true,"choices":null},{"key":"max","title":"Maximum video speed","description":"The maximum video speed. Must be greater than the minimum video speed and smaller than 5.","required":true,"choices":null}],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"debug-mode","description":"Enables Twitch\u0027s internal debugging mode.","version":"0.0.1","excluded":true,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"debugging","description":"Adds debugging options.","version":"0.0.1","excluded":false,"options":[{"key":"debuggable","title":"App debugging","description":"Whether to make the app debuggable on Android.","required":false,"choices":null}],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"disable-ads","description":"Disables ads in HexEditor.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.myprog.hexedit","versions":[]}]},{"name":"disable-auto-captions","description":"Disable forced captions from being automatically enabled.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-auto-player-popup-panels","description":"Disable automatic popup panels (playlist or live chat) on video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-capture-restriction","description":"Allows capturing Spotify\u0027s audio output while screen sharing or screen recording.","version":"0.0.2","excluded":false,"options":[],"dependencies":["disable-capture-restriction-resource-patch"],"compatiblePackages":[{"name":"com.spotify.music","versions":[]}]},{"name":"disable-fullscreen-panels","description":"Disables video description and comments panel in fullscreen view.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-login-requirement","description":"Do not force login.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"disable-startup-shorts-player","description":"Disables playing YouTube Shorts when launching YouTube.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"disable-zoom-haptics","description":"Disables haptics when zooming.","version":"0.0.1","excluded":false,"options":[],"dependencies":["settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"downloads","description":"Enables downloading music and videos from YouTube.","version":"0.0.1","excluded":false,"options":[],"dependencies":["downloads-resource-patch","player-controls-bytecode-patch","video-id-hook"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"downloads","description":"Removes download restrictions and changes the default path to download to.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"dynamic-color","description":"Replaces the default Twitter Blue with the users Material You palette.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.twitter.android","versions":[]}]},{"name":"enable-wide-searchbar","description":"Replaces the search icon with a wide search bar. This will hide the YouTube logo when active.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"exclusive-audio-playback","description":"Enables the option to play music without video.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"feed-filter","description":"Filters tiktok videos: removing ads, removing livestreams.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"fix-google-login","description":"Allows logging in with a Google account.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"general-ads","description":"Removes general ads.","version":"0.0.1","excluded":false,"options":[],"dependencies":["GeneralAdsResourcePatch","VerticalScrollPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"general-reddit-ads","description":"Removes general ads from the Reddit frontpage and subreddits.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.reddit.frontpage","versions":[]}]},{"name":"hdr-auto-brightness","description":"Makes the brightness of HDR videos follow the system default.","version":"0.0.2","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-ads","description":"Removes ads from TikTok.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"hide-album-cards","description":"Hides the album cards below the artist description.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","hide-album-cards-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-artist-card","description":"Hides the artist card below the searchbar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["resource-mapping","LithoFilterPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-autoplay-button","description":"Hides the autoplay button in the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings","resource-mapping"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-captions-button","description":"Hides the captions button on video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-cast-button","description":"Hides the cast button in the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"hide-create-button","description":"Hides the create button in the navigation bar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","resource-mapping","settings","ResolvePivotBarFingerprintsPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-crowdfunding-box","description":"Hides the crowdfunding box between the player and video description.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","crowdfunding-box-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-email-address","description":"Hides the email address in the account switcher.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","hide-email-address-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-endscreen-cards","description":"Hides the suggested video cards at the end of a video in fullscreen.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","hide-endscreen-cards-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-get-premium","description":"Removes all \"Get Premium\" evidences from the avatar menu.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"hide-info-cards","description":"Hides info-cards in videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","HideInfocardsResourcePatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-my-mix","description":"Hides mix playlists.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-premium-navbar","description":"Removes the premium tab from the navbar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["resource-mapping"],"compatiblePackages":[{"name":"com.spotify.music","versions":[]}]},{"name":"hide-shorts-button","description":"Hides the shorts button on the navigation bar.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings","ResolvePivotBarFingerprintsPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-time-and-seekbar","description":"Hides progress bar and time counter on videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-video-buttons","description":"Adds options to hide action buttons under a video.","version":"0.0.1","excluded":false,"options":[],"dependencies":["resource-mapping","LithoFilterPatch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-watch-in-vr","description":"Hides the Watch in VR option in the player settings flyout panel.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"hide-watermark","description":"Hides creator\u0027s watermarks on videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"microg-support","description":"Allows YouTube ReVanced to run without root and under a different package name with Vanced MicroG.","version":"0.0.1","excluded":false,"options":[],"dependencies":["microg-resource-patch","hide-cast-button","client-spoof"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"minimized-playback","description":"Enables minimized and background playback.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"minimized-playback-music","description":"Enables minimized playback on Kids music.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"monochrome-icon","description":"Adds a monochrome icon.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.twitter.android","versions":[]}]},{"name":"music-microg-support","description":"Allows YouTube Music ReVanced to run without root and under a different package name.","version":"0.0.2","excluded":false,"options":[],"dependencies":["music-microg-resource-patch"],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"music-video-ads","description":"Removes ads in the music player.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"old-quality-layout","description":"Enables the original video quality flyout in the video player settings","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"open-links-directly","description":"Bypasses URL redirects and opens links directly inside YouTube app.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"playback-speed","description":"Enables the playback speed option for all videos.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"predictive-back-gesture","description":"Enables the predictive back gesture introduced on Android 13.","version":"0.0.1","excluded":true,"options":[],"dependencies":[],"compatiblePackages":[]},{"name":"premium-heading","description":"Shows premium branding on the home screen.","version":"0.0.1","excluded":false,"options":[],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"premium-icon-reddit","description":"Unlocks premium Reddit app icons.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.reddit.frontpage","versions":[]}]},{"name":"premium-unlock","description":"Unlocks premium functions.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"org.citra.citra_emu","versions":[]},{"name":"org.citra.citra_emu.canary","versions":[]}]},{"name":"pro-unlock","description":"Unlocks pro-only functions.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.backdrops.wallpapers","versions":[]}]},{"name":"promo-code-unlock","description":"Disables the validation of promo code. Any code will work to unlock all features.","version":"0.0.1","excluded":false,"options":[],"dependencies":["spoof-cert-patch"],"compatiblePackages":[{"name":"de.dwd.warnapp","versions":[]}]},{"name":"remember-video-quality","description":"Adds the ability to remember the video quality you chose in the video quality flyout.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","video-id-hook","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"remove-player-button-background","description":"Removes the background from the video player buttons.","version":"0.0.1","excluded":false,"options":[],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"return-youtube-dislike","description":"Shows the dislike count of videos using the Return YouTube Dislike API.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","video-id-hook","return-youtube-dislike-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"seekbar-tapping","description":"Enables tap-to-seek on the seekbar of the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"settings","description":"Adds settings for ReVanced to YouTube.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"settings","description":"Adds settings menu to Twitch.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings-resource-patch"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"settings","description":"Adds ReVanced settings to TikTok.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"show-deleted-messages","description":"Shows deleted chat messages behind a clickable spoiler.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"tv.twitch.android.app","versions":[]}]},{"name":"show-seekbar","description":"Shows progress bar for all video.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"sim-spoof","description":"Spoofs the information which is retrieved from the sim-card.","version":"0.0.1","excluded":true,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.ss.android.ugc.trill","versions":[]},{"name":"com.zhiliaoapp.musically","versions":[]}]},{"name":"sponsorblock","description":"Integrate SponsorBlock.","version":"0.0.1","excluded":false,"options":[],"dependencies":["video-information","player-controls-bytecode-patch","integrations","sponsorblock-resource-patch","video-id-hook"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"spotify-theme","description":"Applies a custom theme.","version":"0.0.1","excluded":false,"options":[{"key":"backgroundColor","title":"Background color","description":"The background color. Can be a hex color or a resource reference.","required":false,"choices":null},{"key":"accentColor","title":"Accent color","description":"The accent color (\u0027spotify green\u0027 by default). Can be a hex color or a resource reference.","required":false,"choices":null},{"key":"accentPressedColor","title":"Pressed accent for the dark theme","description":"The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.","required":false,"choices":null}],"dependencies":["locale-config-fix"],"compatiblePackages":[{"name":"com.spotify.music","versions":[]}]},{"name":"swipe-controls","description":"Adds volume and brightness swipe controls.","version":"0.0.3","excluded":false,"options":[],"dependencies":["integrations","player-type-hook","swipe-controls-resource-patch"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"tablet-mini-player","description":"Enables the tablet mini player layout.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]},{"name":"tasteBuilder-remover","description":"Removes the \"Tell us which artists you like\" card from the home screen.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"theme","description":"Applies a custom theme.","version":"0.0.1","excluded":true,"options":[{"key":"darkThemeBackgroundColor","title":"Background color for the dark theme","description":"The background color of the dark theme. Can be a hex color or a resource reference.","required":false,"choices":null},{"key":"lightThemeBackgroundColor","title":"Background color for the light theme","description":"The background color of the light theme. Can be a hex color or a resource reference.","required":false,"choices":null}],"dependencies":["litho-components-theme","locale-config-fix"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":[]}]},{"name":"timeline-ads","description":"Removes ads from the Twitter timeline. Might require clearing app data to remove already cached ads.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.twitter.android","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"co.windyapp.android","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all professional features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"org.totschnig.myexpenses","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.awedea.nyx","versions":[]}]},{"name":"unlock-pro","description":"Unlocks pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ithebk.expensemanager","versions":[]}]},{"name":"unlock-pro","description":"Unlocks all pro features.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"ginlemon.iconpackstudio","versions":[]}]},{"name":"unlock-themes","description":"Unlocks all themes.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.ticktick.task","versions":[]}]},{"name":"upgrade-button-remover","description":"Removes the upgrade tab from the pivot bar.","version":"0.0.1","excluded":false,"options":[],"dependencies":[],"compatiblePackages":[{"name":"com.google.android.apps.youtube.music","versions":["5.14.53","5.16.51","5.17.51","5.21.52","5.22.54","5.23.50","5.25.51","5.25.52","5.26.52","5.27.51","5.28.52","5.29.52","5.31.50","5.34.51","5.36.51"]}]},{"name":"video-ads","description":"Removes ads in the video player.","version":"0.0.1","excluded":false,"options":[],"dependencies":["integrations","settings","fix-playback"],"compatiblePackages":[{"name":"com.google.android.youtube","versions":["17.49.37"]}]}] \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..472c476 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "revanced-patches" diff --git a/src/main/kotlin/app/revanced/extensions/Extensions.kt b/src/main/kotlin/app/revanced/extensions/Extensions.kt new file mode 100644 index 0000000..7c70f90 --- /dev/null +++ b/src/main/kotlin/app/revanced/extensions/Extensions.kt @@ -0,0 +1,44 @@ +package app.revanced.extensions + +import app.revanced.patcher.extensions.MethodFingerprintExtensions.name +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.util.proxy.mutableTypes.MutableClass +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.util.MethodUtil +import org.w3c.dom.Node + +// TODO: populate this to all patches +/** + * Convert a [MethodFingerprint] to a [PatchResultError]. + * + * @return A [PatchResultError] for the [MethodFingerprint]. + */ +fun MethodFingerprint.toErrorResult() = PatchResultError("Failed to resolve $name") + +/** + * Find the [MutableMethod] from a given [Method] in a [MutableClass]. + * + * @param method The [Method] to find. + * @return The [MutableMethod]. + */ +fun MutableClass.findMutableMethodOf(method: Method) = this.methods.first { + MethodUtil.methodSignaturesMatch(it, method) +} + +/** + * apply a transform to all methods of the class + * + * @param transform the transformation function. original method goes in, transformed method goes out + */ +fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) { + val transformedMethods = methods.map { it.transform() } + methods.clear() + methods.addAll(transformedMethods) +} + +internal fun Node.doRecursively(action: (Node) -> Unit) { + action(this) + for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/meta/Meta.kt b/src/main/kotlin/app/revanced/meta/Meta.kt new file mode 100644 index 0000000..6be5ed0 --- /dev/null +++ b/src/main/kotlin/app/revanced/meta/Meta.kt @@ -0,0 +1,27 @@ +package app.revanced.meta + +import app.revanced.meta.json.generateJson +import app.revanced.meta.readme.generateText +import app.revanced.patcher.data.Context +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.util.patch.PatchBundle +import java.io.File + +typealias Bundle = List>> + +object Meta { + @JvmStatic + fun main(args: Array) { + val patches = accumulatePatches() + if (patches.isEmpty()) throw IllegalStateException("No patches found") + + generateText(patches) + generateJson(patches) + } +} + +fun accumulatePatches() = PatchBundle.Jar( + File("build/libs/").listFiles()!!.first { + it.name.startsWith("revanced-patches-") && it.name.endsWith(".jar") + }.absolutePath +).loadPatches() \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/meta/json/Generator.kt b/src/main/kotlin/app/revanced/meta/json/Generator.kt new file mode 100644 index 0000000..cc762ee --- /dev/null +++ b/src/main/kotlin/app/revanced/meta/json/Generator.kt @@ -0,0 +1,48 @@ +package app.revanced.meta.json + +import app.revanced.meta.Bundle +import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages +import app.revanced.patcher.extensions.PatchExtensions.dependencies +import app.revanced.patcher.extensions.PatchExtensions.description +import app.revanced.patcher.extensions.PatchExtensions.include +import app.revanced.patcher.extensions.PatchExtensions.options +import app.revanced.patcher.extensions.PatchExtensions.patchName +import app.revanced.patcher.extensions.PatchExtensions.version +import app.revanced.patcher.patch.PatchOption +import com.google.gson.GsonBuilder +import java.io.File + +private val gson = GsonBuilder().serializeNulls().create() + +fun generateJson(bundle: Bundle) { + val patches = bundle.map { + JsonPatch( + it.patchName, + it.description ?: "This patch has no description.", + it.version ?: "0.0.0", + !it.include, + it.options?.map { option -> + Option( + option.key, + option.title, + option.description, + option.required, + option.let { lo -> + if (lo is PatchOption.ListOption<*>) { + lo.options.toMutableList().toTypedArray() + } else null + } + ) + }?.toTypedArray() ?: emptyArray(), + it.dependencies?.map { dep -> + dep.java.patchName + }?.toTypedArray() ?: emptyArray(), + it.compatiblePackages?.map { pkg -> + CompatiblePackage(pkg.name, pkg.versions) + }?.toTypedArray() ?: emptyArray() + ) + } + + val json = File("patches.json") + json.writeText(gson.toJson(patches)) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/meta/json/JsonPatch.kt b/src/main/kotlin/app/revanced/meta/json/JsonPatch.kt new file mode 100644 index 0000000..8a0d597 --- /dev/null +++ b/src/main/kotlin/app/revanced/meta/json/JsonPatch.kt @@ -0,0 +1,26 @@ +@file:Suppress("ArrayInDataClass") // We don't need it here. + +package app.revanced.meta.json + +data class JsonPatch( + val name: String, + val description: String, + val version: String, + val excluded: Boolean, + val options: Array

\n") + + output.appendLine(TABLE_HEADER) + pkg.value.forEach { output.appendLine("| `${it.patchName}` | ${it.description} | ${it.getLatestVersion() ?: "all"} |") } + + output.appendLine("
\n") + } + + val readmeTemplate = Template(File("README-template.md").readText()) + readmeTemplate.replaceVariable(TABLE_REGEX, output.toString()) + + val readme = File("README.md") + readme.writeText(readmeTemplate.toString()) +} diff --git a/src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt b/src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt new file mode 100644 index 0000000..6c461f2 --- /dev/null +++ b/src/main/kotlin/app/revanced/meta/readme/SemanticVersion.kt @@ -0,0 +1,22 @@ +package app.revanced.meta.readme + +data class SemanticVersion(val major: Int, val minor: Int, val patch: Int) { + companion object { + fun fromString(version: String): SemanticVersion { + val parts = version.split(".") + if (parts.count() != 3) throw IllegalArgumentException("Invalid semantic version") + val versionNumbers = parts.map { it.toInt() } + return SemanticVersion(versionNumbers[0], versionNumbers[1], versionNumbers[2]) + } + } + + override fun toString(): String = "$major.$minor.$patch" +} + +object SemanticVersionComparator : Comparator { + override fun compare(a: SemanticVersion, b: SemanticVersion): Int = when { + a.major != b.major -> a.major - b.major + a.minor != b.minor -> a.minor - b.minor + else -> a.patch - b.patch + } +} diff --git a/src/main/kotlin/app/revanced/meta/readme/Template.kt b/src/main/kotlin/app/revanced/meta/readme/Template.kt new file mode 100644 index 0000000..3b952c9 --- /dev/null +++ b/src/main/kotlin/app/revanced/meta/readme/Template.kt @@ -0,0 +1,12 @@ +package app.revanced.meta.readme + +class Template(template: String) { + val result = StringBuilder(template) + + fun replaceVariable(regex: Regex, value: String) { + val range = regex.find(result)!!.range + result.replace(range.first, range.last + 1, value) + } + + override fun toString(): String = result.toString() +} diff --git a/src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt b/src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt new file mode 100644 index 0000000..05beb7b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/interaction/gestures/patch/PredictiveBackGesturePatch.kt @@ -0,0 +1,37 @@ +package app.revanced.patches.all.interaction.gestures.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.Patch + +@Patch(false) +@Name("predictive-back-gesture") +@Description("Enables the predictive back gesture introduced on Android 13.") +@Version("0.0.1") +class PredictiveBackGesturePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + context.xmlEditor["AndroidManifest.xml"].use { editor -> + val document = editor.file + + with(document.getElementsByTagName("application").item(0)) { + if (attributes.getNamedItem(FLAG) != null) return@with + + document.createAttribute(FLAG) + .apply { value = "true" } + .let(attributes::setNamedItem) + + } + } + + return PatchResultSuccess() + } + + private companion object { + const val FLAG = "android:enableOnBackInvokedCallback" + } +} diff --git a/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt new file mode 100644 index 0000000..60b4832 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/annotations/ProUnlockCompatibility.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.backdrops.misc.pro.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.backdrops.wallpapers")]) +internal annotation class ProUnlockCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt new file mode 100644 index 0000000..7deff0a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/fingerprints/ProUnlockFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.backdrops.misc.pro.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object ProUnlockFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ + ), + customFingerprint = { it.definingClass == "Lcom/backdrops/wallpapers/data/local/DatabaseHandlerIAB;" && it.name == "lambda\$existPurchase\$0" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt new file mode 100644 index 0000000..0365284 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/backdrops/misc/pro/patch/ProUnlockPatch.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.backdrops.misc.pro.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.MethodFingerprintExtensions.name +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.backdrops.misc.pro.annotations.ProUnlockCompatibility +import app.revanced.patches.backdrops.misc.pro.fingerprints.ProUnlockFingerprint +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@Name("pro-unlock") +@Description("Unlocks pro-only functions.") +@ProUnlockCompatibility +@Version("0.0.1") +class ProUnlockPatch : BytecodePatch( + listOf(ProUnlockFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = ProUnlockFingerprint.result ?: return PatchResultError("${ProUnlockFingerprint.name} not found") + + val moveRegisterInstruction = result.mutableMethod.instruction(result.scanResult.patternScanResult!!.endIndex - 1) + val register = (moveRegisterInstruction as OneRegisterInstruction).registerA + + result.mutableMethod.addInstructions( + result.scanResult.patternScanResult!!.endIndex, + """ + const/4 v$register, 0x1 + """ + ) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt b/src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt new file mode 100644 index 0000000..a40ee14 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/citra/misc/premium/annotations/PremiumUnlockCompatbility.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.citra.misc.premium.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("org.citra.citra_emu"), Package("org.citra.citra_emu.canary")]) +internal annotation class PremiumUnlockCompatbility diff --git a/src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt b/src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt new file mode 100644 index 0000000..347c31a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/citra/misc/premium/fingerprints/PremiumUnlockFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.citra.misc.premium.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PremiumUnlockFingerprint : MethodFingerprint( + customFingerprint = { it.definingClass == "Lorg/citra/citra_emu/ui/main/MainActivity;" && it.name == "isPremiumActive" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt new file mode 100644 index 0000000..10d4eeb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/citra/misc/premium/patch/PremiumUnlockPatch.kt @@ -0,0 +1,37 @@ +package app.revanced.patches.citra.misc.premium.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.MethodFingerprintExtensions.name +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patches.citra.misc.premium.annotations.PremiumUnlockCompatbility +import app.revanced.patches.citra.misc.premium.fingerprints.PremiumUnlockFingerprint + +@Patch +@Name("premium-unlock") +@Description("Unlocks premium functions.") +@PremiumUnlockCompatbility +@Version("0.0.1") +class PremiumUnlockPatch : BytecodePatch( + listOf(PremiumUnlockFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = PremiumUnlockFingerprint.result ?: return PatchResultError("${PremiumUnlockFingerprint.name} not found") + + result.mutableMethod.addInstructions( + 0, + """ + const v0, 0x1 + return v0 + """ + ) + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt new file mode 100644 index 0000000..e0ae0e0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/hexeditor/ad/annotations/HexEditorAdsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.hexeditor.ad.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.myprog.hexedit") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HexEditorAdsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt new file mode 100644 index 0000000..34c3e94 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/hexeditor/ad/fingerprints/PrimaryAdsFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.hexeditor.ad.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PrimaryAdsFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("PreferencesHelper;") && methodDef.name == "isAdsDisabled" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt b/src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt new file mode 100644 index 0000000..67bc094 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/hexeditor/ad/patch/HexEditorAdsPatch.kt @@ -0,0 +1,39 @@ +package app.revanced.patches.hexeditor.ad.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.removeInstruction +import app.revanced.patcher.extensions.replaceInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.hexeditor.ad.annotations.HexEditorAdsCompatibility +import app.revanced.patches.hexeditor.ad.fingerprints.PrimaryAdsFingerprint + +@Patch +@Name("disable-ads") +@Description("Disables ads in HexEditor.") +@HexEditorAdsCompatibility +@Version("0.0.1") +class HexEditorAdsPatch : BytecodePatch( + listOf( + PrimaryAdsFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = PrimaryAdsFingerprint.result!!.mutableMethod + + method.replaceInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt new file mode 100644 index 0000000..649e066 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/annotations/UnlockProCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.iconpackstudio.misc.pro.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("ginlemon.iconpackstudio")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnlockProCompatibility diff --git a/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt new file mode 100644 index 0000000..945ef2c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/fingerprints/CheckProFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.iconpackstudio.misc.pro.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CheckProFingerprint : MethodFingerprint( + "Z", + customFingerprint = { it.definingClass.endsWith("IPSPurchaseRepository;")} +) diff --git a/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt new file mode 100644 index 0000000..a4a2d4f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/iconpackstudio/misc/pro/patch/UnlockProPatch.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.iconpackstudio.misc.pro.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.iconpackstudio.misc.pro.annotations.UnlockProCompatibility +import app.revanced.patches.iconpackstudio.misc.pro.fingerprints.CheckProFingerprint + +@Patch +@Name("unlock-pro") +@Description("Unlocks all pro features.") +@UnlockProCompatibility +@Version("0.0.1") +class UnlockProPatch : BytecodePatch( + listOf( + CheckProFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = CheckProFingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt new file mode 100644 index 0000000..8a46c0e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/moneymanager/annotations/UnlockProCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.moneymanager.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.ithebk.expensemanager")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnlockProCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt b/src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt new file mode 100644 index 0000000..9ea14f8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/moneymanager/fingerprints/UnlockProFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.moneymanager.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 UnlockProFingerprint : MethodFingerprint( + "Z", + AccessFlags.STATIC or AccessFlags.SYNTHETIC, + parameters = listOf("L"), + opcodes = listOf( + Opcode.IGET_BOOLEAN, + Opcode.RETURN + ), + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("MainActivity;") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt new file mode 100644 index 0000000..e857e71 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/moneymanager/patch/UnlockProPatch.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.moneymanager.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.moneymanager.annotations.UnlockProCompatibility +import app.revanced.patches.moneymanager.fingerprints.UnlockProFingerprint + +@Patch +@Name("unlock-pro") +@Description("Unlocks pro features.") +@UnlockProCompatibility +@Version("0.0.1") +class UnlockProPatch : BytecodePatch( + listOf(UnlockProFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + UnlockProFingerprint.result!!.mutableMethod.addInstructions( + 0, + """ + const/4 v0, 0x1 + + return v0 + """ + ) + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt new file mode 100644 index 0000000..314e159 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/ad/video/annotations/MusicVideoAdsCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.ad.video.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MusicVideoAdsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt new file mode 100644 index 0000000..dd60e20 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsConstructorFingerprint.kt @@ -0,0 +1,27 @@ +package app.revanced.patches.music.ad.video.fingerprints +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object ShowMusicVideoAdsConstructorFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L"), listOf( + Opcode.INVOKE_DIRECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.CONST_4, + Opcode.IPUT_BOOLEAN, + Opcode.RETURN_VOID + ) +) diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt new file mode 100644 index 0000000..d6d8a13 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/ad/video/fingerprints/ShowMusicVideoAdsFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.music.ad.video.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 ShowMusicVideoAdsFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("Z"), listOf( + Opcode.IPUT_BOOLEAN, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ) +) diff --git a/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt new file mode 100644 index 0000000..bbf4848 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/ad/video/patch/MusicVideoAdsPatch.kt @@ -0,0 +1,40 @@ +package app.revanced.patches.music.ad.video.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.addInstructions +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.Patch +import app.revanced.patches.music.ad.video.annotations.MusicVideoAdsCompatibility +import app.revanced.patches.music.ad.video.fingerprints.ShowMusicVideoAdsConstructorFingerprint +import app.revanced.patches.music.ad.video.fingerprints.ShowMusicVideoAdsFingerprint + +@Patch +@Name("music-video-ads") +@Description("Removes ads in the music player.") +@MusicVideoAdsCompatibility +@Version("0.0.1") +class MusicVideoAdsPatch : BytecodePatch( + listOf( + ShowMusicVideoAdsConstructorFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + ShowMusicVideoAdsFingerprint.resolve(context, ShowMusicVideoAdsConstructorFingerprint.result!!.classDef) + + val result = ShowMusicVideoAdsFingerprint.result!! + + result.mutableMethod.addInstructions( + result.scanResult.patternScanResult!!.startIndex, """ + const/4 p1, 0x0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt new file mode 100644 index 0000000..5fd4bca --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/annotations/CodecsUnlockCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.audio.codecs.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CodecsUnlockCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt new file mode 100644 index 0000000..92f047d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/AllCodecsReferenceFingerprint.kt @@ -0,0 +1,52 @@ +package app.revanced.patches.music.audio.codecs.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object AllCodecsReferenceFingerprint : MethodFingerprint( + "J", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.CONST_4, + Opcode.IF_EQZ, + Opcode.IGET_BOOLEAN, + Opcode.IF_NEZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.IPUT_BOOLEAN, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.GOTO, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.IGET_BOOLEAN, + Opcode.IF_NEZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.IPUT_BOOLEAN, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.GOTO, + Opcode.MOVE_EXCEPTION, + Opcode.INVOKE_SUPER, + Opcode.MOVE_RESULT_WIDE, + Opcode.RETURN_WIDE + ), listOf("itag") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt new file mode 100644 index 0000000..75189e8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/fingerprints/CodecsLockFingerprint.kt @@ -0,0 +1,29 @@ +package app.revanced.patches.music.audio.codecs.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object CodecsLockFingerprint : MethodFingerprint( + "L", AccessFlags.PUBLIC or AccessFlags.STATIC, opcodes = listOf( + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.INVOKE_DIRECT, + Opcode.RETURN_OBJECT + ), + strings = listOf("eac3_supported") +) diff --git a/src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt new file mode 100644 index 0000000..9ff6916 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/codecs/patch/CodecsUnlockPatch.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.music.audio.codecs.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.data.toMethodWalker +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.smali.toInstruction +import app.revanced.patches.music.audio.codecs.annotations.CodecsUnlockCompatibility +import app.revanced.patches.music.audio.codecs.fingerprints.AllCodecsReferenceFingerprint +import app.revanced.patches.music.audio.codecs.fingerprints.CodecsLockFingerprint +import org.jf.dexlib2.Opcode + +@Patch +@Name("codecs-unlock") +@Description("Adds more audio codec options. The new audio codecs usually result in better audio quality.") +@CodecsUnlockCompatibility +@Version("0.0.1") +class CodecsUnlockPatch : BytecodePatch( + listOf( + CodecsLockFingerprint, AllCodecsReferenceFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val codecsLockResult = CodecsLockFingerprint.result!! + + val implementation = codecsLockResult.mutableMethod.implementation!! + + val scanResultStartIndex = codecsLockResult.scanResult.patternScanResult!!.startIndex + val instructionIndex = scanResultStartIndex + + if (implementation.instructions[scanResultStartIndex - 1].opcode == Opcode.CHECK_CAST) { + // for 5.16.xx and lower + -3 + } else { + // since 5.17.xx + -2 + } + + val allCodecsResult = AllCodecsReferenceFingerprint.result!! + val allCodecsMethod = + context.toMethodWalker(allCodecsResult.method) + .nextMethod(allCodecsResult.scanResult.patternScanResult!!.startIndex) + .getMethod() + + implementation.replaceInstruction( + instructionIndex, + "invoke-static {}, ${allCodecsMethod.definingClass}->${allCodecsMethod.name}()Ljava/util/Set;".toInstruction() + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt new file mode 100644 index 0000000..a653e57 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/annotations/ExclusiveAudioCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.audio.exclusiveaudio.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ExclusiveAudioCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt new file mode 100644 index 0000000..29c90b6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/AudioOnlyEnablerFingerprint.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.music.audio.exclusiveaudio.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 AudioOnlyEnablerFingerprint: MethodFingerprint( + "Z", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IF_NEZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.GOTO, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.RETURN + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt new file mode 100644 index 0000000..02eb60a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/fingerprints/ExclusiveAudioFingerprint.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.music.audio.exclusiveaudio.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object ExclusiveAudioFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("L", "Z"), + listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQ, + Opcode.CONST_4, + Opcode.GOTO, + Opcode.NOP, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IF_EQZ, + Opcode.IF_EQZ, + Opcode.INVOKE_INTERFACE, + Opcode.INVOKE_INTERFACE, + Opcode.GOTO, + Opcode.RETURN_VOID + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt new file mode 100644 index 0000000..d679e87 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/patch/ExclusiveAudioPatch.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.music.audio.exclusiveaudio.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.replaceInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.audio.exclusiveaudio.annotations.ExclusiveAudioCompatibility +import app.revanced.patches.music.audio.exclusiveaudio.fingerprints.AudioOnlyEnablerFingerprint + +@Patch +@Name("exclusive-audio-playback") +@Description("Enables the option to play music without video.") +@ExclusiveAudioCompatibility +@Version("0.0.1") +class ExclusiveAudioPatch : BytecodePatch( + listOf( + AudioOnlyEnablerFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = AudioOnlyEnablerFingerprint.result!!.mutableMethod + method.replaceInstruction(method.implementation!!.instructions.count() - 1, "const/4 v0, 0x1") + method.addInstruction("return v0") + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt new file mode 100644 index 0000000..a54a371 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/annotations/CompactHeaderCompatibility.kt @@ -0,0 +1,29 @@ +package app.revanced.patches.music.layout.compactheader.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CompactHeaderCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt new file mode 100644 index 0000000..661c138 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/fingerprints/CompactHeaderConstructorFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.music.layout.compactheader.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 CompactHeaderConstructorFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L", "L", "L"), listOf( + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.CONST, + Opcode.CONST_4, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST + ) +) diff --git a/src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt new file mode 100644 index 0000000..879dfa0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/compactheader/patch/CompactHeaderPatch.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.music.layout.compactheader.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.layout.compactheader.annotations.CompactHeaderCompatibility +import app.revanced.patches.music.layout.compactheader.fingerprints.CompactHeaderConstructorFingerprint +import org.jf.dexlib2.builder.instruction.BuilderInstruction11x + +@Patch(false) +@Name("compact-header") +@Description("Hides the music category bar at the top of the homepage.") +@CompactHeaderCompatibility +@Version("0.0.1") +class CompactHeaderPatch : BytecodePatch( + listOf( + CompactHeaderConstructorFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = CompactHeaderConstructorFingerprint.result!! + val method = result.mutableMethod + + val insertIndex = result.scanResult.patternScanResult!!.endIndex + val register = (method.implementation!!.instructions[insertIndex - 1] as BuilderInstruction11x).registerA + method.addInstructions( + insertIndex, """ + const/16 v2, 0x8 + invoke-virtual {v${register}, v2}, Landroid/view/View;->setVisibility(I)V + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt new file mode 100644 index 0000000..b5ec0f5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.layout.minimizedplayback.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MinimizedPlaybackCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt new file mode 100644 index 0000000..eb3d28a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.music.layout.minimizedplayback.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 MinimizedPlaybackManagerFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("I", "L", "Z"), + listOf( + Opcode.IGET, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.IF_NE, + Opcode.IGET_BOOLEAN, + Opcode.IF_EQ, + Opcode.GOTO, + Opcode.RETURN_VOID, + Opcode.SGET_OBJECT, + Opcode.CONST_4, + Opcode.IF_NE, + Opcode.IPUT_BOOLEAN, + ) +) diff --git a/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt new file mode 100644 index 0000000..dcfdb9f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/minimizedplayback/patch/MinimizedPlaybackPatch.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.music.layout.minimizedplayback.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.layout.minimizedplayback.annotations.MinimizedPlaybackCompatibility +import app.revanced.patches.music.layout.minimizedplayback.fingerprints.MinimizedPlaybackManagerFingerprint + +@Patch +@Name("minimized-playback-music") +@Description("Enables minimized playback on Kids music.") +@MinimizedPlaybackCompatibility +@Version("0.0.1") +class MinimizedPlaybackPatch : BytecodePatch( + listOf( + MinimizedPlaybackManagerFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + MinimizedPlaybackManagerFingerprint.result!!.mutableMethod.addInstructions( + 0, """ + return-void + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt new file mode 100644 index 0000000..f2089df --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/annotations/HideGetPremiumCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.layout.premium.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideGetPremiumCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt new file mode 100644 index 0000000..5b89674 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.music.layout.premium.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 HideGetPremiumFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf( + Opcode.IF_NEZ, + Opcode.CONST_16, + Opcode.GOTO, + Opcode.NOP, + Opcode.INVOKE_VIRTUAL + ) +) diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt new file mode 100644 index 0000000..d91ff64 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/fingerprints/HideGetPremiumParentFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.music.layout.premium.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 HideGetPremiumParentFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf( + Opcode.IGET_BOOLEAN, + Opcode.CONST_4, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC + ), + listOf("FEmusic_history"), +) diff --git a/src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt new file mode 100644 index 0000000..727ff9d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/premium/patch/HideGetPremiumPatch.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.music.layout.premium.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.addInstructions +import app.revanced.patcher.extensions.replaceInstruction +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.Patch +import app.revanced.patches.music.layout.premium.annotations.HideGetPremiumCompatibility +import app.revanced.patches.music.layout.premium.fingerprints.HideGetPremiumFingerprint +import app.revanced.patches.music.layout.premium.fingerprints.HideGetPremiumParentFingerprint + +@Patch +@Name("hide-get-premium") +@Description("Removes all \"Get Premium\" evidences from the avatar menu.") +@HideGetPremiumCompatibility +@Version("0.0.1") +class HideGetPremiumPatch : BytecodePatch( + listOf( + HideGetPremiumParentFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val parentResult = HideGetPremiumParentFingerprint.result!! + HideGetPremiumFingerprint.resolve(context, parentResult.classDef) + + val startIndex = parentResult.scanResult.patternScanResult!!.startIndex + + val parentMethod = parentResult.mutableMethod + parentMethod.replaceInstruction( + startIndex, """ + const/4 v1, 0x0 + """ + ) + + val result = HideGetPremiumFingerprint.result!! + val method = result.mutableMethod + method.addInstructions( + startIndex, """ + const/16 v0, 0x8 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt new file mode 100644 index 0000000..c51a2f1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/annotations/RemoveTasteBuilderCompatibility.kt @@ -0,0 +1,32 @@ +package app.revanced.patches.music.layout.tastebuilder.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +/** + * -- Note 2022-08-05 -- + * Since 5.17.xx the tastebuilder component is dismissible, so this patch is less useful + * also it is partly litho now + */ +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class RemoveTasteBuilderCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt new file mode 100644 index 0000000..e72193c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/fingerprints/TasteBuilderConstructorFingerprint.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.music.layout.tastebuilder.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object TasteBuilderConstructorFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L"), listOf( + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_VIRTUAL, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.CONST + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt new file mode 100644 index 0000000..d3b61a3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/tastebuilder/patch/RemoveTasteBuilderPatch.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.music.layout.tastebuilder.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.layout.tastebuilder.annotations.RemoveTasteBuilderCompatibility +import app.revanced.patches.music.layout.tastebuilder.fingerprints.TasteBuilderConstructorFingerprint +import org.jf.dexlib2.iface.instruction.formats.Instruction22c + +@Patch +@Name("tasteBuilder-remover") +@Description("Removes the \"Tell us which artists you like\" card from the home screen.") +@RemoveTasteBuilderCompatibility +@Version("0.0.1") +class RemoveTasteBuilderPatch : BytecodePatch( + listOf( + TasteBuilderConstructorFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = TasteBuilderConstructorFingerprint.result!! + val method = result.mutableMethod + + val insertIndex = result.scanResult.patternScanResult!!.endIndex - 8 + val register = (method.implementation!!.instructions[insertIndex] as Instruction22c).registerA + method.addInstructions( + insertIndex, """ + const/16 v1, 0x8 + invoke-virtual {v${register}, v1}, Landroid/view/View;->setVisibility(I)V + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt new file mode 100644 index 0000000..b949a4a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/annotations/RemoveUpgradeButtonCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.layout.upgradebutton.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class RemoveUpgradeButtonCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt new file mode 100644 index 0000000..e62225b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/fingerprints/PivotBarConstructorFingerprint.kt @@ -0,0 +1,62 @@ +package app.revanced.patches.music.layout.upgradebutton.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object PivotBarConstructorFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + listOf("L", "Z"), + listOf( + Opcode.INVOKE_DIRECT, + Opcode.CONST_4, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_BOOLEAN, + Opcode.IGET_OBJECT, + Opcode.IF_NEZ, + Opcode.GOTO, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IGET, + Opcode.CONST, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.GOTO, + Opcode.SGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IGET, + Opcode.CONST, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_INTERFACE, + Opcode.GOTO, + Opcode.NOP, + Opcode.IPUT_OBJECT, + Opcode.RETURN_VOID + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt new file mode 100644 index 0000000..9bf8ad0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/patch/RemoveUpgradeButtonPatch.kt @@ -0,0 +1,78 @@ +package app.revanced.patches.music.layout.upgradebutton.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.smali.toInstructions +import app.revanced.patches.music.layout.upgradebutton.annotations.RemoveUpgradeButtonCompatibility +import app.revanced.patches.music.layout.upgradebutton.fingerprints.PivotBarConstructorFingerprint +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction22t +import org.jf.dexlib2.iface.instruction.formats.Instruction22c +import org.jf.dexlib2.iface.instruction.formats.Instruction35c + + +@Patch +@Name("upgrade-button-remover") +@Description("Removes the upgrade tab from the pivot bar.") +@RemoveUpgradeButtonCompatibility +@Version("0.0.1") +class RemoveUpgradeButtonPatch : BytecodePatch( + listOf( + PivotBarConstructorFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = PivotBarConstructorFingerprint.result!! + val implementation = result.mutableMethod.implementation!! + + val pivotBarElementFieldRef = + (implementation.instructions[result.scanResult.patternScanResult!!.endIndex - 1] as Instruction22c).reference + + val register = (implementation.instructions.first() as Instruction35c).registerC + // first compile all the needed instructions + val instructionList = """ + invoke-interface { v0 }, Ljava/util/List;->size()I + move-result v1 + const/4 v2, 0x3 + invoke-interface {v0, v2}, Ljava/util/List;->remove(I)Ljava/lang/Object; + iput-object v0, v$register, $pivotBarElementFieldRef + """.toInstructions().toMutableList() + + + val endIndex = result.scanResult.patternScanResult!!.endIndex + + // replace the instruction to retain the label at given index + implementation.replaceInstruction( + endIndex - 1, instructionList[0] // invoke-interface + ) + // do not forget to remove this instruction since we added it already + instructionList.removeFirst() + + val exitInstruction = instructionList.last() // iput-object + implementation.addInstruction( + endIndex, exitInstruction + ) + // do not forget to remove this instruction since we added it already + instructionList.removeLast() + + // add the necessary if statement to remove the upgrade tab button in case it exists + instructionList.add( + 2, // if-le + BuilderInstruction22t( + Opcode.IF_LE, 1, 2, implementation.newLabelForIndex(endIndex) + ) + ) + + implementation.addInstructions( + endIndex, instructionList + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt new file mode 100644 index 0000000..3bb82d4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/annotations/MusicMicroGPatchCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.misc.microg.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MusicMicroGPatchCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt new file mode 100644 index 0000000..04d0430 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastContextFetchFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CastContextFetchFingerprint : MethodFingerprint( + strings = listOf("Error fetching CastContext.") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt new file mode 100644 index 0000000..a4d39a8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CastDynamiteModuleFingerprint : MethodFingerprint( + strings = listOf("com.google.android.gms.cast.framework.internal.CastDynamiteModuleImpl") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt new file mode 100644 index 0000000..f4119e5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CastDynamiteModuleV2Fingerprint : MethodFingerprint( + strings = listOf("Failed to load module via V2: ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt new file mode 100644 index 0000000..cfc36f0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.music.misc.microg.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object GooglePlayUtilityFingerprint : MethodFingerprint( + "I", + AccessFlags.PUBLIC or AccessFlags.STATIC, + listOf("L", "I"), + strings = listOf( + "This should never happen.", + "MetadataValueReader", + "GooglePlayServicesUtil", + "com.android.vending", + "android.hardware.type.embedded" + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt new file mode 100644 index 0000000..e9931b4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/PrimeFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PrimeFingerprint : MethodFingerprint( + strings = listOf("com.google.android.GoogleCamera", "com.android.vending") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt new file mode 100644 index 0000000..d81f77f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/fingerprints/ServiceCheckFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.music.misc.microg.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object ServiceCheckFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.STATIC, + listOf("L", "I"), + strings = listOf("Google Play Services not available") +) diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt new file mode 100644 index 0000000..07b99c3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/bytecode/MusicMicroGBytecodePatch.kt @@ -0,0 +1,65 @@ +package app.revanced.patches.music.misc.microg.patch.bytecode + +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.patch.BytecodePatch +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.microg.annotations.MusicMicroGPatchCompatibility +import app.revanced.patches.music.misc.microg.fingerprints.* +import app.revanced.patches.music.misc.microg.patch.resource.MusicMicroGResourcePatch +import app.revanced.patches.music.misc.microg.shared.Constants.MUSIC_PACKAGE_NAME +import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants +import app.revanced.util.microg.MicroGBytecodeHelper + +@Patch +@DependsOn([MusicMicroGResourcePatch::class]) +@Name("music-microg-support") +@Description("Allows YouTube Music ReVanced to run without root and under a different package name.") +@MusicMicroGPatchCompatibility +@Version("0.0.2") +class MusicMicroGBytecodePatch : BytecodePatch( + listOf( + ServiceCheckFingerprint, + GooglePlayUtilityFingerprint, + CastDynamiteModuleFingerprint, + CastDynamiteModuleV2Fingerprint, + CastContextFetchFingerprint, + PrimeFingerprint, + ) +) { + // NOTE: the previous patch also replaced the following strings, but it seems like they are not needed: + // - "com.google.android.gms.chimera.GmsIntentOperationService", + // - "com.google.android.gms.phenotype.internal.IPhenotypeCallbacks", + // - "com.google.android.gms.phenotype.internal.IPhenotypeService", + // - "com.google.android.gms.phenotype.PACKAGE_NAME", + // - "com.google.android.gms.phenotype.UPDATE", + // - "com.google.android.gms.phenotype", + override fun execute(context: BytecodeContext) = + // apply common microG patch + MicroGBytecodeHelper.patchBytecode( + context, + arrayOf( + MicroGBytecodeHelper.packageNameTransform( + Constants.PACKAGE_NAME, + Constants.REVANCED_PACKAGE_NAME + ) + ), + MicroGBytecodeHelper.PrimeMethodTransformationData( + PrimeFingerprint, + MUSIC_PACKAGE_NAME, + REVANCED_MUSIC_PACKAGE_NAME + ), + listOf( + ServiceCheckFingerprint, + GooglePlayUtilityFingerprint, + CastDynamiteModuleFingerprint, + CastDynamiteModuleV2Fingerprint, + CastContextFetchFingerprint + ) + ).let { PatchResultSuccess() } +} diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt new file mode 100644 index 0000000..19daba3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/patch/resource/MusicMicroGResourcePatch.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.music.misc.microg.patch.resource + +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.patches.music.misc.microg.annotations.MusicMicroGPatchCompatibility +import app.revanced.patches.music.misc.microg.shared.Constants.MUSIC_PACKAGE_NAME +import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_APP_NAME +import app.revanced.patches.music.misc.microg.shared.Constants.REVANCED_MUSIC_PACKAGE_NAME +import app.revanced.patches.music.misc.microg.shared.Constants.SPOOFED_PACKAGE_NAME +import app.revanced.patches.music.misc.microg.shared.Constants.SPOOFED_PACKAGE_SIGNATURE +import app.revanced.util.microg.MicroGManifestHelper +import app.revanced.util.microg.MicroGResourceHelper + +@Name("music-microg-resource-patch") +@Description("Resource patch to allow YouTube Music ReVanced to run without root and under a different package name.") +@MusicMicroGPatchCompatibility +@Version("0.0.2") +class MusicMicroGResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + // update manifest + MicroGResourceHelper.patchManifest( + context, + MUSIC_PACKAGE_NAME, + REVANCED_MUSIC_PACKAGE_NAME, + REVANCED_MUSIC_APP_NAME + ) + + // add metadata to the manifest + MicroGManifestHelper.addSpoofingMetadata( + context, + SPOOFED_PACKAGE_NAME, + SPOOFED_PACKAGE_SIGNATURE + ) + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt b/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt new file mode 100644 index 0000000..a7405cc --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/microg/shared/Constants.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.music.misc.microg.shared + +object Constants { + internal const val REVANCED_MUSIC_APP_NAME = "YT Music ReVanced" + internal const val REVANCED_MUSIC_PACKAGE_NAME = "app.revanced.android.apps.youtube.music" + internal const val MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music" + internal const val SPOOFED_PACKAGE_NAME = MUSIC_PACKAGE_NAME + internal const val SPOOFED_PACKAGE_SIGNATURE = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875" +} diff --git a/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt new file mode 100644 index 0000000..0c91cb5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/annotations/BackgroundPlayCompatibility.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.premium.backgroundplay.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.apps.youtube.music", + arrayOf( + "5.14.53", + "5.16.51", + "5.17.51", + "5.21.52", + "5.22.54", + "5.23.50", + "5.25.51", + "5.25.52", + "5.26.52", + "5.27.51", + "5.28.52", + "5.29.52", + "5.31.50", + "5.34.51", + "5.36.51" + ) + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class BackgroundPlayCompatibility diff --git a/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt new file mode 100644 index 0000000..fdaea77 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/fingerprints/BackgroundPlaybackDisableFingerprint.kt @@ -0,0 +1,43 @@ +package app.revanced.patches.music.premium.backgroundplay.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object BackgroundPlaybackDisableFingerprint : MethodFingerprint( + "Z", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L"), listOf( + Opcode.CONST_4, + Opcode.IF_EQZ, + Opcode.IGET, + Opcode.AND_INT_LIT16, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.IGET, + Opcode.CONST, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.IGET, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.GOTO, + Opcode.SGET_OBJECT, + Opcode.GOTO, + Opcode.CONST_4, + Opcode.IF_EQZ, + Opcode.IGET_BOOLEAN, + Opcode.IF_EQZ, + Opcode.CONST_4, + Opcode.RETURN, + Opcode.RETURN, + Opcode.RETURN + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt new file mode 100644 index 0000000..9c860a9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/premium/backgroundplay/patch/BackgroundPlayPatch.kt @@ -0,0 +1,36 @@ +package app.revanced.patches.music.premium.backgroundplay.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.premium.backgroundplay.annotations.BackgroundPlayCompatibility +import app.revanced.patches.music.premium.backgroundplay.fingerprints.BackgroundPlaybackDisableFingerprint + +@Patch +@Name("background-play") +@Description("Enables playing music in the background.") +@BackgroundPlayCompatibility +@Version("0.0.1") +class BackgroundPlayPatch : BytecodePatch( + listOf( + BackgroundPlaybackDisableFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + BackgroundPlaybackDisableFingerprint.result!!.mutableMethod.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt new file mode 100644 index 0000000..e4d8a13 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/annotations/UnlockProCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.myexpenses.misc.pro.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("org.totschnig.myexpenses")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnlockProCompatibility diff --git a/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt new file mode 100644 index 0000000..caebe9e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/fingerprints/IsEnabledFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.myexpenses.misc.pro.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object IsEnabledFingerprint : MethodFingerprint( + "Z", + strings = listOf("feature", "feature.licenceStatus") +) diff --git a/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt new file mode 100644 index 0000000..ebf34e6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/myexpenses/misc/pro/patch/UnlockProPatch.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.myexpenses.misc.pro.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.myexpenses.misc.pro.annotations.UnlockProCompatibility +import app.revanced.patches.myexpenses.misc.pro.fingerprints.IsEnabledFingerprint + +@Patch +@Name("unlock-pro") +@Description("Unlocks all professional features.") +@UnlockProCompatibility +@Version("0.0.1") +class UnlockProPatch : BytecodePatch( + listOf( + IsEnabledFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = IsEnabledFingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt new file mode 100644 index 0000000..39c9434 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/annotations/UnlockProCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.nyx.misc.pro.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.awedea.nyx")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnlockProCompatibility diff --git a/src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt new file mode 100644 index 0000000..90b0af5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/fingerprints/CheckProFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.nyx.misc.pro.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CheckProFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("BillingManager;") && methodDef.name == "isProVersion" + } +) diff --git a/src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt new file mode 100644 index 0000000..bb0ee59 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/nyx/misc/pro/patch/UnlockProPatch.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.nyx.misc.pro.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.nyx.misc.pro.annotations.UnlockProCompatibility +import app.revanced.patches.nyx.misc.pro.fingerprints.CheckProFingerprint + +@Patch +@Name("unlock-pro") +@Description("Unlocks all pro features.") +@UnlockProCompatibility +@Version("0.0.1") +class UnlockProPatch : BytecodePatch( + listOf( + CheckProFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = CheckProFingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt new file mode 100644 index 0000000..0666c87 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/annotations/GeneralAdsCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.reddit.ad.general.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.reddit.frontpage")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class GeneralAdsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt new file mode 100644 index 0000000..86341a6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/ad/general/patch/GeneralAdsPatch.kt @@ -0,0 +1,54 @@ +package app.revanced.patches.reddit.ad.general.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.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.reddit.ad.general.annotations.GeneralAdsCompatibility +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction21c +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.StringReference +import org.jf.dexlib2.immutable.reference.ImmutableStringReference + +@Patch +@Name("general-reddit-ads") +@Description("Removes general ads from the Reddit frontpage and subreddits.") +@GeneralAdsCompatibility +@Version("0.0.1") +class GeneralAdsPatch : BytecodePatch() { + override fun execute(context: BytecodeContext): PatchResult { + context.classes.forEach { classDef -> + classDef.methods.forEach methodLoop@{ method -> + val implementation = method.implementation ?: return@methodLoop + + implementation.instructions.forEachIndexed { i, instruction -> + if (instruction.opcode != Opcode.CONST_STRING) return@forEachIndexed + if (((instruction as ReferenceInstruction).reference as StringReference).string != "AdPost") return@forEachIndexed + + val proxiedClass = context.proxy(classDef).mutableClass + + val proxiedImplementation = proxiedClass.methods.first { + it.name == method.name && it.parameterTypes.containsAll(method.parameterTypes) + }.implementation!! + + var newString = "AdPost1" + if (proxiedImplementation.instructions[i - 1].opcode == Opcode.CONST_STRING) { + newString = "SubredditPost" + } + proxiedImplementation.replaceInstruction( + i, BuilderInstruction21c( + Opcode.CONST_STRING, (proxiedImplementation.instructions[i] as BuilderInstruction21c).registerA, ImmutableStringReference(newString) + ) + ) + } + } + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt new file mode 100644 index 0000000..11ea3c8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/annotations/PremiumIconCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.reddit.layout.premiumicon.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.reddit.frontpage")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PremiumIconCompatibility diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt new file mode 100644 index 0000000..bb8bf0d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/fingerprints/PremiumIconFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.reddit.layout.premiumicon.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PremiumIconFingerprint : MethodFingerprint( + "Z", + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("MyAccount;") && methodDef.name == "isPremiumSubscriber" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt new file mode 100644 index 0000000..156c746 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/reddit/layout/premiumicon/patch/PremiumIconPatch.kt @@ -0,0 +1,36 @@ +package app.revanced.patches.reddit.layout.premiumicon.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.reddit.layout.premiumicon.annotations.PremiumIconCompatibility +import app.revanced.patches.reddit.layout.premiumicon.fingerprints.PremiumIconFingerprint + +@Patch +@Name("premium-icon-reddit") +@Description("Unlocks premium Reddit app icons.") +@PremiumIconCompatibility +@Version("0.0.1") +class PremiumIconPatch : BytecodePatch( + listOf( + PremiumIconFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = PremiumIconFingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt new file mode 100644 index 0000000..572ec21 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.shared.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SeekbarFingerprint : MethodFingerprint( + "V", + strings = listOf("timed_markers_width") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt new file mode 100644 index 0000000..7afcdae --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/SeekbarOnDrawFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.shared.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SeekbarOnDrawFingerprint : MethodFingerprint( + customFingerprint = { it.name == "onDraw" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt new file mode 100644 index 0000000..b10c5e2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/fingerprints/WatchWhileActivityFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.shared.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object WatchWhileActivityFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("WatchWhileActivity;") && methodDef.name == "" + } +) diff --git a/src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt new file mode 100644 index 0000000..2ef2e48 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/integrations/patch/AbstractIntegrationsPatch.kt @@ -0,0 +1,66 @@ +package app.revanced.patches.shared.integrations.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import org.jf.dexlib2.iface.Method + +@Description("Applies mandatory patches to implement the ReVanced integrations into the application.") +@Version("0.0.1") +abstract class AbstractIntegrationsPatch( + private val integrationsDescriptor: String, + private val hooks: Iterable +) : BytecodePatch(hooks) { + /** + * [MethodFingerprint] for integrations. + * + * @param contextRegisterResolver A [RegisterResolver] to get the register. + * @see MethodFingerprint + */ + abstract class IntegrationsFingerprint( + strings: Iterable? = null, + customFingerprint: ((methodDef: Method) -> Boolean)? = null, + private val contextRegisterResolver: (Method) -> Int = object : RegisterResolver {} + ) : MethodFingerprint(strings = strings, customFingerprint = customFingerprint) { + fun invoke(integrationsDescriptor: String): PatchResult { + result?.mutableMethod?.let { method -> + val contextRegister = contextRegisterResolver(method) + + method.addInstruction( + 0, + "sput-object v$contextRegister, " + + "$integrationsDescriptor->context:Landroid/content/Context;" + ) + } ?: return PatchResultError("Could not find hook target fingerprint.") + + return PatchResultSuccess() + } + + interface RegisterResolver : (Method) -> Int { + override operator fun invoke(method: Method) = method.implementation!!.registerCount - 1 + } + } + + override fun execute(context: BytecodeContext): PatchResult { + if (context.findClass(integrationsDescriptor) == null) return MISSING_INTEGRATIONS + + for (hook in hooks) hook.invoke(integrationsDescriptor).let { + if (it is PatchResultError) return it + } + + return PatchResultSuccess() + } + + private companion object { + val MISSING_INTEGRATIONS = PatchResultError( + "Integrations have not been merged yet. " + + "This patch can not succeed without merging the integrations." + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt b/src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt new file mode 100644 index 0000000..b607dc2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/mapping/misc/patch/ResourceMappingPatch.kt @@ -0,0 +1,73 @@ +package app.revanced.patches.shared.mapping.misc.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 org.w3c.dom.Element +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + + +@Name("resource-mapping") +@Description("Creates a map of public resources.") +@Version("0.0.1") +class ResourceMappingPatch : ResourcePatch { + companion object { + internal lateinit var resourceMappings: List + private set + + private val THREAD_COUNT = Runtime.getRuntime().availableProcessors() + private val threadPoolExecutor = Executors.newFixedThreadPool(THREAD_COUNT) + } + + override fun execute(context: ResourceContext): PatchResult { + // save the file in memory to concurrently read from + val resourceXmlFile = context["res/values/public.xml"].readBytes() + + // create a synchronized list to store the resource mappings + val mappings = Collections.synchronizedList(mutableListOf()) + + for (threadIndex in 0 until THREAD_COUNT) { + threadPoolExecutor.execute thread@{ + context.xmlEditor[resourceXmlFile.inputStream()].use { editor -> + val resources = editor.file.documentElement.childNodes + val resourcesLength = resources.length + val jobSize = resourcesLength / THREAD_COUNT + + val batchStart = jobSize * threadIndex + val batchEnd = jobSize * (threadIndex + 1) + element@ for (i in batchStart until batchEnd) { + // make sure to not to go out of bounds when rounding errors occur at calculating the jobSize + if (i >= resourcesLength) return@thread + + val node = resources.item(i) + if (node !is Element) continue + + val nameAttribute = node.getAttribute("name") + val typeAttribute = node.getAttribute("type") + + if (node.nodeName != "public" || nameAttribute.startsWith("APKTOOL")) continue + + val id = node.getAttribute("id").substring(2).toLong(16) + mappings.add(ResourceElement(typeAttribute, nameAttribute, id)) + } + } + } + } + + threadPoolExecutor + .also { it.shutdown() } + .awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS) + + resourceMappings = mappings + + return PatchResultSuccess() + } +} + +data class ResourceElement(val type: String, val name: String, val id: Long) diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt new file mode 100644 index 0000000..1853a97 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt @@ -0,0 +1,31 @@ +package app.revanced.patches.shared.settings.preference + +import app.revanced.patches.shared.settings.preference.impl.StringResource +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Base preference class for all preferences. + * + * @param key The key of the preference. + * @param title The title of the preference. + */ +internal abstract class BasePreference( + override val key: String, + override val title: StringResource, +) : IPreference { + + /** + * Serialize preference element to XML. + * Overriding methods should invoke super and operate on its return value. + * @param ownerDocument Target document to create elements from. + * @param resourceCallback Called when a resource has been processed. + */ + open fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)? = null): Element { + return ownerDocument.createElement(tag).apply { + if(key.isNotEmpty()) + setAttribute("android:key", key) + setAttribute("android:title", "@string/${title.also { resourceCallback?.invoke(it) }.name}") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt new file mode 100644 index 0000000..5c5e417 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BaseResource.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.shared.settings.preference + +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Base resource class for all resources. + * + * @param name The name of the resource. + */ +internal abstract class BaseResource( + override val name: String +) : IResource { + + /** + * Serialize resource element to XML. + * Overriding methods should invoke super and operate on its return value. + * @param ownerDocument Target document to create elements from. + * @param resourceCallback Called when a resource has been processed. + */ + open fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)? = null): Element { + return ownerDocument.createElement(tag).apply { + setAttribute("name", name) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt new file mode 100644 index 0000000..b0e8ddb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/ComponentsExtensions.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.shared.settings.preference + +import app.revanced.patches.shared.settings.preference.impl.StringResource +import org.w3c.dom.Element +import org.w3c.dom.Node + +/** + * Add a resource node child + * + * @param resource The resource to add. + * @param resourceCallback Called when a resource has been processed. + */ +internal fun Node.addResource(resource: BaseResource, resourceCallback: ((IResource) -> Unit)? = null) { + appendChild(resource.serialize(ownerDocument, resourceCallback)) +} + +/** + * Add a preference node child to the settings. + * + * @param preference The preference to add. + * @param resourceCallback Called when a resource has been processed. + */ +internal fun Node.addPreference(preference: BasePreference, resourceCallback: ((IResource) -> Unit)? = null) { + appendChild(preference.serialize(ownerDocument, resourceCallback)) +} + +internal fun Element.addSummary(summaryResource: StringResource?, summaryType: SummaryType = SummaryType.DEFAULT) = + summaryResource?.let { summary -> + setAttribute("android:${summaryType.type}", "@string/${summary.name}") + } + +internal fun Element.addDefault(default: T) { + default?.let { + setAttribute( + "android:defaultValue", when (it) { + is Boolean -> if (it) "true" else "false" + is String -> it + else -> throw IllegalArgumentException("Unsupported default value type: ${it::class.java.name}") + } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt new file mode 100644 index 0000000..ec31835 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IPreference.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.shared.settings.preference + +import app.revanced.patches.shared.settings.preference.impl.StringResource + +/** + * Preference + */ +internal interface IPreference { + /** + * Key of the preference. + */ + val key: String + + /** + * Title of the preference. + */ + val title: StringResource + + /** + * Tag name of the preference. + */ + val tag: String +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt new file mode 100644 index 0000000..dc3fe61 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/IResource.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.shared.settings.preference + +/** + * Resource + */ +internal interface IResource { + /** + * Name of the resource. + */ + val name: String + + /** + * Tag name of the resource. + */ + val tag: String +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt new file mode 100644 index 0000000..006f698 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/SummaryType.kt @@ -0,0 +1,5 @@ +package app.revanced.patches.shared.settings.preference + +enum class SummaryType(val type: String) { + DEFAULT("summary"), ON("summaryOn"), OFF("summaryOff") +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt new file mode 100644 index 0000000..608dccc --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ArrayResource.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BaseResource +import app.revanced.patches.shared.settings.preference.IResource +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Represents an array resource. + * + * @param name The name of the array resource. + * @param items The items of the array resource. + */ +internal data class ArrayResource( + override val name: String, + val items: List +) : BaseResource(name) { + override val tag = "string-array" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + setAttribute("name", name) + + items.forEach { item -> + resourceCallback?.invoke(item) + + this.appendChild(ownerDocument.createElement("item").also { itemNode -> + itemNode.textContent = "@string/${item.name}" + }) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt new file mode 100644 index 0000000..0943214 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/InputType.kt @@ -0,0 +1,6 @@ +package app.revanced.patches.shared.settings.preference.impl + +enum class InputType(val type: String) { + STRING("text"), + NUMBER("number"), +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt new file mode 100644 index 0000000..9f2f9f8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/ListPreference.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import app.revanced.patches.shared.settings.preference.addDefault +import app.revanced.patches.shared.settings.preference.addSummary +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * List preference. + * + * @param key The key of the list preference. + * @param title The title of the list preference. + * @param entries The human-readable entries of the list preference. + * @param entryValues The entry values of the list preference. + * @param default The default entry value of the list preference. + * @param summary The summary of the list preference. + */ +internal class ListPreference( + key: String, + title: StringResource, + val entries: ArrayResource, + val entryValues: ArrayResource, + val default: String? = null, + val summary: StringResource? = null +) : BasePreference(key, title) { + override val tag: String = "ListPreference" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + setAttribute("android:entries", "@array/${entries.also { resourceCallback?.invoke(it) }.name}") + setAttribute("android:entryValues", "@array/${entryValues.also { resourceCallback?.invoke(it) }.name}") + addDefault(default) + addSummary(summary) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt new file mode 100644 index 0000000..fcacd61 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/Preference.kt @@ -0,0 +1,44 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import app.revanced.patches.shared.settings.preference.addSummary +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * A Preference object. + * + * @param title The title of the preference. + * @param intent The intent of the preference. + * @param summary The summary of the text preference. + */ +internal class Preference( + key: String, + title: StringResource, + val intent: Intent, + val summary: StringResource? = null +) : BasePreference(key, title) { + override val tag: String = "Preference" + + /* Key-less constructor */ + constructor( + title: StringResource, + intent: Intent, + summary: StringResource? = null + ) : this("", title, intent, summary) + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + addSummary(summary?.also { resourceCallback?.invoke(it) }) + + this.appendChild(ownerDocument.createElement("intent").also { intentNode -> + intentNode.setAttribute("android:targetPackage", intent.targetPackage) + intentNode.setAttribute("android:data", intent.data) + intentNode.setAttribute("android:targetClass", intent.targetClass) + }) + } + } + + data class Intent(val targetPackage: String, val data: String, val targetClass: String) +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt new file mode 100644 index 0000000..f575dfe --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceCategory.kt @@ -0,0 +1,29 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Preference category. + * + * @param key The key of the preference. + * @param title The title of the preference. + * @param preferences Child preferences of this category. + */ +internal open class PreferenceCategory( + key: String, + title: StringResource, + var preferences: List +) : BasePreference(key, title) { + override val tag: String = "PreferenceCategory" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + for (childPreference in preferences) { + this.appendChild(childPreference.serialize(ownerDocument, resourceCallback)) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt new file mode 100644 index 0000000..b7c82ba --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/PreferenceScreen.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import app.revanced.patches.shared.settings.preference.addSummary +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Preference screen. + * + * @param key The key of the preference. + * @param title The title of the preference. + * @param preferences Child preferences of this screen. + * @param summary The summary of the text preference. + */ +internal open class PreferenceScreen( + key: String, + title: StringResource, + var preferences: List, + val summary: StringResource? = null +) : BasePreference(key, title) { + override val tag: String = "PreferenceScreen" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + addSummary(summary?.also { resourceCallback?.invoke(it) }) + + for (childPreference in preferences) { + this.appendChild(childPreference.serialize(ownerDocument, resourceCallback)) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt new file mode 100644 index 0000000..49471fa --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/StringResource.kt @@ -0,0 +1,31 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BaseResource +import app.revanced.patches.shared.settings.preference.IResource +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Represents a string value in the strings.xml file + * + * @param name The name of the string + * @param value The value of the string + * @param formatted If the string is formatted. If false, the attribute will be set + */ +internal data class StringResource( + override val name: String, + val value: String, + val formatted: Boolean = true +) : BaseResource(name) { + override val tag = "string" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + // if the string is un-formatted, explicitly add the formatted attribute + if (!formatted) + setAttribute("formatted", "false") + + textContent = value + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt new file mode 100644 index 0000000..78d9f1d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/SwitchPreference.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import app.revanced.patches.shared.settings.preference.addDefault +import app.revanced.patches.shared.settings.preference.addSummary +import app.revanced.patches.shared.settings.preference.SummaryType +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Switch preference. + * + * @param key The key of the switch. + * @param title The title of the switch. + * @param default The default value of the switch. + * @param summaryOn The summary to show when the preference is enabled. + * @param summaryOff The summary to show when the preference is disabled. + */ +internal class SwitchPreference( + key: String, title: StringResource, + val default: Boolean = false, + val summaryOn: StringResource? = null, + val summaryOff: StringResource? = null +) : BasePreference(key, title) { + override val tag: String = "SwitchPreference" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + addDefault(default) + addSummary(summaryOn?.also { resourceCallback?.invoke(it) }, SummaryType.ON) + addSummary(summaryOff?.also { resourceCallback?.invoke(it) }, SummaryType.OFF) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt new file mode 100644 index 0000000..907a4a1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/TextPreference.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.shared.settings.preference.impl + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import app.revanced.patches.shared.settings.preference.addDefault +import app.revanced.patches.shared.settings.preference.addSummary +import org.w3c.dom.Document +import org.w3c.dom.Element + +/** + * Text preference. + * + * @param key The key of the text preference. + * @param title The title of the text preference. + * @param inputType The input type of the text preference. + * @param default The default value of the text preference. + * @param summary The summary of the text preference. + */ +internal class TextPreference( + key: String, + title: StringResource, + var inputType: InputType = InputType.STRING, + val default: String? = null, + val summary: StringResource? = null +) : BasePreference(key, title) { + override val tag: String = "EditTextPreference" + + override fun serialize(ownerDocument: Document, resourceCallback: ((IResource) -> Unit)?): Element { + return super.serialize(ownerDocument, resourceCallback).apply { + setAttribute("android:inputType", inputType.type) + addDefault(default) + addSummary(summary?.also { resourceCallback?.invoke(it) }) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt new file mode 100644 index 0000000..f62c24b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/resource/patch/AbstractSettingsResourcePatch.kt @@ -0,0 +1,135 @@ +package app.revanced.patches.shared.settings.resource.patch + +import app.revanced.patcher.data.DomFileEditor +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.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.IResource +import app.revanced.patches.shared.settings.preference.addPreference +import app.revanced.patches.shared.settings.preference.addResource +import app.revanced.patches.shared.settings.preference.impl.ArrayResource +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +import org.w3c.dom.Node + +/** + * Abstract settings resource patch + * + * @param preferenceFileName Name of the settings preference xml file + * @param sourceDirectory Source directory to copy the preference template from + */ +abstract class AbstractSettingsResourcePatch( + private val preferenceFileName: String, + private val sourceDirectory: String, +) : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + /* + * used for self-restart + */ + context.xmlEditor["AndroidManifest.xml"].use { editor -> + editor.file.getElementsByTagName("manifest").item(0).also { + it.appendChild(it.ownerDocument.createElement("uses-permission").also { element -> + element.setAttribute("android:name", "android.permission.SCHEDULE_EXACT_ALARM") + }) + } + } + + /* copy preference template from source dir */ + context.copyResources( + sourceDirectory, + ResourceUtils.ResourceGroup( + "xml", "$preferenceFileName.xml" + ) + ) + + /* prepare xml editors */ + stringsEditor = context.xmlEditor["res/values/strings.xml"] + arraysEditor = context.xmlEditor["res/values/arrays.xml"] + revancedPreferencesEditor = context.xmlEditor["res/xml/$preferenceFileName.xml"] + + return PatchResultSuccess() + } + + internal companion object { + private var revancedPreferenceNode: Node? = null + private var stringsNode: Node? = null + private var arraysNode: Node? = null + + private var strings = mutableListOf() + + private var revancedPreferencesEditor: DomFileEditor? = null + set(value) { + field = value + revancedPreferenceNode = value.getNode("PreferenceScreen") + } + private var stringsEditor: DomFileEditor? = null + set(value) { + field = value + stringsNode = value.getNode("resources") + } + private var arraysEditor: DomFileEditor? = null + set(value) { + field = value + arraysNode = value.getNode("resources") + } + + /** + * Add a new string to the resources. + * + * @param identifier The key of the string. + * @param value The value of the string. + * @throws IllegalArgumentException if the string already exists. + */ + fun addString(identifier: String, value: String, formatted: Boolean) = + StringResource(identifier, value, formatted).include() + + /** + * Add an array to the resources. + * + * @param arrayResource The array resource to add. + */ + fun addArray(arrayResource: ArrayResource) = + arraysNode!!.addResource(arrayResource) { it.include() } + + /** + * Add a preference to the settings. + * + * @param preference The preference to add. + */ + fun addPreference(preference: BasePreference) = + revancedPreferenceNode!!.addPreference(preference) { it.include() } + + /** + * Add a new resource to the resources. + * + * @throws IllegalArgumentException if the resource already exists. + */ + internal fun IResource.include() { + when (this) { + is StringResource -> { + if (strings.any { it.name == name }) return + strings.add(this) + } + + is ArrayResource -> addArray(this) + else -> throw NotImplementedError("Unsupported resource type") + } + } + + internal fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0) + } + + override fun close() { + // merge all strings, skip duplicates + strings.forEach { + stringsNode!!.addResource(it) + } + + revancedPreferencesEditor?.close() + stringsEditor?.close() + arraysEditor?.close() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt b/src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt new file mode 100644 index 0000000..a257af9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/settings/util/AbstractPreferenceScreen.kt @@ -0,0 +1,89 @@ +package app.revanced.patches.shared.settings.util + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.impl.PreferenceCategory +import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.preference.impl.StringResource +import java.io.Closeable + +internal abstract class AbstractPreferenceScreen( + private val root: MutableList = mutableListOf() +) : Closeable { + + override fun close() { + if (root.isEmpty()) + return + + for (preference in root.sortedBy { it.title }) { + commit(preference.transform()) + } + } + + /** + * Finalize and insert root preference into resource patch + */ + abstract fun commit(screen: PreferenceScreen) + + open inner class Screen( + key: String, + title: String, + val summary: String? = null, + preferences: MutableList = mutableListOf(), + val categories: MutableList = mutableListOf() + ) : BasePreferenceCollection(key, title, preferences) { + override fun transform(): PreferenceScreen { + return PreferenceScreen( + key, + StringResource("${key}_title", title), + preferences.sortedBy { it.title.value } + + categories.sortedBy { it.title }.map { it.transform() }, + summary?.let { summary -> + StringResource("${key}_summary", summary) + } + ) + } + + private fun ensureScreenInserted() { + // Add to screens if not yet done + if(!this@AbstractPreferenceScreen.root.contains(this)) + this@AbstractPreferenceScreen.root.add(this) + } + + fun addPreferences(vararg preferences: BasePreference) { + ensureScreenInserted() + this.preferences.addAll(preferences) + } + + open inner class Category( + key: String, + title: String, + preferences: MutableList = mutableListOf() + ): BasePreferenceCollection(key, title, preferences) { + override fun transform(): PreferenceCategory { + return PreferenceCategory( + key, + StringResource("${key}_title", title), + preferences.sortedBy { it.title.value } + ) + } + + fun addPreferences(vararg preferences: BasePreference) { + ensureScreenInserted() + + // Add to categories if not yet done + if(!this@Screen.categories.contains(this)) + this@Screen.categories.add(this) + + this.preferences.addAll(preferences) + } + } + } + + abstract class BasePreferenceCollection( + val key: String, + val title: String, + val preferences: MutableList = mutableListOf() + ) { + abstract fun transform(): BasePreference + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt new file mode 100644 index 0000000..3aad515 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/audio/annotation/DisableCaptureRestrictionCompatibility.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.spotify.audio.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package("com.spotify.music")] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DisableCaptureRestrictionCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt new file mode 100644 index 0000000..88aae6a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/audio/bytecode/patch/DisableCaptureRestrictionBytecodePatch.kt @@ -0,0 +1,86 @@ +package app.revanced.patches.spotify.audio.bytecode.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.instruction +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.spotify.audio.annotation.DisableCaptureRestrictionCompatibility +import app.revanced.patches.spotify.audio.fingerprints.DisableCaptureRestrictionAudioDriverFingerprint +import app.revanced.patches.spotify.audio.resource.patch.DisableCaptureRestrictionResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.MethodReference + +@Patch +@Name("disable-capture-restriction") +@DependsOn([DisableCaptureRestrictionResourcePatch::class]) +@Description("Allows capturing Spotify's audio output while screen sharing or screen recording.") +@DisableCaptureRestrictionCompatibility +@Version("0.0.2") +class DisableCaptureRestrictionBytecodePatch : BytecodePatch( + listOf( + DisableCaptureRestrictionAudioDriverFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = DisableCaptureRestrictionAudioDriverFingerprint.result!!.mutableMethod + + var invokePosition: Int? = null + var invokeParamRegister: Int? = null + + // Find INVOKE_VIRTUAL opcode with call to AudioAttributesBuilder.setAllowedCapturePolicy(I) + for ((index, instruction) in method.implementation!!.instructions.withIndex()) { + if(instruction.opcode != Opcode.INVOKE_VIRTUAL) + continue + + val methodName = ((instruction as ReferenceInstruction).reference as MethodReference).name + if (methodName != "setAllowedCapturePolicy") + continue + + // Store register of the integer parameter for setAllowedCapturePolicy + invokeParamRegister = (instruction as FiveRegisterInstruction).registerD + invokePosition = index + } + + if(invokePosition == null || invokeParamRegister == null) + return PatchResultError("Cannot find setAllowedCapturePolicy method call") + + // Walk back to the const/4 instruction that sets the parameter register + var matchFound = false + for (index in invokePosition downTo 0) { + val instruction = method.instruction(index) + if(instruction.opcode != Opcode.CONST_4) + continue + + val register = (instruction as OneRegisterInstruction).registerA + if(register != invokeParamRegister) + continue + + // Replace parameter value + method.replaceInstruction( + index, "const/4 v$register, $ALLOW_CAPTURE_BY_ALL" + ) + matchFound = true + break + } + + return if (matchFound) + PatchResultSuccess() + else + PatchResultError("Const instruction not found") + } + + private companion object { + const val ALLOW_CAPTURE_BY_ALL = 0x01 + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt new file mode 100644 index 0000000..1917555 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/audio/fingerprints/DisableCaptureRestrictionAudioDriverFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.spotify.audio.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object DisableCaptureRestrictionAudioDriverFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass == "Lcom/spotify/playback/playbacknative/AudioDriver;" && methodDef.name == "constructAudioAttributes" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt b/src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt new file mode 100644 index 0000000..5088cce --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/audio/resource/patch/DisableCaptureRestrictionResourcePatch.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.spotify.audio.resource.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.patches.spotify.audio.annotation.DisableCaptureRestrictionCompatibility +import org.w3c.dom.Element + +@Name("disable-capture-restriction-resource-patch") +@Description("Sets allowAudioPlaybackCapture in manifest to true.") +@DisableCaptureRestrictionCompatibility +@Version("0.0.2") +class DisableCaptureRestrictionResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + // create an xml editor instance + context.xmlEditor["AndroidManifest.xml"].use { dom -> + // get the application node + val applicationNode = dom + .file + .getElementsByTagName("application") + .item(0) as Element + + // set allowAudioPlaybackCapture attribute to true + applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true") + } + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt new file mode 100644 index 0000000..bc24c39 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/annotations/ThemeCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.spotify.layout.theme.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.spotify.music")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ThemeCompatibility diff --git a/src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt new file mode 100644 index 0000000..dfbc7fd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/layout/theme/patch/ThemePatch.kt @@ -0,0 +1,66 @@ +package app.revanced.patches.spotify.layout.theme.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.* +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.spotify.layout.theme.annotations.ThemeCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import org.w3c.dom.Element + +@Patch +@DependsOn([FixLocaleConfigErrorPatch::class]) +@Name("spotify-theme") +@Description("Applies a custom theme.") +@ThemeCompatibility +@Version("0.0.1") +class ThemePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + context.xmlEditor["res/values/colors.xml"].use { editor -> + val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element + + for (i in 0 until resourcesNode.childNodes.length) { + val node = resourcesNode.childNodes.item(i) as? Element ?: continue + + node.textContent = when (node.getAttribute("name")) { + "gray_7" -> backgroundColor!! + "dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor!! + "dark_brightaccent_background_press" -> accentPressedColor!! + else -> continue + } + } + } + + return PatchResultSuccess() + } + + companion object : OptionsContainer() { + var backgroundColor: String? by option( + PatchOption.StringOption( + key = "backgroundColor", + default = "@android:color/black", + title = "Background color", + description = "The background color. Can be a hex color or a resource reference.", + ) + ) + var accentColor: String? by option( + PatchOption.StringOption( + key = "accentColor", + default = "#ff1ed760", + title = "Accent color", + description = "The accent color ('spotify green' by default). Can be a hex color or a resource reference.", + ) + ) + var accentPressedColor: String? by option( + PatchOption.StringOption( + key = "accentPressedColor", + default = "#ff169c46", + title = "Pressed accent for the dark theme", + description = "The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.", + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt new file mode 100644 index 0000000..41f6531 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/annotations/PremiumNavbarTabCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.spotify.premium_navbar_tab.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.spotify.music")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PremiumNavbarTabCompatibility diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt new file mode 100644 index 0000000..296c87f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.spotify.premium_navbar_tab.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object AddPremiumNavbarTabFingerprint : MethodFingerprint( + parameters = listOf("L", "L", "L", "L", "L", "L") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt new file mode 100644 index 0000000..e67bd8f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/fingerprints/AddPremiumNavbarTabParentFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.spotify.premium_navbar_tab.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object AddPremiumNavbarTabParentFingerprint : MethodFingerprint( + strings = listOf("com.samsung.android.samsungaccount.action.REQUEST_AUTHCODE") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt new file mode 100644 index 0000000..e45bb6f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/spotify/premium_navbar_tab/patch/PremiumNavbarTabPatch.kt @@ -0,0 +1,63 @@ +package app.revanced.patches.spotify.premium_navbar_tab.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.removeInstruction +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.spotify.premium_navbar_tab.annotations.PremiumNavbarTabCompatibility +import app.revanced.patches.spotify.premium_navbar_tab.fingerprints.AddPremiumNavbarTabFingerprint +import app.revanced.patches.spotify.premium_navbar_tab.fingerprints.AddPremiumNavbarTabParentFingerprint +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +@Patch +@Name("hide-premium-navbar") +@Description("Removes the premium tab from the navbar.") +@PremiumNavbarTabCompatibility +@Version("0.0.1") +@DependsOn([ResourceMappingPatch::class]) +class PremiumNavbarTabPatch : BytecodePatch( + listOf( + AddPremiumNavbarTabParentFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val parentResult = AddPremiumNavbarTabParentFingerprint.result!! + AddPremiumNavbarTabFingerprint.resolve(context, parentResult.classDef) + + val result = AddPremiumNavbarTabFingerprint.result!! + + val method = result.mutableMethod + val methodInstructions = method.implementation!!.instructions + val lastInstructionIdx = methodInstructions.size - 1 + + val premiumTabId = + ResourceMappingPatch.resourceMappings.single { it.type == "id" && it.name == "premium_tab" }.id + + var removeAmount = 2 + // 2nd const remove method + for ((i, instruction) in methodInstructions.asReversed().withIndex()) { + if (instruction.opcode.ordinal != Opcode.CONST.ordinal) continue + if ((instruction as WideLiteralInstruction).wideLiteral != premiumTabId) continue + + val findThreshold = 10 + val constIndex = lastInstructionIdx - i + val invokeInstruction = methodInstructions.subList(constIndex, constIndex + findThreshold).first { + it.opcode.ordinal == Opcode.INVOKE_VIRTUAL_RANGE.ordinal + } + method.removeInstruction(methodInstructions.indexOf(invokeInstruction)) + + if (--removeAmount == 0) break + } + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt new file mode 100644 index 0000000..cc09664 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/annotations/UnlockThemesCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.ticktick.misc.themeunlock.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.ticktick.task")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnlockThemesCompatibility diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt new file mode 100644 index 0000000..dccab40 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/CheckLockedThemesFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.ticktick.misc.themeunlock.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CheckLockedThemesFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("Theme;") && methodDef.name == "isLockedTheme" + } +) diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt new file mode 100644 index 0000000..dc22b02 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/fingerprints/SetThemeFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.ticktick.misc.themeunlock.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SetThemeFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("ThemePreviewActivity;") && methodDef.name == "lambda\$updateUserBtn\$1" + } +) diff --git a/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt new file mode 100644 index 0000000..e1b6664 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/ticktick/misc/themeunlock/patch/UnlockThemePatch.kt @@ -0,0 +1,43 @@ +package app.revanced.patches.ticktick.misc.themeunlock.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.addInstructions +import app.revanced.patcher.extensions.removeInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.ticktick.misc.themeunlock.annotations.UnlockThemesCompatibility +import app.revanced.patches.ticktick.misc.themeunlock.fingerprints.CheckLockedThemesFingerprint +import app.revanced.patches.ticktick.misc.themeunlock.fingerprints.SetThemeFingerprint + +@Patch +@Name("unlock-themes") +@Description("Unlocks all themes.") +@UnlockThemesCompatibility +@Version("0.0.1") +class UnlockProPatch : BytecodePatch( + listOf( + CheckLockedThemesFingerprint, + SetThemeFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val lockedThemesMethod = CheckLockedThemesFingerprint.result!!.mutableMethod + lockedThemesMethod.addInstructions( + 0, + """ + const/4 v0, 0x0 + return v0 + """ + ) + + val setThemeMethod = SetThemeFingerprint.result!!.mutableMethod + setThemeMethod.removeInstructions(0, 9) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt new file mode 100644 index 0000000..529acaf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/annotations/HideAdsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.ad.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideAdsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt new file mode 100644 index 0000000..fc252a2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/ConvertHelpFeedItemListFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.tiktok.ad.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object ConvertHelpFeedItemListFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/ConvertHelp;") && + methodDef.name.endsWith("${'$'}FeedItemList") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt new file mode 100644 index 0000000..4f9de5d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/fingerprints/FeedItemListCloneFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.tiktok.ad.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object FeedItemListCloneFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/FeedItemList;") && methodDef.name == "clone" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt new file mode 100644 index 0000000..862f805 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/ad/patch/HideAdsPatch.kt @@ -0,0 +1,57 @@ +package app.revanced.patches.tiktok.ad.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.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.tiktok.ad.annotations.HideAdsCompatibility +import app.revanced.patches.tiktok.ad.fingerprints.ConvertHelpFeedItemListFingerprint +import app.revanced.patches.tiktok.ad.fingerprints.FeedItemListCloneFingerprint +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction +import org.jf.dexlib2.iface.reference.FieldReference + +@Patch +@Name("hide-ads") +@Description("Removes ads from TikTok.") +@HideAdsCompatibility +@Version("0.0.1") +class HideAdsPatch : BytecodePatch( + listOf( + FeedItemListCloneFingerprint, + ConvertHelpFeedItemListFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + listOf( + FeedItemListCloneFingerprint, + ConvertHelpFeedItemListFingerprint + ).forEach { fingerprint -> + val method = fingerprint.result!!.mutableMethod + // iterate all instructions in the clone method + for ((index, instruction) in method.implementation!!.instructions.withIndex()) { + // conditions for the instruction we need + if (instruction.opcode.ordinal != Opcode.IPUT_OBJECT.ordinal) continue + val preloadAdsFieldInstruction = (instruction as? ReferenceInstruction) + if ((preloadAdsFieldInstruction?.reference as? FieldReference)?.name != "preloadAds") continue + + // set null instead of the field "preloadAds" + val overrideRegister = (preloadAdsFieldInstruction as TwoRegisterInstruction).registerA + method.addInstruction( + index, + "const/4 v$overrideRegister, 0x0" + ) + return@forEach + } + return PatchResultError("Can not find required instruction.") + } + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt new file mode 100644 index 0000000..d33f698 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/annotations/FeedFilterCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.feedfilter.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FeedFilterCompatibility diff --git a/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt new file mode 100644 index 0000000..e2f05c0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/fingerprints/FeedApiServiceLIZFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.tiktok.feedfilter.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object FeedApiServiceLIZFingerprint : MethodFingerprint( + access = AccessFlags.PUBLIC or AccessFlags.STATIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC, + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/FeedApiService;") && methodDef.name == "LIZ" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt new file mode 100644 index 0000000..fb6ba0b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/patch/FeedFilterPatch.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.tiktok.feedfilter.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.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.tiktok.feedfilter.annotations.FeedFilterCompatibility +import app.revanced.patches.tiktok.feedfilter.fingerprints.FeedApiServiceLIZFingerprint +import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint +import app.revanced.patches.tiktok.misc.settings.patch.SettingsPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("feed-filter") +@Description("Filters tiktok videos: removing ads, removing livestreams.") +@FeedFilterCompatibility +@Version("0.0.1") +class FeedFilterPatch : BytecodePatch( + listOf( + FeedApiServiceLIZFingerprint, + SettingsStatusLoadFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = FeedApiServiceLIZFingerprint.result!!.mutableMethod + for ((index, instruction) in method.implementation!!.instructions.withIndex()) { + if (instruction.opcode != Opcode.RETURN_OBJECT) continue + val feedItemsRegister = (instruction as OneRegisterInstruction).registerA + method.addInstruction( + index, + "invoke-static {v$feedItemsRegister}, Lapp/revanced/tiktok/feedfilter/FeedItemsFilter;->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V" + ) + break + } + val method2 = SettingsStatusLoadFingerprint.result!!.mutableMethod + method2.addInstruction( + 0, + "invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsStatus;->enableFeedFilter()V" + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt new file mode 100644 index 0000000..2a353e8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/annotations/DownloadsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.interaction.downloads.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DownloadsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt new file mode 100644 index 0000000..f1e8516 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.interaction.downloads.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object ACLCommonShareFingerprint : MethodFingerprint( + "I", + AccessFlags.PUBLIC or AccessFlags.FINAL, + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/ACLCommonShare;") && + methodDef.name == "getCode" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt new file mode 100644 index 0000000..384530d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint2.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.tiktok.interaction.downloads.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.tiktok.interaction.downloads.annotations.DownloadsCompatibility +import org.jf.dexlib2.AccessFlags + +@Name("acl-common-share-get-show-type") +@DownloadsCompatibility +@Version("0.0.1") +object ACLCommonShareFingerprint2 : MethodFingerprint( + "I", + AccessFlags.PUBLIC or AccessFlags.FINAL, + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/ACLCommonShare;") && + methodDef.name == "getShowType" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt new file mode 100644 index 0000000..cf585dd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/ACLCommonShareFingerprint3.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.tiktok.interaction.downloads.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.tiktok.interaction.downloads.annotations.DownloadsCompatibility +import org.jf.dexlib2.AccessFlags + +@Name("acl-common-share-get-transcode") +@DownloadsCompatibility +@Version("0.0.1") +object ACLCommonShareFingerprint3 : MethodFingerprint( + "I", + AccessFlags.PUBLIC or AccessFlags.FINAL, + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/ACLCommonShare;") && + methodDef.name == "getTranscode" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt new file mode 100644 index 0000000..8daff09 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/fingerprints/DownloadPathParentFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.tiktok.interaction.downloads.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object DownloadPathParentFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf( + "code", + "reason", + "params insufficient" + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt new file mode 100644 index 0000000..a3ea757 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/patch/DownloadsPatch.kt @@ -0,0 +1,131 @@ +package app.revanced.patches.tiktok.interaction.downloads.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.data.toMethodWalker +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.extensions.replaceInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.tiktok.interaction.downloads.annotations.DownloadsCompatibility +import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint +import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint2 +import app.revanced.patches.tiktok.interaction.downloads.fingerprints.ACLCommonShareFingerprint3 +import app.revanced.patches.tiktok.interaction.downloads.fingerprints.DownloadPathParentFingerprint +import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint +import app.revanced.patches.tiktok.misc.settings.patch.SettingsPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.StringReference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("downloads") +@Description("Removes download restrictions and changes the default path to download to.") +@DownloadsCompatibility +@Version("0.0.1") +class DownloadsPatch : BytecodePatch( + listOf( + ACLCommonShareFingerprint, + ACLCommonShareFingerprint2, + ACLCommonShareFingerprint3, + DownloadPathParentFingerprint, + SettingsStatusLoadFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method1 = ACLCommonShareFingerprint.result!!.mutableMethod + method1.replaceInstructions( + 0, + """ + const/4 v0, 0x0 + return v0 + """ + ) + val method2 = ACLCommonShareFingerprint2.result!!.mutableMethod + method2.replaceInstructions( + 0, + """ + const/4 v0, 0x2 + return v0 + """ + ) + //Download videos without watermark. + val method3 = ACLCommonShareFingerprint3.result!!.mutableMethod + method3.addInstructions( + 0, + """ + invoke-static {}, Lapp/revanced/tiktok/download/DownloadsPatch;->shouldRemoveWatermark()Z + move-result v0 + if-eqz v0, :noremovewatermark + const/4 v0, 0x1 + return v0 + :noremovewatermark + nop + """ + ) + //Change the download path patch + val method4 = DownloadPathParentFingerprint.result!!.mutableMethod + val implementation4 = method4.implementation + val instructions = implementation4!!.instructions + var targetOffset = -1 + //Search for the target method called instruction offset. + for ((index, instruction) in instructions.withIndex()) { + if (instruction.opcode != Opcode.CONST_STRING) continue + val reference = (instruction as ReferenceInstruction).reference as StringReference + if (reference.string != "video/mp4") continue + val targetInstruction = instructions[index + 1] + if (targetInstruction.opcode != Opcode.INVOKE_STATIC) continue + targetOffset = index + 1 + break + } + if (targetOffset == -1) return PatchResultError("Can not find download path uri method.") + //Change videos' download path. + val downloadUriMethod = context + .toMethodWalker(DownloadPathParentFingerprint.result!!.method) + .nextMethod(targetOffset, true) + .getMethod() as MutableMethod + downloadUriMethod.implementation!!.instructions.forEachIndexed { index, instruction -> + if (instruction.opcode == Opcode.SGET_OBJECT) { + val overrideRegister = (instruction as OneRegisterInstruction).registerA + downloadUriMethod.addInstructions( + index + 1, + """ + invoke-static {}, Lapp/revanced/tiktok/download/DownloadsPatch;->getDownloadPath()Ljava/lang/String; + move-result-object v$overrideRegister + """ + ) + } + if (instruction.opcode == Opcode.CONST_STRING) { + val string = ((instruction as ReferenceInstruction).reference as StringReference).string + if (string.contains("/Camera")) { + val overrideRegister = (instruction as OneRegisterInstruction).registerA + val overrideString = string.replace("/Camera", "") + downloadUriMethod.replaceInstruction( + index, + """ + const-string v$overrideRegister, "$overrideString" + """ + ) + } + } + } + val method5 = SettingsStatusLoadFingerprint.result!!.mutableMethod + method5.addInstruction( + 0, + "invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsStatus;->enableDownload()V" + ) + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt new file mode 100644 index 0000000..713de94 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/annotations/ShowSeekbarCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.interaction.seekbar.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ShowSeekbarCompatibility diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt new file mode 100644 index 0000000..fb64631 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/fingerprints/AwemeGetVideoControlFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.tiktok.interaction.seekbar.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object AwemeGetVideoControlFingerprint : MethodFingerprint( + "L", + AccessFlags.PUBLIC.value, + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/Aweme;") && methodDef.name == "getVideoControl" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt new file mode 100644 index 0000000..0c99ab3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/seekbar/patch/ShowSeekbarPatch.kt @@ -0,0 +1,47 @@ +package app.revanced.patches.tiktok.interaction.seekbar.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.tiktok.interaction.seekbar.annotations.ShowSeekbarCompatibility +import app.revanced.patches.tiktok.interaction.seekbar.fingerprints.AwemeGetVideoControlFingerprint +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction11n +import org.jf.dexlib2.builder.instruction.BuilderInstruction22c + +@Patch +@Name("show-seekbar") +@Description("Shows progress bar for all video.") +@ShowSeekbarCompatibility +@Version("0.0.1") +class ShowSeekbarPatch : BytecodePatch( + listOf( + AwemeGetVideoControlFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + //Get VideoControl FieldReference + val videoControl = context.findClass { it.type.endsWith("/VideoControl;") } + ?: return PatchResultError("Can not find target class") + val fieldList = videoControl.immutableClass.fields.associateBy { field -> field.name } + + val method = AwemeGetVideoControlFingerprint.result!!.mutableMethod + val implementation = method.implementation!! + implementation.addInstructions( + 1, listOf( + BuilderInstruction11n(Opcode.CONST_4, 1, 1), + BuilderInstruction22c(Opcode.IPUT, 1, 0, fieldList["showProgressBar"]!!), + BuilderInstruction22c(Opcode.IPUT, 1, 0, fieldList["draftProgressBar"]!!) + ) + ) + return PatchResultSuccess() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt new file mode 100644 index 0000000..33c3dc4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/annotations/PlaybackSpeedCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.interaction.speed.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlaybackSpeedCompatibility diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt new file mode 100644 index 0000000..4e70ca8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/fingerprints/SpeedControlParentFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.tiktok.interaction.speed.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object SpeedControlParentFingerprint : MethodFingerprint( + returnType = "L", + access = AccessFlags.PRIVATE or AccessFlags.FINAL, + parameters = listOf( + "L" + ), + strings = listOf( + "playback_speed" + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt new file mode 100644 index 0000000..c1f15a7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/patch/PlaybackSpeedPatch.kt @@ -0,0 +1,48 @@ +package app.revanced.patches.tiktok.interaction.speed.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.data.toMethodWalker +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.tiktok.interaction.speed.annotations.PlaybackSpeedCompatibility +import app.revanced.patches.tiktok.interaction.speed.fingerprints.SpeedControlParentFingerprint +import org.jf.dexlib2.Opcode + +@Patch +@Name("playback-speed") +@Description("Enables the playback speed option for all videos.") +@PlaybackSpeedCompatibility +@Version("0.0.1") +class PlaybackSpeedPatch : BytecodePatch( + listOf( + SpeedControlParentFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val parentMethod = SpeedControlParentFingerprint.result!!.mutableMethod + val parentMethodInstructions = parentMethod.implementation!!.instructions + for ((index, instruction) in parentMethodInstructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) continue + val isSpeedEnableMethod = context + .toMethodWalker(parentMethod) + .nextMethod(index, true) + .getMethod() as MutableMethod + isSpeedEnableMethod.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + break + } + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt new file mode 100644 index 0000000..e397ecf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/annotations/IntegrationsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.integrations.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class IntegrationsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt new file mode 100644 index 0000000..9afc4ec --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/fingerprints/InitFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.tiktok.misc.integrations.fingerprints + +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint + +object InitFingerprint : IntegrationsFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/AwemeHostApplication;") && + methodDef.name == "onCreate" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt new file mode 100644 index 0000000..6deed29 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/integrations/patch/IntegrationsPatch.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.tiktok.misc.integrations.patch + +import app.revanced.patcher.annotation.Name +import app.revanced.patches.tiktok.misc.integrations.annotations.IntegrationsCompatibility +import app.revanced.patches.tiktok.misc.integrations.fingerprints.InitFingerprint +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch + +@Name("integrations") +@IntegrationsCompatibility +class IntegrationsPatch : AbstractIntegrationsPatch( + "Lapp/revanced/tiktok/utils/ReVancedUtils;", + listOf(InitFingerprint) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt new file mode 100644 index 0000000..64acf15 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/annotations/DisableLoginRequirementCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.login.disablerequirement.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DisableLoginRequirementCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt new file mode 100644 index 0000000..842dcae --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +object MandatoryLoginServiceFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/MandatoryLoginService;") && + methodDef.name == "enableForcedLogin" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt new file mode 100644 index 0000000..59015a6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/fingerprints/MandatoryLoginServiceFingerprint2.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.tiktok.misc.login.disablerequirement.annotations.DisableLoginRequirementCompatibility + +@Name("mandatory-login-service-fingerprint2") +@DisableLoginRequirementCompatibility +@Version("0.0.1") +object MandatoryLoginServiceFingerprint2 : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/MandatoryLoginService;") && + methodDef.name == "shouldShowForcedLogin" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt new file mode 100644 index 0000000..0967f72 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/disablerequirement/patch/DisableLoginRequirementPatch.kt @@ -0,0 +1,43 @@ +package app.revanced.patches.tiktok.misc.login.disablerequirement.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.tiktok.misc.login.disablerequirement.annotations.DisableLoginRequirementCompatibility +import app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints.MandatoryLoginServiceFingerprint +import app.revanced.patches.tiktok.misc.login.disablerequirement.fingerprints.MandatoryLoginServiceFingerprint2 + +@Patch +@Name("disable-login-requirement") +@Description("Do not force login.") +@DisableLoginRequirementCompatibility +@Version("0.0.1") +class DisableLoginRequirementPatch : BytecodePatch( + listOf( + MandatoryLoginServiceFingerprint, + MandatoryLoginServiceFingerprint2 + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + listOf( + MandatoryLoginServiceFingerprint, + MandatoryLoginServiceFingerprint2 + ).forEach { fingerprint -> + val method = fingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/4 v0, 0x0 + return v0 + """ + ) + } + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt new file mode 100644 index 0000000..0f03b09 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/annotations/FixGoogleLoginCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.login.fixgoogle.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FixGoogleLoginCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt new file mode 100644 index 0000000..2e1a1ea --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleAuthAvailableFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object GoogleAuthAvailableFingerprint : MethodFingerprint( + returnType = "Z", + access = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf(), + customFingerprint = { methodDef -> + methodDef.definingClass == "Lcom/bytedance/lobby/google/GoogleAuth;" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt new file mode 100644 index 0000000..26e51a5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/fingerprints/GoogleOneTapAuthAvailableFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object GoogleOneTapAuthAvailableFingerprint : MethodFingerprint( + returnType = "Z", + access = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf(), + customFingerprint = { methodDef -> + methodDef.definingClass == "Lcom/bytedance/lobby/google/GoogleOneTapAuth;" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt new file mode 100644 index 0000000..9e27025 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/patch/FixGoogleLoginPatch.kt @@ -0,0 +1,44 @@ +package app.revanced.patches.tiktok.misc.login.fixgoogle.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.tiktok.misc.login.fixgoogle.annotations.FixGoogleLoginCompatibility +import app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints.GoogleAuthAvailableFingerprint +import app.revanced.patches.tiktok.misc.login.fixgoogle.fingerprints.GoogleOneTapAuthAvailableFingerprint + +@Patch +@Name("fix-google-login") +@Description("Allows logging in with a Google account.") +@FixGoogleLoginCompatibility +@Version("0.0.1") +class FixGoogleLoginPatch : BytecodePatch( + listOf( + GoogleOneTapAuthAvailableFingerprint, + GoogleAuthAvailableFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + listOf( + GoogleOneTapAuthAvailableFingerprint, + GoogleAuthAvailableFingerprint + ).forEach { + with(it.result!!.mutableMethod) { + addInstructions( + 0, + """ + const/4 v0, 0x0 + return v0 + """ + ) + } + } + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt new file mode 100644 index 0000000..8aa98d7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/annotations/SettingsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.settings.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SettingsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt new file mode 100644 index 0000000..e734a6f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AboutViewFingerprint.kt @@ -0,0 +1,39 @@ +package app.revanced.patches.tiktok.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +@FuzzyPatternScanMethod(4) +object AboutViewFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.MOVE, + Opcode.INVOKE_DIRECT_RANGE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.NEW_INSTANCE, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.CONST_4, + Opcode.CONST_STRING, + Opcode.INVOKE_DIRECT_RANGE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.CONST, + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.CONST + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt new file mode 100644 index 0000000..69778a6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/AdPersonalizationActivityOnCreateFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.tiktok.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object AdPersonalizationActivityOnCreateFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/AdPersonalizationActivity;") && + methodDef.name == "onCreate" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt new file mode 100644 index 0000000..46f2ba6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsOnViewCreatedFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.tiktok.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SettingsOnViewCreatedFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/SettingNewVersionFragment;") && + methodDef.name == "onViewCreated" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt new file mode 100644 index 0000000..c2f1d3c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.tiktok.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SettingsStatusLoadFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("Lapp/revanced/tiktok/settingsmenu/SettingsStatus;") && + methodDef.name == "load" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt new file mode 100644 index 0000000..0fcd094 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/patch/SettingsPatch.kt @@ -0,0 +1,120 @@ +package app.revanced.patches.tiktok.misc.settings.patch + +import app.revanced.extensions.toErrorResult +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.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.extensions.replaceInstruction +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.tiktok.misc.settings.annotations.SettingsCompatibility +import app.revanced.patches.tiktok.misc.settings.fingerprints.AboutViewFingerprint +import app.revanced.patches.tiktok.misc.settings.fingerprints.AdPersonalizationActivityOnCreateFingerprint +import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsOnViewCreatedFingerprint +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.instruction.formats.Instruction35c +import org.jf.dexlib2.iface.reference.StringReference +import org.jf.dexlib2.iface.reference.TypeReference + +@Patch +@DependsOn([IntegrationsPatch::class]) +@Name("settings") +@Description("Adds ReVanced settings to TikTok.") +@SettingsCompatibility +@Version("0.0.1") +class SettingsPatch : BytecodePatch( + listOf( + AdPersonalizationActivityOnCreateFingerprint, + SettingsOnViewCreatedFingerprint, + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsOnViewCreatedFingerprint.result?.let { + AboutViewFingerprint.resolve(context, it.method, it.classDef) + } + // Patch Settings UI to add 'Revanced Settings'. + val targetIndexes = findOptionsOnClickIndex() + with(SettingsOnViewCreatedFingerprint.result!!.mutableMethod) { + for (index in targetIndexes) { + if ( + instruction(index).opcode != Opcode.NEW_INSTANCE || + instruction(index - 4).opcode != Opcode.MOVE_RESULT_OBJECT + ) + return PatchResultError("Hardcode offset changed.") + patchOptionNameAndOnClickEvent(index, context) + } + } + // Implement settings screen in `AdPersonalizationActivity` + with(AdPersonalizationActivityOnCreateFingerprint.result!!.mutableMethod) { + for ((index, instruction) in implementation!!.instructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_SUPER) continue + val thisRegister = (instruction as Instruction35c).registerC + addInstructions( + index + 1, + """ + invoke-static {v$thisRegister}, Lapp/revanced/tiktok/settingsmenu/SettingsMenu;->initializeSettings(Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;)V + return-void + """ + ) + break + } + } + return PatchResultSuccess() + } + + private fun findOptionsOnClickIndex(): IntArray { + val results = IntArray(2) + SettingsOnViewCreatedFingerprint.result?.apply { + for ((index, instruction) in mutableMethod.implementation!!.instructions.withIndex()) { + // Old UI settings option to replace to 'Revanced Settings' + if (instruction.opcode == Opcode.CONST_STRING) { + val string = ((instruction as ReferenceInstruction).reference as StringReference).string + if (string == "copyright_policy") { + results[0] = index - 2 + break + } + } + } + + // New UI settings option to replace to 'Revanced Settings' + results[1] = AboutViewFingerprint.result!!.scanResult.patternScanResult!!.startIndex + } ?: throw SettingsOnViewCreatedFingerprint.toErrorResult() + return results + } + + private fun patchOptionNameAndOnClickEvent(index: Int, context: BytecodeContext) { + with(SettingsOnViewCreatedFingerprint.result!!.mutableMethod) { + // Patch option name + val overrideRegister = (instruction(index - 4) as OneRegisterInstruction).registerA + replaceInstruction( + index - 4, + """ + const-string v$overrideRegister, "Revanced Settings" + """ + ) + + // Patch option OnClick Event + with(((instruction(index) as ReferenceInstruction).reference as TypeReference).type) { + context.findClass(this)!!.mutableClass.methods.first { it.name == "onClick" } + .addInstructions( + 0, + """ + invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsMenu;->startSettingsActivity()V + return-void + """ + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt new file mode 100644 index 0000000..a775e5c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/annotations/SpoofSimCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.tiktok.misc.spoof.sim.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.ss.android.ugc.trill"), + Package("com.zhiliaoapp.musically") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SpoofSimCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt new file mode 100644 index 0000000..822a2e2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/patch/SpoofSimPatch.kt @@ -0,0 +1,110 @@ +package app.revanced.patches.tiktok.misc.spoof.sim.patch + +import app.revanced.extensions.findMutableMethodOf +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.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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.tiktok.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.tiktok.misc.settings.fingerprints.SettingsStatusLoadFingerprint +import app.revanced.patches.tiktok.misc.settings.patch.SettingsPatch +import app.revanced.patches.tiktok.misc.spoof.sim.annotations.SpoofSimCompatibility +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.instruction.formats.Instruction35c +import org.jf.dexlib2.iface.reference.MethodReference + +@Patch(false) +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("sim-spoof") +@Description("Spoofs the information which is retrieved from the sim-card.") +@SpoofSimCompatibility +@Version("0.0.1") +class SpoofSimPatch : BytecodePatch() { + private companion object { + val replacements = hashMapOf( + "getSimCountryIso" to "getCountryIso", + "getNetworkCountryIso" to "getCountryIso", + "getSimOperator" to "getOperator", + "getNetworkOperator" to "getOperator", + "getSimOperatorName" to "getOperatorName", + "getNetworkOperatorName" to "getOperatorName" + ) + } + + override fun execute(context: BytecodeContext): PatchResult { + // Find all api call to check sim information + buildMap { + context.classes.forEach { classDef -> + classDef.methods.let { methods -> + buildMap methodList@{ + methods.forEach methods@{ method -> + with(method.implementation?.instructions ?: return@methods) { + ArrayDeque>().also { patchIndices -> + this.forEachIndexed { index, instruction -> + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@forEachIndexed + + val methodRef = + (instruction as Instruction35c).reference as MethodReference + if (methodRef.definingClass != "Landroid/telephony/TelephonyManager;") return@forEachIndexed + + replacements[methodRef.name]?.let { replacement -> + patchIndices.add(index to replacement) + } + } + }.also { if (it.isEmpty()) return@methods }.let { patches -> + put(method, patches) + } + } + } + } + }.also { if (it.isEmpty()) return@forEach }.let { methodPatches -> + put(classDef, methodPatches) + } + } + }.forEach { (classDef, methods) -> + with(context.proxy(classDef).mutableClass) { + methods.forEach { (method, patches) -> + with(findMutableMethodOf(method)) { + while (!patches.isEmpty()) { + val (index, replacement) = patches.removeLast() + replaceReference(index, replacement) + } + } + } + } + } + + // Enable patch in settings + with(SettingsStatusLoadFingerprint.result!!.mutableMethod) { + addInstruction( + 0, + "invoke-static {}, Lapp/revanced/tiktok/settingsmenu/SettingsStatus;->enableSimSpoof()V" + ) + } + + return PatchResultSuccess() + } + + // Patch Android API and return fake sim information + private fun MutableMethod.replaceReference(index: Int, replacement: String) { + val resultReg = (instruction(index + 1) as OneRegisterInstruction).registerA + + addInstructions( + index + 2, + """ + invoke-static {v$resultReg}, Lapp/revanced/tiktok/spoof/sim/SpoofSimPatch;->$replacement(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$resultReg + """ + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt new file mode 100644 index 0000000..0e39cb8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/annotations/AudioAdsCompatibility.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.ad.audio.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class AudioAdsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt new file mode 100644 index 0000000..56bdf62 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/fingerprints/AudioAdsPresenterPlayFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.ad.audio.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object AudioAdsPresenterPlayFingerprint : MethodFingerprint( + customFingerprint = { method -> + method.definingClass.endsWith("AudioAdsPlayerPresenter;") && method.name == "playAd" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt new file mode 100644 index 0000000..a5cccb1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/audio/patch/AudioAdsPatch.kt @@ -0,0 +1,67 @@ +package app.revanced.patches.twitch.ad.audio.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.addInstructions +import app.revanced.patcher.extensions.instruction +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.twitch.ad.audio.annotations.AudioAdsCompatibility +import app.revanced.patches.twitch.ad.audio.fingerprints.AudioAdsPresenterPlayFingerprint +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("block-audio-ads") +@Description("Blocks audio ads in streams and VODs.") +@AudioAdsCompatibility +@Version("0.0.1") +class AudioAdsPatch : BytecodePatch( + listOf(AudioAdsPresenterPlayFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + // Block playAds call + with(AudioAdsPresenterPlayFingerprint.result!!) { + mutableMethod.addInstructions( + 0, + """ + invoke-static { }, Lapp/revanced/twitch/patches/AudioAdsPatch;->shouldBlockAudioAds()Z + move-result v0 + if-eqz v0, :show_audio_ads + return-void + """, + listOf(ExternalLabel("show_audio_ads", mutableMethod.instruction(0))) + ) + } + + SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences( + SwitchPreference( + "revanced_block_audio_ads", + StringResource( + "revanced_block_audio_ads", + "Block audio ads" + ), + true, + StringResource( + "revanced_block_audio_ads_on", + "Audio ads are blocked" + ), + StringResource( + "revanced_block_audio_ads_off", + "Audio ads are unblocked" + ), + ) + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt new file mode 100644 index 0000000..4103c94 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/annotations/EmbeddedAdsCompatibility.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.ad.embedded.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class EmbeddedAdsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt new file mode 100644 index 0000000..3480440 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/fingerprints/CreateUsherClientFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.ad.embedded.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CreateUsherClientFingerprint : MethodFingerprint( + customFingerprint = { method -> + method.definingClass.endsWith("Ltv/twitch/android/network/OkHttpClientFactory;") && method.name == "buildOkHttpClient" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt new file mode 100644 index 0000000..cf8f53e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/embedded/patch/EmbeddedAdsPatch.kt @@ -0,0 +1,78 @@ +package app.revanced.patches.twitch.ad.embedded.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.MethodFingerprintExtensions.name +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.shared.settings.preference.impl.ArrayResource +import app.revanced.patches.shared.settings.preference.impl.ListPreference +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.twitch.ad.embedded.annotations.EmbeddedAdsCompatibility +import app.revanced.patches.twitch.ad.embedded.fingerprints.CreateUsherClientFingerprint +import app.revanced.patches.twitch.ad.video.patch.VideoAdsPatch +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([VideoAdsPatch::class, IntegrationsPatch::class, SettingsPatch::class]) +@Name("block-embedded-ads") +@Description("Blocks embedded steam ads using services like TTV.lol or PurpleAdBlocker.") +@EmbeddedAdsCompatibility +@Version("0.0.1") +class EmbeddedAdsPatch : BytecodePatch( + listOf(CreateUsherClientFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = CreateUsherClientFingerprint.result ?: return PatchResultError("${CreateUsherClientFingerprint.name} not found") + + // Inject OkHttp3 application interceptor + result.mutableMethod.addInstructions( + 3, + """ + invoke-static {}, Lapp/revanced/twitch/patches/EmbeddedAdsPatch;->createRequestInterceptor()Lapp/revanced/twitch/api/RequestInterceptor; + move-result-object v2 + invoke-virtual {v0, v2}, Lokhttp3/OkHttpClient${"$"}Builder;->addInterceptor(Lokhttp3/Interceptor;)Lokhttp3/OkHttpClient${"$"}Builder; + """ + ) + + SettingsPatch.PreferenceScreen.ADS.SURESTREAM.addPreferences( + ListPreference( + "revanced_block_embedded_ads", + StringResource( + "revanced_block_embedded_ads", + "Block embedded video ads" + ), + ArrayResource( + "revanced_hls_proxies", + listOf( + StringResource("revanced_proxy_disabled", "Disabled"), + StringResource("revanced_proxy_ttv_lol", "TTV LOL proxy"), + StringResource("revanced_proxy_purpleadblock", "PurpleAdBlock proxy"), + ) + ), + ArrayResource( + "revanced_hls_proxies_values", + listOf( + StringResource("key_revanced_proxy_disabled", "disabled"), + StringResource("key_revanced_proxy_ttv_lol", "ttv-lol"), + StringResource("key_revanced_proxy_purpleadblock", "purpleadblock") + ) + ), + "ttv-lol" + ) + ) + + SettingsPatch.addString("revanced_embedded_ads_service_unavailable", "%s is unavailable. Ads may show. Try switching to another ad block service in settings.") + SettingsPatch.addString("revanced_embedded_ads_service_failed", "%s server returned an error. Ads may show. Try switching to another ad block service in settings.") + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt new file mode 100644 index 0000000..dca265b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/shared/util/AbstractAdPatch.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.twitch.ad.shared.util + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.util.smali.ExternalLabel + +abstract class AbstractAdPatch( + val conditionCall: String, + val skipLabelName: String, + internal val fingerprints: Iterable? = null, +) : BytecodePatch(fingerprints) { + + protected fun createConditionInstructions(register: String = "v0") = """ + invoke-static { }, $conditionCall + move-result $register + if-eqz $register, :$skipLabelName + """ + + protected data class ReturnMethod(val returnType: Char = 'V', val value: String = "") + + protected fun BytecodeContext.blockMethods(clazz: String, vararg methodNames: String, returnMethod: ReturnMethod = ReturnMethod()): Boolean { + + return with(findClass(clazz)?.mutableClass) { + this ?: return false + + this.methods.filter { methodNames.contains(it.name) }.forEach { + val retIntructions = when(returnMethod.returnType) { + 'V' -> "return-void" + 'Z' -> """ + const/4 v0, ${returnMethod.value} + return v0 + """ + else -> throw NotImplementedError() + } + it.addInstructions( + 0, + """ + ${createConditionInstructions("v0")} + $retIntructions + """, + listOf(ExternalLabel(skipLabelName, it.instruction(0))) + ) + } + true + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt new file mode 100644 index 0000000..60c4d16 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/annotations/VideoAdsCompatibility.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.ad.video.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class VideoAdsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt new file mode 100644 index 0000000..04c1c09 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/CheckAdEligibilityLambdaFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.twitch.ad.video.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object CheckAdEligibilityLambdaFingerprint : MethodFingerprint( + "L", + AccessFlags.PRIVATE or AccessFlags.FINAL or AccessFlags.STATIC, + listOf("L", "L", "L"), + customFingerprint = { method -> + method.definingClass.endsWith("AdEligibilityFetcher;") && + method.name.contains("shouldRequestAd") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt new file mode 100644 index 0000000..ed38035 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/ContentConfigShowAdsFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.ad.video.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object ContentConfigShowAdsFingerprint : MethodFingerprint( + customFingerprint = { method -> + method.definingClass.endsWith("ContentConfigData;") && method.name == "getShowAds" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt new file mode 100644 index 0000000..3256ad0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/fingerprints/GetReadyToShowAdFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.ad.video.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object GetReadyToShowAdFingerprint : MethodFingerprint( + customFingerprint = { method -> + method.definingClass.endsWith("/StreamDisplayAdsPresenter;") && method.name == "getReadyToShowAdOrAbort" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt new file mode 100644 index 0000000..9b1197e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/ad/video/patch/VideoAdsPatch.kt @@ -0,0 +1,149 @@ +package app.revanced.patches.twitch.ad.video.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.addInstructions +import app.revanced.patcher.extensions.instruction +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.twitch.ad.shared.util.AbstractAdPatch +import app.revanced.patches.twitch.ad.video.annotations.VideoAdsCompatibility +import app.revanced.patches.twitch.ad.video.fingerprints.CheckAdEligibilityLambdaFingerprint +import app.revanced.patches.twitch.ad.video.fingerprints.ContentConfigShowAdsFingerprint +import app.revanced.patches.twitch.ad.video.fingerprints.GetReadyToShowAdFingerprint +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("block-video-ads") +@Description("Blocks video ads in streams and VODs.") +@VideoAdsCompatibility +@Version("0.0.1") +class VideoAdsPatch : AbstractAdPatch( + "Lapp/revanced/twitch/patches/VideoAdsPatch;->shouldBlockVideoAds()Z", + "show_video_ads", + listOf( + ContentConfigShowAdsFingerprint, + CheckAdEligibilityLambdaFingerprint, + GetReadyToShowAdFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + /* Amazon ads SDK */ + context.blockMethods( + "Lcom/amazon/ads/video/player/AdsManagerImpl;", + "playAds" + ) + + /* Twitch ads manager */ + context.blockMethods( + "Ltv/twitch/android/shared/ads/VideoAdManager;", + "checkAdEligibilityAndRequestAd", "requestAd", "requestAds" + ) + + /* Various ad presenters */ + context.blockMethods( + "Ltv/twitch/android/shared/ads/AdsPlayerPresenter;", + "requestAd", "requestFirstAd", "requestFirstAdIfEligible", "requestMidroll", "requestAdFromMultiAdFormatEvent" + ) + + context.blockMethods( + "Ltv/twitch/android/shared/ads/AdsVodPlayerPresenter;", + "requestAd", "requestFirstAd", + ) + + context.blockMethods( + "Ltv/twitch/android/feature/theatre/ads/AdEdgeAllocationPresenter;", + "parseAdAndCheckEligibility", "requestAdsAfterEligibilityCheck", "showAd", "bindMultiAdFormatAllocation" + ) + + /* A/B ad testing experiments */ + context.blockMethods( + "Ltv/twitch/android/provider/experiments/helpers/DisplayAdsExperimentHelper;", + "areDisplayAdsEnabled", + returnMethod = ReturnMethod('Z', "0") + ) + + context.blockMethods( + "Ltv/twitch/android/shared/ads/tracking/MultiFormatAdsTrackingExperiment;", + "shouldUseMultiAdFormatTracker", "shouldUseVideoAdTracker", + returnMethod = ReturnMethod('Z', "0") + ) + + context.blockMethods( + "Ltv/twitch/android/shared/ads/MultiformatAdsExperiment;", + "shouldDisableClientSideLivePreroll", "shouldDisableClientSideVodPreroll", + returnMethod = ReturnMethod('Z', "1") + ) + + // Pretend our player is ineligible for all ads + with(CheckAdEligibilityLambdaFingerprint.result!!) { + mutableMethod.addInstructions( + 0, + """ + ${createConditionInstructions()} + const/4 v0, 0 + invoke-static {v0}, Lio/reactivex/Single;->just(Ljava/lang/Object;)Lio/reactivex/Single; + move-result-object p0 + return-object p0 + """, + listOf(ExternalLabel(skipLabelName, mutableMethod.instruction(0))) + ) + } + + with(GetReadyToShowAdFingerprint.result!!) { + val adFormatDeclined = "Ltv/twitch/android/shared/display/ads/theatre/StreamDisplayAdsPresenter\$Action\$AdFormatDeclined;" + mutableMethod.addInstructions( + 0, + """ + ${createConditionInstructions()} + sget-object p2, $adFormatDeclined->INSTANCE:$adFormatDeclined + invoke-static {p1, p2}, Ltv/twitch/android/core/mvp/presenter/StateMachineKt;->plus(Ltv/twitch/android/core/mvp/presenter/PresenterState;Ltv/twitch/android/core/mvp/presenter/PresenterAction;)Ltv/twitch/android/core/mvp/presenter/StateAndAction; + move-result-object p1 + return-object p1 + """, + listOf(ExternalLabel(skipLabelName, mutableMethod.instruction(0))) + ) + } + + // Spoof showAds JSON field + with(ContentConfigShowAdsFingerprint.result!!) { + mutableMethod.addInstructions(0, """ + ${createConditionInstructions()} + const/4 v0, 0 + :$skipLabelName + return v0 + """ + ) + } + + SettingsPatch.PreferenceScreen.ADS.CLIENT_SIDE.addPreferences( + SwitchPreference( + "revanced_block_video_ads", + StringResource( + "revanced_block_video_ads", + "Block video ads" + ), + true, + StringResource( + "revanced_block_video_ads_on", + "Video ads are blocked" + ), + StringResource( + "revanced_block_video_ads_off", + "Video ads are unblocked" + ), + ) + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt new file mode 100644 index 0000000..c0c71be --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/annotations/ShowDeletedMessagesCompatibility.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.chat.antidelete.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ShowDeletedMessagesCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt new file mode 100644 index 0000000..733710b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/ChatUtilCreateDeletedSpanFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.chat.antidelete.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object ChatUtilCreateDeletedSpanFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/ChatUtil\$Companion;") && methodDef.name == "createDeletedSpanFromChatMessageSpan" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt new file mode 100644 index 0000000..6ecb63c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/DeletedMessageClickableSpanCtorFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.twitch.chat.antidelete.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object DeletedMessageClickableSpanCtorFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("DeletedMessageClickableSpan;") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt new file mode 100644 index 0000000..7e8ee66 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/fingerprints/SetHasModAccessFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.chat.antidelete.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SetHasModAccessFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("DeletedMessageClickableSpan;") && methodDef.name == "setHasModAccess" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt new file mode 100644 index 0000000..dba6e83 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/chat/antidelete/patch/ShowDeletedMessagesPatch.kt @@ -0,0 +1,103 @@ +package app.revanced.patches.twitch.chat.antidelete.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.* +import app.revanced.patcher.patch.* +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.settings.preference.impl.ArrayResource +import app.revanced.patches.shared.settings.preference.impl.ListPreference +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.twitch.chat.antidelete.annotations.ShowDeletedMessagesCompatibility +import app.revanced.patches.twitch.chat.antidelete.fingerprints.* +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("show-deleted-messages") +@Description("Shows deleted chat messages behind a clickable spoiler.") +@ShowDeletedMessagesCompatibility +@Version("0.0.1") +class ShowDeletedMessagesPatch : BytecodePatch( + listOf( + SetHasModAccessFingerprint, + DeletedMessageClickableSpanCtorFingerprint, + ChatUtilCreateDeletedSpanFingerprint + ) +) { + private fun createSpoilerConditionInstructions(register: String = "v0") = """ + invoke-static {}, Lapp/revanced/twitch/patches/ShowDeletedMessagesPatch;->shouldUseSpoiler()Z + move-result $register + if-eqz $register, :no_spoiler + """ + + override fun execute(context: BytecodeContext): PatchResult { + // Spoiler mode: Force set hasModAccess member to true in constructor + with(DeletedMessageClickableSpanCtorFingerprint.result!!.mutableMethod) { + addInstructions( + implementation!!.instructions.lastIndex, /* place in front of return-void */ + """ + ${createSpoilerConditionInstructions()} + const/4 v0, 1 + iput-boolean v0, p0, $definingClass->hasModAccess:Z + """, + listOf(ExternalLabel("no_spoiler", instruction(implementation!!.instructions.lastIndex))) + ) + } + + // Spoiler mode: Disable setHasModAccess setter + with(SetHasModAccessFingerprint.result!!) { + mutableMethod.addInstruction(0, "return-void") + } + + // Cross-out mode: Reformat span of deleted message + with(ChatUtilCreateDeletedSpanFingerprint.result!!) { + mutableMethod.addInstructions( + 0, + """ + invoke-static {p2}, Lapp/revanced/twitch/patches/ShowDeletedMessagesPatch;->reformatDeletedMessage(Landroid/text/Spanned;)Landroid/text/Spanned; + move-result-object v0 + if-eqz v0, :no_reformat + return-object v0 + """, + listOf(ExternalLabel("no_reformat", mutableMethod.instruction(0))) + ) + } + + SettingsPatch.PreferenceScreen.CHAT.GENERAL.addPreferences( + ListPreference( + "revanced_show_deleted_messages", + StringResource( + "revanced_show_deleted_messages_title", + "Show deleted messages" + ), + ArrayResource( + "revanced_deleted_messages", + listOf( + StringResource("revanced_deleted_messages_hide", "Do not show deleted messages"), + StringResource("revanced_deleted_messages_spoiler", "Hide deleted messages behind a spoiler"), + StringResource("revanced_deleted_messages_cross_out", "Show deleted messages as crossed-out text") + ) + ), + ArrayResource( + "revanced_deleted_messages_values", + listOf( + StringResource("key_revanced_deleted_messages_hide", "hide"), + StringResource("key_revanced_deleted_messages_spoiler", "spoiler"), + StringResource("key_revanced_deleted_messages_cross_out", "cross-out") + ) + ), + "cross-out" + ) + ) + + SettingsPatch.addString("revanced_deleted_msg", "message deleted") + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt new file mode 100644 index 0000000..394b9e9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/debug/annotations/DebugModeCompatibility.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.debug.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DebugModeCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt new file mode 100644 index 0000000..e7a3178 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsDebugConfigEnabledFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.debug.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object IsDebugConfigEnabledFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("BuildConfigUtil;") && methodDef.name == "isDebugConfigEnabled" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt new file mode 100644 index 0000000..cf8f1f7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/IsOmVerificationEnabledFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.debug.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object IsOmVerificationEnabledFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("BuildConfigUtil;") && methodDef.name == "isOmVerificationEnabled" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt new file mode 100644 index 0000000..12c5de0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/debug/fingerprints/ShouldShowDebugOptionsFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.debug.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object ShouldShowDebugOptionsFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("BuildConfigUtil;") && methodDef.name == "shouldShowDebugOptions" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt b/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt new file mode 100644 index 0000000..755efe6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/debug/patch/DebugModePatch.kt @@ -0,0 +1,76 @@ +package app.revanced.patches.twitch.debug.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.addInstructions +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.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.twitch.debug.annotations.DebugModeCompatibility +import app.revanced.patches.twitch.debug.fingerprints.IsDebugConfigEnabledFingerprint +import app.revanced.patches.twitch.debug.fingerprints.IsOmVerificationEnabledFingerprint +import app.revanced.patches.twitch.debug.fingerprints.ShouldShowDebugOptionsFingerprint +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.bytecode.patch.SettingsPatch + +@Patch(false) +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("debug-mode") +@Description("Enables Twitch's internal debugging mode.") +@DebugModeCompatibility +@Version("0.0.1") +class DebugModePatch : BytecodePatch( + listOf( + IsDebugConfigEnabledFingerprint, + IsOmVerificationEnabledFingerprint, + ShouldShowDebugOptionsFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + listOf( + IsDebugConfigEnabledFingerprint, + IsOmVerificationEnabledFingerprint, + ShouldShowDebugOptionsFingerprint + ).forEach { + with(it.result!!) { + with(mutableMethod) { + addInstructions( + 0, + """ + invoke-static {}, Lapp/revanced/twitch/patches/DebugModePatch;->isDebugModeEnabled()Z + move-result v0 + return v0 + """ + ) + } + } + } + + SettingsPatch.PreferenceScreen.MISC.OTHER.addPreferences( + SwitchPreference( + "revanced_debug_mode", + StringResource( + "revanced_debug_mode_enable", + "Enable debug mode" + ), + false, + StringResource( + "revanced_debug_mode_on", + "Debug mode is enabled (not recommended)" + ), + StringResource( + "revanced_debug_mode_off", + "Debug mode is disabled" + ), + ) + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt new file mode 100644 index 0000000..f0e219f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/annotations/IntegrationsCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.misc.integrations.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class IntegrationsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt new file mode 100644 index 0000000..7f55dd2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/fingerprints/InitFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.twitch.misc.integrations.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patches.twitch.misc.integrations.annotations.IntegrationsCompatibility +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint + +@Name("init-fingerprint") +@IntegrationsCompatibility +@Version("0.0.1") +object InitFingerprint : IntegrationsFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/TwitchApplication;") && + methodDef.name == "onCreate" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt new file mode 100644 index 0000000..027106d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/integrations/patch/IntegrationsPatch.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.twitch.misc.integrations.patch + +import app.revanced.patcher.annotation.Name +import app.revanced.patches.twitch.misc.integrations.fingerprints.InitFingerprint +import app.revanced.patches.twitch.misc.integrations.annotations.IntegrationsCompatibility +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch + +@Name("integrations") +@IntegrationsCompatibility +class IntegrationsPatch : AbstractIntegrationsPatch( + "Lapp/revanced/twitch/utils/ReVancedUtils;", + listOf(InitFingerprint) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt new file mode 100644 index 0000000..0eadf69 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/annotations/SettingsCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.misc.settings.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("tv.twitch.android.app")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SettingsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt new file mode 100644 index 0000000..d0f8b72 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/bytecode/patch/SettingsPatch.kt @@ -0,0 +1,196 @@ +package app.revanced.patches.twitch.misc.settings.bytecode.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.* +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +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.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.settings.preference.impl.PreferenceCategory +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.util.AbstractPreferenceScreen +import app.revanced.patches.twitch.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.twitch.misc.settings.annotations.SettingsCompatibility +import app.revanced.patches.twitch.misc.settings.components.CustomPreferenceCategory +import app.revanced.patches.twitch.misc.settings.fingerprints.* +import app.revanced.patches.twitch.misc.settings.resource.patch.SettingsResourcePatch +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.immutable.ImmutableField + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsResourcePatch::class]) +@Name("settings") +@Description("Adds settings menu to Twitch.") +@SettingsCompatibility +@Version("0.0.1") +class SettingsPatch : BytecodePatch( + listOf( + SettingsActivityOnCreateFingerprint, + SettingsMenuItemEnumFingerprint, + MenuGroupsUpdatedFingerprint, + MenuGroupsOnClickFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + // Hook onCreate to handle fragment creation + with(SettingsActivityOnCreateFingerprint.result!!) { + val insertIndex = mutableMethod.implementation!!.instructions.size - 2 + mutableMethod.addInstructions( + insertIndex, + """ + invoke-static {p0}, $SETTINGS_HOOKS_CLASS->handleSettingsCreation(Landroidx/appcompat/app/AppCompatActivity;)Z + move-result v0 + if-eqz v0, :no_rv_settings_init + return-void + """, + listOf(ExternalLabel("no_rv_settings_init", mutableMethod.instruction(insertIndex))) + ) + } + + // Create new menu item for settings menu + with(SettingsMenuItemEnumFingerprint.result!!) { + injectMenuItem( + REVANCED_SETTINGS_MENU_ITEM_NAME, + REVANCED_SETTINGS_MENU_ITEM_ID, + REVANCED_SETTINGS_MENU_ITEM_TITLE_RES, + REVANCED_SETTINGS_MENU_ITEM_ICON_RES + ) + } + + // Intercept settings menu creation and add new menu item + with(MenuGroupsUpdatedFingerprint.result!!) { + mutableMethod.addInstructions( + 0, + """ + sget-object v0, $MENU_ITEM_ENUM_CLASS->$REVANCED_SETTINGS_MENU_ITEM_NAME:$MENU_ITEM_ENUM_CLASS + invoke-static {p1, v0}, $SETTINGS_HOOKS_CLASS->handleSettingMenuCreation(Ljava/util/List;Ljava/lang/Object;)Ljava/util/List; + move-result-object p1 + """ + ) + } + + // Intercept onclick events for the settings menu + with(MenuGroupsOnClickFingerprint.result!!) { + val insertIndex = 0 + mutableMethod.addInstructions( + insertIndex, + """ + invoke-static {p1}, $SETTINGS_HOOKS_CLASS->handleSettingMenuOnClick(Ljava/lang/Enum;)Z + move-result p2 + if-eqz p2, :no_rv_settings_onclick + sget-object p1, $MENU_DISMISS_EVENT_CLASS->INSTANCE:$MENU_DISMISS_EVENT_CLASS + invoke-virtual {p0, p1}, Ltv/twitch/android/core/mvp/viewdelegate/RxViewDelegate;->pushEvent(Ltv/twitch/android/core/mvp/viewdelegate/ViewDelegateEvent;)V + return-void + """, + listOf(ExternalLabel("no_rv_settings_onclick", mutableMethod.instruction(insertIndex))) + ) + } + + addString("revanced_settings", "ReVanced Settings", false) + addString("revanced_reboot_message", "Twitch needs to restart to apply your changes. Restart now?", false) + addString("revanced_reboot", "Restart", false) + addString("revanced_cancel", "Cancel", false) + + return PatchResultSuccess() + } + + internal companion object { + fun addString(identifier: String, value: String, formatted: Boolean = true) = + SettingsResourcePatch.addString(identifier, value, formatted) + + fun addPreferenceScreen(preferenceScreen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) = + SettingsResourcePatch.addPreferenceScreen(preferenceScreen) + + /* Private members */ + private const val REVANCED_SETTINGS_MENU_ITEM_NAME = "RevancedSettings" + private const val REVANCED_SETTINGS_MENU_ITEM_ID = 0x7 + private const val REVANCED_SETTINGS_MENU_ITEM_TITLE_RES = "revanced_settings" + private const val REVANCED_SETTINGS_MENU_ITEM_ICON_RES = "ic_settings" + + private const val MENU_ITEM_ENUM_CLASS = "Ltv/twitch/android/feature/settings/menu/SettingsMenuItem;" + private const val MENU_DISMISS_EVENT_CLASS = "Ltv/twitch/android/feature/settings/menu/SettingsMenuViewDelegate\$Event\$OnDismissClicked;" + + private const val INTEGRATIONS_PACKAGE = "app/revanced/twitch" + private const val SETTINGS_HOOKS_CLASS = "L$INTEGRATIONS_PACKAGE/settingsmenu/SettingsHooks;" + private const val REVANCED_UTILS_CLASS = "L$INTEGRATIONS_PACKAGE/utils/ReVancedUtils;" + + private fun MethodFingerprintResult.injectMenuItem( + name: String, + value: Int, + titleResourceName: String, + iconResourceName: String + ) { + // Add new static enum member field + mutableClass.staticFields.add( + ImmutableField( + mutableMethod.definingClass, + name, + MENU_ITEM_ENUM_CLASS, + AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.ENUM or AccessFlags.STATIC, + null, + null, + null + ).toMutable() + ) + + // Add initializer for the new enum member + mutableMethod.addInstructions( + mutableMethod.implementation!!.instructions.size - 4, + """ + new-instance v0, $MENU_ITEM_ENUM_CLASS + const-string v1, "$titleResourceName" + invoke-static {v1}, $REVANCED_UTILS_CLASS->getStringId(Ljava/lang/String;)I + move-result v1 + const-string v3, "$iconResourceName" + invoke-static {v3}, $REVANCED_UTILS_CLASS->getDrawableId(Ljava/lang/String;)I + move-result v3 + const-string v4, "$name" + const/4 v5, $value + invoke-direct {v0, v4, v5, v1, v3}, $MENU_ITEM_ENUM_CLASS->(Ljava/lang/String;III)V + sput-object v0, $MENU_ITEM_ENUM_CLASS->$name:$MENU_ITEM_ENUM_CLASS + """ + ) + } + } + + /** + * Preference screens patches should add their settings to. + */ + internal object PreferenceScreen : AbstractPreferenceScreen() { + val ADS = CustomScreen("ads", "Ads", "Ad blocking settings") + val CHAT = CustomScreen("chat", "Chat", "Chat settings") + val MISC = CustomScreen("misc", "Misc", "Miscellaneous patches") + + internal class CustomScreen(key: String, title: String, summary: String) : Screen(key, title, summary) { + /* Categories */ + val GENERAL = CustomCategory("general", "General settings") + val OTHER = CustomCategory("other", "Other settings") + val CLIENT_SIDE = CustomCategory("client_ads", "Client-side ads") + val SURESTREAM = CustomCategory("surestream_ads", "Server-side surestream ads") + + internal inner class CustomCategory(key: String, title: String) : Screen.Category(key, title) { + /* For Twitch, we need to load our CustomPreferenceCategory class instead of the default one. */ + override fun transform(): PreferenceCategory { + return CustomPreferenceCategory( + key, + StringResource("${key}_title", title), + preferences.sortedBy { it.title.value } + ) + } + } + } + + override fun commit(screen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) { + addPreferenceScreen(screen) + } + } + + override fun close() = PreferenceScreen.close() +} diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt new file mode 100644 index 0000000..20c7bdb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/components/CustomPreferenceCategory.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.twitch.misc.settings.components + +import app.revanced.patches.shared.settings.preference.BasePreference +import app.revanced.patches.shared.settings.preference.impl.PreferenceCategory +import app.revanced.patches.shared.settings.preference.impl.StringResource + +/** + * Customized preference category for Twitch. + * + * @param key The key of the preference. + * @param title The title of the preference. + * @param preferences Child preferences of this category. + */ +internal open class CustomPreferenceCategory( + key: String, + title: StringResource, + preferences: List +) : PreferenceCategory(key, title, preferences) { + override val tag: String = "app.revanced.twitch.settingsmenu.preference.CustomPreferenceCategory" +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt new file mode 100644 index 0000000..1a63759 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsOnClickFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.twitch.misc.settings.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object MenuGroupsOnClickFingerprint : MethodFingerprint( + "V", + AccessFlags.PRIVATE or AccessFlags.STATIC or AccessFlags.FINAL, + listOf("L", "L", "L"), + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/SettingsMenuViewDelegate;") + && methodDef.name.contains("render") + } +) diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt new file mode 100644 index 0000000..913f31d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/MenuGroupsUpdatedFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object MenuGroupsUpdatedFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/SettingsMenuPresenter\$Event\$MenuGroupsUpdated;") + && methodDef.name == "" + } +) diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt new file mode 100644 index 0000000..f976f02 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsActivityOnCreateFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitch.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SettingsActivityOnCreateFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/SettingsActivity;") && + methodDef.name == "onCreate" + } +) diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt new file mode 100644 index 0000000..cdb2dd1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/fingerprints/SettingsMenuItemEnumFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitch.misc.settings.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SettingsMenuItemEnumFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("/SettingsMenuItem;") && methodDef.name == "" + } +) diff --git a/src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt new file mode 100644 index 0000000..07420ce --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitch/misc/settings/resource/patch/SettingsResourcePatch.kt @@ -0,0 +1,44 @@ +package app.revanced.patches.twitch.misc.settings.resource.patch + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patches.shared.settings.preference.impl.ArrayResource +import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.resource.patch.AbstractSettingsResourcePatch +import app.revanced.patches.twitch.misc.settings.annotations.SettingsCompatibility + +@Name("settings-resource-patch") +@SettingsCompatibility +@Version("0.0.1") +class SettingsResourcePatch : AbstractSettingsResourcePatch( +"revanced_prefs", +"twitch/settings" +) { + internal companion object { + /* Companion delegates */ + + /** + * Add a new string to the resources. + * + * @param identifier The key of the string. + * @param value The value of the string. + * @throws IllegalArgumentException if the string already exists. + */ + fun addString(identifier: String, value: String, formatted: Boolean) = + AbstractSettingsResourcePatch.addString(identifier, value, formatted) + + /** + * Add an array to the resources. + * + * @param arrayResource The array resource to add. + */ + fun addArray(arrayResource: ArrayResource) = AbstractSettingsResourcePatch.addArray(arrayResource) + + /** + * Add a preference to the settings. + * + * @param preferenceScreen The name of the preference screen. + */ + fun addPreferenceScreen(preferenceScreen: PreferenceScreen) = AbstractSettingsResourcePatch.addPreference(preferenceScreen) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt new file mode 100644 index 0000000..54ce876 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitter.ad.timeline.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.twitter.android")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class TimelineAdsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt new file mode 100644 index 0000000..5d4b987 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.twitter.ad.timeline.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object TimelineTweetJsonParserFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.IPUT_OBJECT, + Opcode.GOTO, + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IPUT_OBJECT, + Opcode.RETURN_VOID, + ), strings = listOf("tweetPromotedMetadata", "promotedMetadata", "hasModeratedReplies", "conversationAnnotation"), + customFingerprint = { methodDef -> methodDef.name == "parseField" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt new file mode 100644 index 0000000..753a2ea --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt @@ -0,0 +1,77 @@ +package app.revanced.patches.twitter.ad.timeline.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.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.twitter.ad.timeline.annotations.TimelineAdsCompatibility +import app.revanced.patches.twitter.ad.timeline.fingerprints.TimelineTweetJsonParserFingerprint +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.BuilderInstruction +import org.jf.dexlib2.builder.instruction.BuilderInstruction22c +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.StringReference + +@Patch +@Name("timeline-ads") +@Description("Removes ads from the Twitter timeline. Might require clearing app data to remove already cached ads.") +@TimelineAdsCompatibility +@Version("0.0.1") +class TimelineAdsPatch : BytecodePatch( + listOf(TimelineTweetJsonParserFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + if (removePromotedAds()) + return PatchResultError("The instruction for the tweet id field could not be found") + + return PatchResultSuccess() + } + + private fun removePromotedAds(): Boolean { + val (parserFingerprintResult, parserMethod, instructions) = TimelineTweetJsonParserFingerprint.unwrap() + + // Anchor index + val tweetIdFieldInstructionIndex = instructions.indexOfFirst { instruction -> + if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@indexOfFirst false + if (((instruction as? ReferenceInstruction)?.reference as StringReference).string != "tweetSocialProof") return@indexOfFirst false + + // Use the above conditions as an anchor to find the index for the instruction with the field we need + return@indexOfFirst true + } - 2 // This is where the instruction with the field is located + + // Reference to the tweetId field for of the timeline tweet + val tweetIdFieldReference = + (parserMethod.instruction(tweetIdFieldInstructionIndex) as? BuilderInstruction22c)?.reference as? FieldReference + ?: return true + + // Set the tweetId field to null + // This will cause twitter to not show the promoted ads, because we set it to null, when the tweet is promoted + parserFingerprintResult.mutableMethod.addInstructions( + parserFingerprintResult.scanResult.patternScanResult!!.startIndex + 1, + """ + const/4 v2, 0x0 + iput-object v2, p0, Lcom/twitter/model/json/timeline/urt/JsonTimelineTweet;->${tweetIdFieldReference.name}:Ljava/lang/String; + """ + ) + return false + } + + private fun MethodFingerprint.unwrap(): Triple> { + val parserFingerprintResult = this.result!! + val parserMethod = parserFingerprintResult.mutableMethod + val instructions = parserMethod.implementation!!.instructions + + return Triple(parserFingerprintResult, parserMethod, instructions) + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt new file mode 100644 index 0000000..135276a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/annotations/DynamicColorCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitter.misc.dynamiccolor.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.twitter.android")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DynamicColorCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt new file mode 100644 index 0000000..3ba3b56 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/patch/DynamicColorPatch.kt @@ -0,0 +1,87 @@ +package app.revanced.patches.twitter.misc.dynamiccolor.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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.twitter.misc.dynamiccolor.annotations.DynamicColorCompatibility +import java.io.FileWriter +import java.nio.file.Files + +@Patch +@Name("dynamic-color") +@Description("Replaces the default Twitter Blue with the users Material You palette.") +@DynamicColorCompatibility +@Version("0.0.1") +class DynamicColorPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + val resDirectory = context["res"] + if (!resDirectory.isDirectory) return PatchResultError("The res folder can not be found.") + + val valuesV31Directory = resDirectory.resolve("values-v31") + if (!valuesV31Directory.isDirectory) Files.createDirectories(valuesV31Directory.toPath()) + + val valuesNightV31Directory = resDirectory.resolve("values-night-v31") + if (!valuesNightV31Directory.isDirectory) Files.createDirectories(valuesNightV31Directory.toPath()) + + listOf(valuesV31Directory, valuesNightV31Directory).forEach { it -> + val colorsXml = it.resolve("colors.xml") + + if(!colorsXml.exists()) { + FileWriter(colorsXml).use { + it.write("") + } + } + } + + context.xmlEditor["res/values-v31/colors.xml"].use { editor -> + val document = editor.file + + mapOf( + "ps__twitter_blue" to "@color/twitter_blue", + "ps__twitter_blue_pressed" to "@color/twitter_blue_fill_pressed", + "twitter_blue" to "@android:color/system_accent1_400", + "twitter_blue_fill_pressed" to "@android:color/system_accent1_300", + "twitter_blue_opacity_30" to "@android:color/system_accent1_100", + "twitter_blue_opacity_50" to "@android:color/system_accent1_200", + "twitter_blue_opacity_58" to "@android:color/system_accent1_300", + "deep_transparent_twitter_blue" to "@android:color/system_accent1_200", + "ic_launcher_background" to "#1DA1F2" + ).forEach { (k, v) -> + val colorElement = document.createElement("color") + + colorElement.setAttribute("name", k) + colorElement.textContent = v + + document.getElementsByTagName("resources").item(0).appendChild(colorElement) + } + } + + context.xmlEditor["res/values-night-v31/colors.xml"].use { editor -> + val document = editor.file + + mapOf( + "twitter_blue" to "@android:color/system_accent1_200", + "twitter_blue_fill_pressed" to "@android:color/system_accent1_300", + "twitter_blue_opacity_30" to "@android:color/system_accent1_50", + "twitter_blue_opacity_50" to "@android:color/system_accent1_100", + "twitter_blue_opacity_58" to "@android:color/system_accent1_200", + "deep_transparent_twitter_blue" to "@android:color/system_accent1_200" + ).forEach { (k, v) -> + val colorElement = document.createElement("color") + + colorElement.setAttribute("name", k) + colorElement.textContent = v + + document.getElementsByTagName("resources").item(0).appendChild(colorElement) + } + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt new file mode 100644 index 0000000..646ad2b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/annotations/MonochromeIconCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.twitter.misc.monochrome.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.twitter.android")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MonochromeIconCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt new file mode 100644 index 0000000..6e83ca5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/misc/monochrome/patch/MonochromeIconPatch.kt @@ -0,0 +1,55 @@ +package app.revanced.patches.twitter.misc.monochrome.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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.twitter.misc.monochrome.annotations.MonochromeIconCompatibility +import java.io.FileWriter +import java.nio.file.Files + +@Patch +@Name("monochrome-icon") +@Description("Adds a monochrome icon.") +@MonochromeIconCompatibility +@Version("0.0.1") +class MonochromeIconPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + val resDirectory = context["res"] + if (!resDirectory.isDirectory) return PatchResultError("The res folder can not be found.") + + val mipmapV33Directory = resDirectory.resolve("mipmap-anydpi-v33") + if (!mipmapV33Directory.isDirectory) Files.createDirectories(mipmapV33Directory.toPath()) + + FileWriter(mipmapV33Directory.resolve("ic_launcher_twitter.xml")).use { + it.write( + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ) + } + + FileWriter(mipmapV33Directory.resolve("ic_launcher_twitter_round.xml")).use { + it.write( + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ) + } + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt new file mode 100644 index 0000000..a87fa53 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/annotations/FirebaseGetCertPatchCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.warnwetter.misc.firebasegetcert.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("de.dwd.warnapp")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FirebaseGetCertPatchCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt new file mode 100644 index 0000000..196f649 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertMessagingFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object GetMessagingCertFingerprint : MethodFingerprint( + "Ljava/lang/String;", + strings = listOf( + "ContentValues", + "Could not get fingerprint hash for package: ", + "No such package: " + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt new file mode 100644 index 0000000..8c1fb8c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/fingerprints/GetCertRegistrationFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object GetReqistrationCertFingerprint : MethodFingerprint( + "Ljava/lang/String;", + strings = listOf( + "FirebaseRemoteConfig", + "Could not get fingerprint hash for package: ", + "No such package: " + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt new file mode 100644 index 0000000..c7dd384 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/firebasegetcert/patch/FirebaseGetCertPatch.kt @@ -0,0 +1,46 @@ +package app.revanced.patches.warnwetter.misc.firebasegetcert.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patches.warnwetter.misc.firebasegetcert.annotations.FirebaseGetCertPatchCompatibility +import app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints.GetMessagingCertFingerprint +import app.revanced.patches.warnwetter.misc.firebasegetcert.fingerprints.GetReqistrationCertFingerprint + +@Name("spoof-cert-patch") +@Description("Spoofs the X-Android-Cert header.") +@FirebaseGetCertPatchCompatibility +@Version("0.0.1") +class FirebaseGetCertPatch : BytecodePatch( + listOf( + GetReqistrationCertFingerprint, + GetMessagingCertFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val spoofedInstruction = + """ + const-string v0, "0799DDF0414D3B3475E88743C91C0676793ED450" + return-object v0 + """ + + val registrationCertMethod = GetReqistrationCertFingerprint.result!!.mutableMethod + val messagingCertMethod = GetMessagingCertFingerprint.result!!.mutableMethod + + registrationCertMethod.addInstructions( + 0, + spoofedInstruction + ) + messagingCertMethod.addInstructions( + 0, + spoofedInstruction + ) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt new file mode 100644 index 0000000..f4c2cc1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/annotations/PromoCodeUnlockCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.warnwetter.misc.promocode.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("de.dwd.warnapp")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PromoCodeUnlockCompatibility diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt new file mode 100644 index 0000000..5b0dd14 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/fingerprints/PromoCodeUnlockFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.warnwetter.misc.promocode.fingerprints +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PromoCodeUnlockFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("PromoTokenVerification;") && methodDef.name == "isValid" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt new file mode 100644 index 0000000..68ee1de --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/warnwetter/misc/promocode/patch/PromoCodeUnlockPatch.kt @@ -0,0 +1,47 @@ +package app.revanced.patches.warnwetter.misc.promocode.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.addInstructions +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.warnwetter.misc.firebasegetcert.patch.FirebaseGetCertPatch +import app.revanced.patches.warnwetter.misc.promocode.annotations.PromoCodeUnlockCompatibility +import app.revanced.patches.warnwetter.misc.promocode.fingerprints.PromoCodeUnlockFingerprint + +@DependsOn( + [ + FirebaseGetCertPatch::class + ] +) +@Patch +@Name("promo-code-unlock") +@Description("Disables the validation of promo code. Any code will work to unlock all features.") +@PromoCodeUnlockCompatibility +@Version("0.0.1") +class PromoCodeUnlockPatch : BytecodePatch( + listOf( + PromoCodeUnlockFingerprint + ) +) { + + override fun execute(context: BytecodeContext): PatchResult { + val method = PromoCodeUnlockFingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/4 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt new file mode 100644 index 0000000..b4b99e4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/annotations/UnlockProCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.windyapp.misc.pro.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("co.windyapp.android")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class UnlockProCompatibility diff --git a/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt new file mode 100644 index 0000000..230f83a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/fingerprints/CheckProFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.windyapp.misc.pro.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CheckProFingerprint : MethodFingerprint( + "I", + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("RawUserData;") && methodDef.name == "isPro" + } +) diff --git a/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt new file mode 100644 index 0000000..fac915b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/windyapp/misc/unlockpro/patch/UnlockProPatch.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.windyapp.misc.pro.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.windyapp.misc.pro.annotations.UnlockProCompatibility +import app.revanced.patches.windyapp.misc.pro.fingerprints.CheckProFingerprint + +@Patch +@Name("unlock-pro") +@Description("Unlocks all pro features.") +@UnlockProCompatibility +@Version("0.0.1") +class UnlockProPatch : BytecodePatch( + listOf( + CheckProFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val method = CheckProFingerprint.result!!.mutableMethod + method.addInstructions( + 0, + """ + const/16 v0, 0x1 + return v0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt new file mode 100644 index 0000000..c5ec9b4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/annotation/GeneralAdsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.ad.general.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class GeneralAdsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt new file mode 100644 index 0000000..b6140dc --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/fingerprints/ReelConstructorFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.youtube.ad.general.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.ad.general.resource.patch.GeneralAdsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object ReelConstructorFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL + ), + customFingerprint = { method -> + method.implementation?.instructions?.any { + it.opcode == Opcode.CONST && (it as WideLiteralInstruction).wideLiteral == GeneralAdsResourcePatch.reelMultipleItemShelfId + } ?: false + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt new file mode 100644 index 0000000..219e02a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/bytecode/patch/GeneralAdsPatch.kt @@ -0,0 +1,91 @@ +package app.revanced.patches.youtube.ad.general.bytecode.patch + +import app.revanced.extensions.findMutableMethodOf +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.instruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.ad.general.annotation.GeneralAdsCompatibility +import app.revanced.patches.youtube.ad.general.bytecode.fingerprints.ReelConstructorFingerprint +import app.revanced.patches.youtube.ad.general.resource.patch.GeneralAdsResourcePatch +import app.revanced.patches.youtube.misc.fix.verticalscroll.patch.VerticalScrollPatch +import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction +import org.jf.dexlib2.iface.instruction.formats.Instruction31i +import org.jf.dexlib2.iface.instruction.formats.Instruction35c + + +@Patch +@DependsOn([GeneralAdsResourcePatch::class, VerticalScrollPatch::class]) +@Name("general-ads") +@Description("Removes general ads.") +@GeneralAdsCompatibility +@Version("0.0.1") +class GeneralAdsPatch : BytecodePatch( + listOf(ReelConstructorFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + fun String.buildHideCall(viewRegister: Int) = "invoke-static { v$viewRegister }, " + + "Lapp/revanced/integrations/patches/GeneralAdsPatch;" + + "->" + + "$this(Landroid/view/View;)V" + + fun MutableMethod.injectHideCall(insertIndex: Int, viewRegister: Int, method: String) = + this.addInstruction(insertIndex, method.buildHideCall(viewRegister)) + + context.classes.forEach { classDef -> + classDef.methods.forEach { method -> + with(method.implementation) { + this?.instructions?.forEachIndexed { index, instruction -> + if (instruction.opcode != org.jf.dexlib2.Opcode.CONST) + return@forEachIndexed + // Instruction to store the id adAttribution into a register + if ((instruction as Instruction31i).wideLiteral != GeneralAdsResourcePatch.adAttributionId) + return@forEachIndexed + + val insertIndex = index + 1 + + // Call to get the view with the id adAttribution + with(instructions.elementAt(insertIndex)) { + if (opcode != org.jf.dexlib2.Opcode.INVOKE_VIRTUAL) + return@forEachIndexed + + // Hide the view + val viewRegister = (this as Instruction35c).registerC + context.proxy(classDef) + .mutableClass + .findMutableMethodOf(method) + .injectHideCall(insertIndex, viewRegister, "hideAdAttributionView") + } + } + } + } + } + + with( + ReelConstructorFingerprint.result + ?: return PatchResultError("Could not resolve fingerprint") + ) { + // iput-object v$viewRegister, ... + val insertIndex = this.scanResult.patternScanResult!!.startIndex + 2 + + with(this.mutableMethod) { + val viewRegister = (instruction(insertIndex) as TwoRegisterInstruction).registerA + + injectHideCall(insertIndex, viewRegister, "hideReelView") + } + + } + + return PatchResultSuccess() + } + +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt new file mode 100644 index 0000000..af5afea --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/general/resource/patch/GeneralAdsResourcePatch.kt @@ -0,0 +1,232 @@ +package app.revanced.patches.youtube.ad.general.resource.patch + +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.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.shared.settings.preference.impl.InputType +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.shared.settings.preference.impl.TextPreference +import app.revanced.patches.youtube.ad.general.annotation.GeneralAdsCompatibility +import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch.PreferenceScreen + +@DependsOn(dependencies = [ + FixLocaleConfigErrorPatch::class, + LithoFilterPatch::class, + SettingsPatch::class, + ResourceMappingPatch::class +]) +@GeneralAdsCompatibility +@Version("0.0.1") +class GeneralAdsResourcePatch : ResourcePatch { + internal companion object { + var adAttributionId: Long = -1 + var reelMultipleItemShelfId: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + PreferenceScreen.ADS.addPreferences( + SwitchPreference( + "revanced_adremover_ad_removal", + StringResource("revanced_adremover_ad_removal_enabled_title", "Hide general ads"), + true, + StringResource("revanced_adremover_ad_removal_enabled_summary_on", "General ads are hidden"), + StringResource("revanced_adremover_ad_removal_enabled_summary_off", "General ads are shown") + ), + SwitchPreference( + "revanced_adremover_buttoned", + StringResource("revanced_adremover_buttoned_enabled_title", "Hide buttoned ad"), + true, + StringResource("revanced_adremover_buttoned_enabled_summary_on", "Buttoned ads are hidden"), + StringResource("revanced_adremover_buttoned_enabled_summary_off", "Buttoned ads are shown") + ), + SwitchPreference( + "revanced_adremover_merchandise", + StringResource("revanced_adremover_merchandise_enabled_title", "Hide merchandise banners"), + true, + StringResource("revanced_adremover_merchandise_enabled_summary_on", "Merchandise banners are hidden"), + StringResource("revanced_adremover_merchandise_enabled_summary_off", "Merchandise banners are shown") + ), + SwitchPreference( + "revanced_adremover_community_posts_removal", + StringResource("revanced_adremover_community_posts_enabled_title", "Hide community posts"), + false, + StringResource("revanced_adremover_community_posts_enabled_summary_on", "Community posts are hidden"), + StringResource("revanced_adremover_community_posts_enabled_summary_off", "Community posts are shown") + ), + SwitchPreference( + "revanced_adremover_compact_banner_removal", + StringResource("revanced_adremover_compact_banner_enabled_title", "Hide compact banners"), + true, + StringResource("revanced_adremover_compact_banner_enabled_summary_on", "Compact banners are hidden"), + StringResource("revanced_adremover_compact_banner_enabled_summary_off", "Compact banners are shown") + ), + SwitchPreference( + "revanced_adremover_movie", + StringResource("revanced_adremover_movie_enabled_title", "Hide movies section"), + true, + StringResource("revanced_adremover_movie_enabled_summary_on", "Movies section is hidden"), + StringResource("revanced_adremover_movie_enabled_summary_off", "Movies section is shown") + ), + SwitchPreference( + "revanced_adremover_feed_survey", + StringResource("revanced_adremover_feed_survey_enabled_title", "Hide feed surveys"), + true, + StringResource("revanced_adremover_feed_survey_enabled_summary_on", "Feed surveys are hidden"), + StringResource("revanced_adremover_feed_survey_enabled_summary_off", "Feed surveys are shown") + ), + SwitchPreference( + "revanced_adremover_shorts", + StringResource("revanced_adremover_shorts_enabled_title", "Hide shorts"), + true, + StringResource("revanced_adremover_shorts_enabled_summary_on", "Shorts are hidden"), + StringResource("revanced_adremover_shorts_enabled_summary_off", "Shorts are shown") + ), + SwitchPreference( + "revanced_adremover_community_guidelines", + StringResource("revanced_adremover_community_guidelines_enabled_title", "Hide community guidelines"), + true, + StringResource( + "revanced_adremover_community_guidelines_enabled_summary_on", + "Community guidelines are hidden" + ), + StringResource( + "revanced_adremover_community_guidelines_enabled_summary_off", + "Community guidelines are shown" + ) + ), + SwitchPreference( + "revanced_adremover_emergency_box_removal", + StringResource("revanced_adremover_emergency_box_enabled_title", "Hide emergency boxes"), + true, + StringResource("revanced_adremover_emergency_box_enabled_summary_on", "Emergency boxes are hidden"), + StringResource("revanced_adremover_emergency_box_enabled_summary_off", "Emergency boxes are shown") + ), + SwitchPreference( + "revanced_adremover_info_panel", + StringResource("revanced_adremover_info_panel_enabled_title", "Hide info panels"), + true, + StringResource("revanced_adremover_info_panel_enabled_summary_on", "Info panels are hidden"), + StringResource("revanced_adremover_info_panel_enabled_summary_off", "Info panels are shown") + ), + SwitchPreference( + "revanced_adremover_medical_panel", + StringResource("revanced_adremover_medical_panel_enabled_title", "Hide medical panels"), + true, + StringResource("revanced_adremover_medical_panel_enabled_summary_on", "Medical panels are hidden"), + StringResource("revanced_adremover_medical_panel_enabled_summary_off", "Medical panels are shown") + ), + SwitchPreference( + "revanced_adremover_paid_content", + StringResource("revanced_adremover_paid_content_enabled_title", "Hide paid content"), + true, + StringResource("revanced_adremover_paid_content_enabled_summary_on", "Paid content is hidden"), + StringResource("revanced_adremover_paid_content_enabled_summary_off", "Paid content is shown") + ), + SwitchPreference( + "revanced_adremover_hide_suggestions", + StringResource("revanced_adremover_hide_suggestions_enabled_title", "Hide suggestions"), + true, + StringResource("revanced_adremover_hide_suggestions_enabled_summary_on", "Suggestions are hidden"), + StringResource("revanced_adremover_hide_suggestions_enabled_summary_off", "Suggestions are shown") + ), + SwitchPreference( + "revanced_adremover_hide_latest_posts", + StringResource("revanced_adremover_hide_latest_posts_enabled_title", "Hide latest posts"), + true, + StringResource("revanced_adremover_hide_latest_posts_enabled_summary_on", "Latest posts are hidden"), + StringResource("revanced_adremover_hide_latest_posts_enabled_summary_off", "Latest posts are shown") + ), + SwitchPreference( + "revanced_adremover_hide_channel_guidelines", + StringResource("revanced_adremover_hide_channel_guidelines_enabled_title", "Hide channel guidelines"), + true, + StringResource( + "revanced_adremover_hide_channel_guidelines_enabled_summary_on", + "Channel guidelines are hidden" + ), + StringResource( + "revanced_adremover_hide_channel_guidelines_enabled_summary_off", + "Channel guidelines are shown" + ) + ), + SwitchPreference( + "revanced_adremover_self_sponsor", + StringResource("revanced_adremover_self_sponsor_enabled_title", "Hide self sponsored cards"), + true, + StringResource("revanced_adremover_self_sponsor_enabled_summary_on", "Self sponsored cards are hidden"), + StringResource("revanced_adremover_self_sponsor_enabled_summary_off", "Self sponsored cards are shown") + ), + SwitchPreference( + "revanced_adremover_separator", + StringResource("revanced_adremover_separator_title", "Hide gray separator"), + true, + StringResource("revanced_adremover_separator_summary_on", "Gray separators are hidden"), + StringResource("revanced_adremover_separator_summary_off", "Gray separators are shown") + ), + SwitchPreference( + "revanced_adremover_chapter_teaser", + StringResource( + "revanced_adremover_chapter_teaser_enabled_title", + "Hide chapter teaser under videos" + ), + true, + StringResource( + "revanced_adremover_chapter_teaser_enabled_summary_on", + "Chapter teasers are hidden" + ), + StringResource( + "revanced_adremover_chapter_teaser_enabled_summary_off", + "Chapter teasers are shown" + ) + ), + app.revanced.patches.shared.settings.preference.impl.PreferenceScreen( + "revanced_adremover_custom", + StringResource("revanced_adremover_custom_title", "Custom filter"), + listOf( + SwitchPreference( + "revanced_adremover_custom_enabled", + StringResource( + "revanced_adremover_custom_enabled_title", + "Enable custom filter" + ), + false, + StringResource( + "revanced_adremover_custom_enabled_summary_on", + "Custom filter is enabled" + ), + StringResource( + "revanced_adremover_custom_enabled_summary_off", + "Custom filter is disabled" + ) + ), + // TODO: This should be a ListPreference, which does not exist yet + TextPreference( + "revanced_adremover_custom_strings", + StringResource("revanced_adremover_custom_strings_title", "Custom filter"), + InputType.STRING, + "", + StringResource( + "revanced_adremover_custom_strings_summary", + "Filter components by their name separated by a comma" + ) + ) + ) + ) + ) + + fun String.getId() = ResourceMappingPatch.resourceMappings.single { it.name == this }.id + + adAttributionId = "ad_attribution".getId() + reelMultipleItemShelfId = "reel_multiple_items_shelf".getId() + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt new file mode 100644 index 0000000..daf8278 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/annotations/VideoAdsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.ad.video.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class VideoAdsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt new file mode 100644 index 0000000..b5a4c70 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/fingerprints/LoadVideoAdsFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.ad.video.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object LoadVideoAdsFingerprint : MethodFingerprint( + strings = listOf( + "TriggerBundle doesn't have the required metadata specified by the trigger ", + "Tried to enter slot with no assigned slotAdapter", + "Trying to enter a slot when a slot of same type and physical position is already active. Its status: ", + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt new file mode 100644 index 0000000..5e16901 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/ad/video/patch/VideoAdsPatch.kt @@ -0,0 +1,58 @@ +package app.revanced.patches.youtube.ad.video.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.addInstructions +import app.revanced.patcher.extensions.instruction +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.ad.video.annotations.VideoAdsCompatibility +import app.revanced.patches.youtube.ad.video.fingerprints.LoadVideoAdsFingerprint +import app.revanced.patches.youtube.misc.fix.playback.patch.FixPlaybackPatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class, FixPlaybackPatch::class]) +@Name("video-ads") +@Description("Removes ads in the video player.") +@VideoAdsCompatibility +@Version("0.0.1") +class VideoAdsPatch : BytecodePatch( + listOf( + LoadVideoAdsFingerprint, + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.ADS.addPreferences( + SwitchPreference( + "revanced_video_ads_removal", + StringResource("revanced_video_ads_removal_title", "Hide video ads"), + true, + StringResource("revanced_video_ads_removal_summary_on", "Video ads are hidden"), + StringResource("revanced_video_ads_removal_summary_off", "Video ads are shown") + ) + ) + + val loadVideoAdsFingerprintMethod = LoadVideoAdsFingerprint.result!!.mutableMethod + + loadVideoAdsFingerprintMethod.addInstructions( + 0, """ + invoke-static { }, Lapp/revanced/integrations/patches/VideoAdsPatch;->shouldShowAds()Z + move-result v0 + if-nez v0, :show_video_ads + return-void + """, listOf(ExternalLabel("show_video_ads", loadVideoAdsFingerprintMethod.instruction(0))) + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt new file mode 100644 index 0000000..3f1e339 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/annotation/DownloadsCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.interaction.downloads.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DownloadsCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt new file mode 100644 index 0000000..c5e6ba0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/bytecode/patch/DownloadsBytecodePatch.kt @@ -0,0 +1,52 @@ +package app.revanced.patches.youtube.interaction.downloads.bytecode.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.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.youtube.interaction.downloads.annotation.DownloadsCompatibility +import app.revanced.patches.youtube.interaction.downloads.resource.patch.DownloadsResourcePatch +import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch +import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch + +@Patch +@Name("downloads") +@DependsOn([DownloadsResourcePatch::class, PlayerControlsBytecodePatch::class, VideoIdPatch::class]) +@Description("Enables downloading music and videos from YouTube.") +@DownloadsCompatibility +@Version("0.0.1") +class DownloadsBytecodePatch : BytecodePatch() { + override fun execute(context: BytecodeContext): PatchResult { + val integrationsPackage = "app/revanced/integrations" + val classDescriptor = "L$integrationsPackage/videoplayer/DownloadButton;" + + /* + initialize the control + */ + + val initializeDownloadsDescriptor = "$classDescriptor->initializeDownloadButton(Ljava/lang/Object;)V" + PlayerControlsBytecodePatch.initializeControl(initializeDownloadsDescriptor) + + /* + add code to change the visibility of the control + */ + + val changeVisibilityDescriptor = "$classDescriptor->changeVisibility(Z)V" + PlayerControlsBytecodePatch.injectVisibilityCheckCall(changeVisibilityDescriptor) + + /* + add code to change to update the video id + */ + + val setVideoIdDescriptor = + "L$integrationsPackage/patches/downloads/DownloadsPatch;->setVideoId(Ljava/lang/String;)V" + VideoIdPatch.injectCall(setVideoIdDescriptor) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt new file mode 100644 index 0000000..0ea6834 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/resource/patch/DownloadsResourcePatch.kt @@ -0,0 +1,72 @@ +package app.revanced.patches.youtube.interaction.downloads.resource.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.shared.settings.preference.impl.* +import app.revanced.patches.youtube.interaction.downloads.annotation.DownloadsCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.playercontrols.resource.patch.BottomControlsResourcePatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +import app.revanced.util.resources.ResourceUtils.mergeStrings + +@Name("downloads-resource-patch") +@DependsOn([BottomControlsResourcePatch::class, FixLocaleConfigErrorPatch::class, SettingsPatch::class]) +@Description("Makes necessary changes to resources for the download button.") +@DownloadsCompatibility +@Version("0.0.1") +class DownloadsResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences( + PreferenceScreen( + "revanced_downloads", + StringResource("revanced_downloads_title", "Download settings"), + listOf( + SwitchPreference( + "revanced_downloads", + StringResource("revanced_downloads_enabled_title", "Show download button"), + true, + StringResource("revanced_downloads_enabled_summary_on", "Download button is shown"), + StringResource("revanced_downloads_enabled_summary_off", "Download button is not shown") + ), + TextPreference( + "revanced_downloads_package_name", + StringResource("revanced_downloads_package_name_title", "Downloader package name"), + InputType.STRING, + "org.schabi.newpipe" /* NewPipe */, + StringResource("revanced_downloads_package_name_summary", "Package name of the downloader app such as NewPipe\\'s or PowerTube\\'s") + ) + ), + StringResource("revanced_downloads_summary", "Settings related to downloads") + ) + ) + + + /* + * Copy strings + */ + + context.mergeStrings("downloads/host/values/strings.xml") + + /* + * Copy resources + */ + + context.copyResources("downloads", ResourceUtils.ResourceGroup("drawable", "revanced_yt_download_button.xml")) + + /* + * Add download button node + */ + + BottomControlsResourcePatch.addControls("downloads/host/layout/${BottomControlsResourcePatch.TARGET_RESOURCE_NAME}") + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt new file mode 100644 index 0000000..5f282ec --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/annotation/SeekbarTappingCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.interaction.seekbar.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SeekbarTappingCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt new file mode 100644 index 0000000..94d6b2c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingFingerprint.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.youtube.interaction.seekbar.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object SeekbarTappingFingerprint : MethodFingerprint( + "Z", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.IGET, + Opcode.IGET_OBJECT, + Opcode.IGET, + Opcode.DIV_INT_2ADDR, + Opcode.ADD_INT, + Opcode.SUB_INT_2ADDR, + Opcode.INT_TO_FLOAT, + Opcode.CMPG_FLOAT, + Opcode.IF_GTZ, + Opcode.INT_TO_FLOAT, + Opcode.CMPG_FLOAT, + Opcode.IF_GTZ, + Opcode.CONST_4, + Opcode.INVOKE_INTERFACE, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_VIRTUAL + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt new file mode 100644 index 0000000..20f83ef --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/fingerprints/SeekbarTappingParentFingerprint.kt @@ -0,0 +1,44 @@ +package app.revanced.patches.youtube.interaction.seekbar.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object SeekbarTappingParentFingerprint : MethodFingerprint( + "L", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST_4, + Opcode.NEW_ARRAY, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST_4, + Opcode.APUT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST_4, + Opcode.APUT_OBJECT, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.RETURN_OBJECT + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt new file mode 100644 index 0000000..fed7924 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/seekbar/patch/EnableSeekbarTappingPatch.kt @@ -0,0 +1,110 @@ +package app.revanced.patches.youtube.interaction.seekbar.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.interaction.seekbar.annotation.SeekbarTappingCompatibility +import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SeekbarTappingFingerprint +import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SeekbarTappingParentFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction21t +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.iface.instruction.formats.Instruction11n +import org.jf.dexlib2.iface.instruction.formats.Instruction35c + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("seekbar-tapping") +@Description("Enables tap-to-seek on the seekbar of the video player.") +@SeekbarTappingCompatibility +@Version("0.0.1") +class EnableSeekbarTappingPatch : BytecodePatch( + listOf( + SeekbarTappingParentFingerprint, SeekbarTappingFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences( + SwitchPreference( + "revanced_enable_tap_seeking", + StringResource("revanced_seekbar_tapping_enabled_title", "Enable seekbar tapping"), + true, + StringResource("revanced_seekbar_tapping_summary_on", "Seekbar tapping is enabled"), + StringResource("revanced_seekbar_tapping_summary_off", "Seekbar tapping is disabled") + ) + ) + + var result = SeekbarTappingParentFingerprint.result!! + + val tapSeekMethods = mutableMapOf() + + // find the methods which tap the seekbar + for (it in result.classDef.methods) { + if (it.implementation == null) continue + + val instructions = it.implementation!!.instructions + // here we make sure we actually find the method because it has more than 7 instructions + if (instructions.count() < 7) continue + + // we know that the 7th instruction has the opcode CONST_4 + val instruction = instructions.elementAt(6) + if (instruction.opcode != Opcode.CONST_4) continue + + // the literal for this instruction has to be either 1 or 2 + val literal = (instruction as Instruction11n).narrowLiteral + + // method founds + if (literal == 1) tapSeekMethods["P"] = it + if (literal == 2) tapSeekMethods["O"] = it + } + + // replace map because we don't need the upper one anymore + result = SeekbarTappingFingerprint.result!! + + val implementation = result.mutableMethod.implementation!! + + // if tap-seeking is enabled, do not invoke the two methods below + val pMethod = tapSeekMethods["P"]!! + val oMethod = tapSeekMethods["O"]!! + + val insertIndex = result.scanResult.patternScanResult!!.endIndex + 1 + + // get the required register + val instruction = implementation.instructions[insertIndex - 1] + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return PatchResultError("Could not find the correct register") + val register = (instruction as Instruction35c).registerC + + val elseLabel = implementation.newLabelForIndex(insertIndex) + // the instructions are written in reverse order. + result.mutableMethod.addInstructions( + insertIndex, """ + invoke-virtual { v$register, v2 }, ${oMethod.definingClass}->${oMethod.name}(I)V + invoke-virtual { v$register, v2 }, ${pMethod.definingClass}->${pMethod.name}(I)V + """ + ) + + // if tap-seeking is disabled, do not invoke the two methods above by jumping to the else label + implementation.addInstruction( + insertIndex, BuilderInstruction21t(Opcode.IF_EQZ, 0, elseLabel) + ) + result.mutableMethod.addInstructions( + insertIndex, """ + invoke-static { }, Lapp/revanced/integrations/patches/SeekbarTappingPatch;->isTapSeekingEnabled()Z + move-result v0 + """ + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt new file mode 100644 index 0000000..6691d5d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/annotation/SwipeControlsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.interaction.swipecontrols.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SwipeControlsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt new file mode 100644 index 0000000..25e3ada --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/fingerprints/SwipeControlsHostActivityFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.interaction.swipecontrols.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SwipeControlsHostActivityFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass == "Lapp/revanced/integrations/swipecontrols/SwipeControlsHostActivity;" && methodDef.name == "" + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt new file mode 100644 index 0000000..22f94b8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/bytecode/SwipeControlsBytecodePatch.kt @@ -0,0 +1,68 @@ +package app.revanced.patches.youtube.interaction.swipecontrols.patch.bytecode + +import app.revanced.extensions.transformMethods +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.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.patcher.util.TypeUtil.traverseClassHierarchy +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.shared.fingerprints.WatchWhileActivityFingerprint +import app.revanced.patches.youtube.interaction.swipecontrols.annotation.SwipeControlsCompatibility +import app.revanced.patches.youtube.interaction.swipecontrols.fingerprints.SwipeControlsHostActivityFingerprint +import app.revanced.patches.youtube.interaction.swipecontrols.patch.resource.SwipeControlsResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.playertype.patch.PlayerTypeHookPatch +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.immutable.ImmutableMethod + +@Patch +@Name("swipe-controls") +@Description("Adds volume and brightness swipe controls.") +@SwipeControlsCompatibility +@Version("0.0.3") +@DependsOn( + [ + IntegrationsPatch::class, + PlayerTypeHookPatch::class, + SwipeControlsResourcePatch::class + ] +) +class SwipeControlsBytecodePatch : BytecodePatch( + listOf( + WatchWhileActivityFingerprint, + SwipeControlsHostActivityFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val wrapperClass = SwipeControlsHostActivityFingerprint.result!!.mutableClass + val targetClass = WatchWhileActivityFingerprint.result!!.mutableClass + + // inject the wrapper class from integrations into the class hierarchy of WatchWhileActivity + wrapperClass.setSuperClass(targetClass.superclass) + targetClass.setSuperClass(wrapperClass.type) + + // ensure all classes and methods in the hierarchy are non-final, so we can override them in integrations + context.traverseClassHierarchy(targetClass) { + accessFlags = accessFlags and AccessFlags.FINAL.value.inv() + transformMethods { + ImmutableMethod( + definingClass, + name, + parameters, + returnType, + accessFlags and AccessFlags.FINAL.value.inv(), + annotations, + hiddenApiRestrictions, + implementation + ).toMutable() + } + } + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt new file mode 100644 index 0000000..01cfffd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/interaction/swipecontrols/patch/resource/SwipeControlsResourcePatch.kt @@ -0,0 +1,111 @@ +package app.revanced.patches.youtube.interaction.swipecontrols.patch.resource + +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.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.shared.settings.preference.impl.TextPreference +import app.revanced.patches.shared.settings.preference.impl.InputType +import app.revanced.patches.youtube.interaction.swipecontrols.annotation.SwipeControlsCompatibility +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources + +@Name("swipe-controls-resource-patch") +@DependsOn([SettingsPatch::class]) +@SwipeControlsCompatibility +@Version("0.0.1") +class SwipeControlsResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences( + PreferenceScreen( + "revanced_swipe_controls", StringResource("revanced_swipe_controls_title", "Swipe controls"), listOf( + SwitchPreference( + "revanced_enable_swipe_brightness", + StringResource("revanced_swipe_brightness_enabled_title", "Enable brightness gesture"), + true, + StringResource("revanced_swipe_brightness_summary_on", "Brightness swipe is enabled"), + StringResource("revanced_swipe_brightness_summary_off", "Brightness swipe is disabled") + ), + SwitchPreference( + "revanced_enable_swipe_volume", + StringResource("revanced_swipe_volume_enabled_title", "Enable volume gesture"), + true, + StringResource("revanced_swipe_volume_summary_on", "Volume swipe is enabled"), + StringResource("revanced_swipe_volume_summary_off", "Volume swipe is disabled") + ), + SwitchPreference( + "revanced_enable_press_to_swipe", + StringResource("revanced_press_to_swipe_enabled_title", "Enable press-to-swipe gesture"), + false, + StringResource("revanced_press_to_swipe_summary_on", "Press-to-swipe is enabled"), + StringResource("revanced_press_to_swipe_summary_off", "Press-to-swipe is disabled") + ), + SwitchPreference( + "revanced_enable_swipe_haptic_feedback", + StringResource("revanced_swipe_haptic_feedback_enabled_title", "Enable haptic feedback"), + true, + StringResource("revanced_swipe_haptic_feedback_summary_on", "Haptic feedback is enabled"), + StringResource("revanced_swipe_haptic_feedback_summary_off", "Haptic feedback is disabled") + ), + TextPreference( + "revanced_swipe_overlay_timeout", + StringResource("revanced_swipe_overlay_timeout_title", "Swipe overlay timeout"), + InputType.NUMBER, + "500", + StringResource( + "revanced_swipe_overlay_timeout_summary", + "The amount of milliseconds the overlay is visible" + ) + ), + TextPreference( + "revanced_swipe_overlay_text_size", + StringResource("revanced_swipe_overlay_text_size_title", "Swipe overlay text size"), + InputType.NUMBER, + "22", + StringResource("revanced_swipe_overlay_text_size_summary", "The text size for swipe overlay") + ), + TextPreference( + "revanced_swipe_overlay_background_alpha", + StringResource("revanced_swipe_overlay_background_alpha_title", "Swipe background visibility"), + InputType.NUMBER, + "127", + StringResource( + "revanced_swipe_overlay_background_alpha_summary", + "The visibility of swipe overlay background" + ) + ), + TextPreference( + "revanced_swipe_magnitude_threshold", + StringResource("revanced_swipe_magnitude_threshold_title", "Swipe magnitude threshold"), + InputType.NUMBER, + "30", + StringResource( + "revanced_swipe_magnitude_threshold_summary", + "The amount of threshold for swipe to occur" + ) + ) + ), + StringResource("revanced_swipe_controls_summary","Control volume and brightness") + ) + ) + + context.copyResources( + "swipecontrols", + ResourceUtils.ResourceGroup( + "drawable", + "ic_sc_brightness_auto.xml", + "ic_sc_brightness_manual.xml", + "ic_sc_volume_mute.xml", + "ic_sc_volume_normal.xml" + ) + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt new file mode 100644 index 0000000..fc1f4c9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/annotations/AutoCaptionsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.autocaptions.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class AutoCaptionsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt new file mode 100644 index 0000000..e65c2b7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/StartVideoInformerFingerprint.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.youtube.layout.autocaptions.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(3) +object StartVideoInformerFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L", "L", "L", "L"), listOf( + Opcode.INVOKE_STATIC, + Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_INTERFACE, + Opcode.IF_EQZ, + Opcode.CONST_STRING, + Opcode.INVOKE_INTERFACE, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.CONST_4, + Opcode.IF_EQ, + Opcode.GOTO, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt new file mode 100644 index 0000000..d42965f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleButtonControllerFingerprint.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.youtube.layout.autocaptions.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 SubtitleButtonControllerFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf( + Opcode.IGET_OBJECT, + Opcode.IF_NEZ, + Opcode.RETURN_VOID, + Opcode.IGET_BOOLEAN, + Opcode.CONST_4, + Opcode.IF_NEZ, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt new file mode 100644 index 0000000..a011e60 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/fingerprints/SubtitleTrackFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.layout.autocaptions.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 SubtitleTrackFingerprint : MethodFingerprint( + "Z", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf( + Opcode.CONST_STRING, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.RETURN, + ), + strings = listOf("DISABLE_CAPTIONS_OPTION") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt new file mode 100644 index 0000000..38c12c9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autocaptions/patch/AutoCaptionsPatch.kt @@ -0,0 +1,80 @@ +package app.revanced.patches.youtube.layout.autocaptions.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.addInstructions +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.youtube.layout.autocaptions.annotations.AutoCaptionsCompatibility +import app.revanced.patches.youtube.layout.autocaptions.fingerprints.StartVideoInformerFingerprint +import app.revanced.patches.youtube.layout.autocaptions.fingerprints.SubtitleButtonControllerFingerprint +import app.revanced.patches.youtube.layout.autocaptions.fingerprints.SubtitleTrackFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("disable-auto-captions") +@Description("Disable forced captions from being automatically enabled.") +@AutoCaptionsCompatibility +@Version("0.0.1") +class AutoCaptionsPatch : BytecodePatch( + listOf( + StartVideoInformerFingerprint, SubtitleButtonControllerFingerprint, SubtitleTrackFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_autocaptions_enabled", + StringResource("revanced_autocaptions_enabled_title", "Disable auto-captions"), + false, + StringResource("revanced_autocaptions_summary_on", "Auto-captions are disabled"), + StringResource("revanced_autocaptions_summary_off", "Auto-captions are enabled") + ) + ) + + val startVideoInformerMethod = StartVideoInformerFingerprint.result!!.mutableMethod + + startVideoInformerMethod.addInstructions( + 0, """ + const/4 v0, 0x0 + sput-boolean v0, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z + """ + ) + + val subtitleButtonControllerMethod = SubtitleButtonControllerFingerprint.result!!.mutableMethod + + subtitleButtonControllerMethod.addInstructions( + 0, """ + const/4 v0, 0x1 + sput-boolean v0, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z + """ + ) + + val subtitleTrackMethod = SubtitleTrackFingerprint.result!!.mutableMethod + + subtitleTrackMethod.addInstructions( + 0, """ + invoke-static {}, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->autoCaptionsEnabled()Z + move-result v0 + if-eqz v0, :auto_captions_enabled + sget-boolean v0, Lapp/revanced/integrations/patches/DisableAutoCaptionsPatch;->captionsButtonDisabled:Z + if-nez v0, :auto_captions_enabled + const/4 v0, 0x1 + return v0 + :auto_captions_enabled + nop + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt new file mode 100644 index 0000000..0a32de4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/annotations/AutoplayButtonCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.autoplaybutton.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class AutoplayButtonCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt new file mode 100644 index 0000000..469d564 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/AutoNavInformerFingerprint.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.youtube.layout.autoplaybutton.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 AutoNavInformerFingerprint : MethodFingerprint( + "Z", + AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + ), + customFingerprint = { it.definingClass.endsWith("WillAutonavInformer;") } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt new file mode 100644 index 0000000..fcc47c1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/fingerprints/LayoutConstructorFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.layout.autoplaybutton.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object LayoutConstructorFingerprint : MethodFingerprint( + strings = listOf("1.0x"), + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("YouTubeControlsOverlay;") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt new file mode 100644 index 0000000..0791144 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/autoplaybutton/patch/HideAutoplayButtonPatch.kt @@ -0,0 +1,92 @@ +package app.revanced.patches.youtube.layout.autoplaybutton.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.addInstructions +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.youtube.layout.autoplaybutton.annotations.AutoplayButtonCompatibility +import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.AutoNavInformerFingerprint +import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.LayoutConstructorFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction +import org.jf.dexlib2.iface.reference.MethodReference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class, ResourceMappingPatch::class]) +@Name("hide-autoplay-button") +@Description("Hides the autoplay button in the video player.") +@AutoplayButtonCompatibility +@Version("0.0.1") +class HideAutoplayButtonPatch : BytecodePatch( + listOf( + LayoutConstructorFingerprint, AutoNavInformerFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_autoplay_button", + StringResource("revanced_hide_autoplay_button_title", "Hide autoplay button"), + true, + StringResource("revanced_hide_autoplay_button_summary_on", "Autoplay button is hidden"), + StringResource("revanced_hide_autoplay_button_summary_off", "Autoplay button is shown") + ) + ) + + val autoNavInformerMethod = AutoNavInformerFingerprint.result!!.mutableMethod + + val layoutGenMethodResult = LayoutConstructorFingerprint.result!! + val layoutGenMethod = layoutGenMethodResult.mutableMethod + val layoutGenMethodInstructions = layoutGenMethod.implementation!!.instructions + + // resolve the offsets such as ... + val autoNavPreviewStubId = ResourceMappingPatch.resourceMappings.single { + it.name == "autonav_preview_stub" + }.id + // where to insert the branch instructions and ... + val insertIndex = layoutGenMethodInstructions.indexOfFirst { + (it as? WideLiteralInstruction)?.wideLiteral == autoNavPreviewStubId + } + // where to branch away + val branchIndex = layoutGenMethodInstructions.subList(insertIndex + 1, layoutGenMethodInstructions.size - 1).indexOfFirst { + ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "addOnLayoutChangeListener" + } + 2 + + val jumpInstruction = layoutGenMethodInstructions[insertIndex + branchIndex] as Instruction + layoutGenMethod.addInstructions( + insertIndex, """ + invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z + move-result v11 + if-eqz v11, :hidden + """, listOf(ExternalLabel("hidden", jumpInstruction)) + ) + + //force disable autoplay since it's hard to do without the button + autoNavInformerMethod.addInstructions( + 0, """ + invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z + move-result v0 + if-nez v0, :hidden + const/4 v0, 0x0 + return v0 + :hidden + nop + """ + ) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt new file mode 100644 index 0000000..96b1d50 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/annotations/PremiumHeadingCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.branding.header.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PremiumHeadingCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt new file mode 100644 index 0000000..83eca46 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/patch/PremiumHeadingPatch.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.youtube.layout.branding.header.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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.branding.header.annotations.PremiumHeadingCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import kotlin.io.path.exists + +@Patch +@DependsOn([FixLocaleConfigErrorPatch::class]) +@Name("premium-heading") +@Description("Shows premium branding on the home screen.") +@PremiumHeadingCompatibility +@Version("0.0.1") +class PremiumHeadingPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + val resDirectory = context["res"] + if (!resDirectory.isDirectory) return PatchResultError("The res folder can not be found.") + + val (original, replacement) = "yt_premium_wordmark_header" to "yt_wordmark_header" + val modes = arrayOf("light", "dark") + + arrayOf("xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi").forEach { size -> + val headingDirectory = resDirectory.resolve("drawable-$size") + modes.forEach { mode -> + val fromPath = headingDirectory.resolve("${original}_$mode.png").toPath() + val toPath = headingDirectory.resolve("${replacement}_$mode.png").toPath() + + if (!fromPath.exists()) + return PatchResultError("The file $fromPath does not exist in the resources. Therefore, this patch can not succeed.") + Files.copy( + fromPath, + toPath, + StandardCopyOption.REPLACE_EXISTING + ) + } + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt new file mode 100644 index 0000000..a0ffc71 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/annotations/CustomBrandingCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.branding.icon.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CustomBrandingCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt new file mode 100644 index 0000000..f373fd6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/patch/CustomBrandingPatch.kt @@ -0,0 +1,94 @@ +package app.revanced.patches.youtube.layout.branding.icon.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.* +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.branding.icon.annotations.CustomBrandingCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +import java.io.File +import java.nio.file.Files + +@Patch +@DependsOn([FixLocaleConfigErrorPatch::class]) +@Name("custom-branding") +@Description("Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).") +@CustomBrandingCompatibility +@Version("0.0.1") +class CustomBrandingPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + fun copyResources(resourceGroups: List) { + iconPath?.let { iconPathString -> + val iconPath = File(iconPathString) + val resourceDirectory = context["res"] + + resourceGroups.forEach { group -> + val fromDirectory = iconPath.resolve(group.resourceDirectoryName) + val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName) + + group.resources.forEach { iconFileName -> + Files.write( + toDirectory.resolve(iconFileName).toPath(), + fromDirectory.resolve(iconFileName).readBytes() + ) + } + } + } ?: resourceGroups.forEach { context.copyResources("branding", it) } + } + + val iconResourceFileNames = arrayOf( + "adaptiveproduct_youtube_background_color_108", + "adaptiveproduct_youtube_foreground_color_108", + "ic_launcher", + "ic_launcher_round" + ).map { "$it.png" }.toTypedArray() + + fun createGroup(directory: String) = ResourceUtils.ResourceGroup( + directory, *iconResourceFileNames + ) + + // change the app icon + arrayOf("xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi") + .map { "mipmap-$it" } + .map(::createGroup) + .let(::copyResources) + + // change the name of the app + val manifest = context["AndroidManifest.xml"] + manifest.writeText( + manifest.readText() + .replace( + "android:label=\"@string/application_name", + "android:label=\"$appName" + ) + ) + + return PatchResultSuccess() + } + + companion object : OptionsContainer() { + private var appName: String? by option( + PatchOption.StringOption( + key = "appName", + default = "YouTube ReVanced", + title = "Application Name", + description = "The name of the application it will show on your home screen.", + required = true + ) + ) + + private var iconPath: String? by option( + PatchOption.StringOption( + key = "iconPath", + default = null, + title = "App Icon Path", + description = "A path containing mipmap resource folders with icons." + ) + ) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt new file mode 100644 index 0000000..c2847ff --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/annotations/HideButtonsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.buttons.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideButtonsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt new file mode 100644 index 0000000..bd82e1d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/patch/HideButtonsPatch.kt @@ -0,0 +1,81 @@ +package app.revanced.patches.youtube.layout.buttons.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.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch +import app.revanced.patches.youtube.layout.buttons.annotations.HideButtonsCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([ResourceMappingPatch::class, LithoFilterPatch::class]) +@Name("hide-video-buttons") +@Description("Adds options to hide action buttons under a video.") +@HideButtonsCompatibility +@Version("0.0.1") +class HideButtonsPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + PreferenceScreen( + "revanced_hide_buttons", + StringResource("revanced_hide_buttons_title", "Hide action buttons"), + listOf( + SwitchPreference( + "revanced_hide_like_button", + StringResource("revanced_hide_like_button_title", "Hide like button"), + false, + StringResource("revanced_hide_like_button_summary_on", "Like button is hidden"), + StringResource("revanced_hide_like_button_summary_off", "Like button is shown") + ), + SwitchPreference( + "revanced_hide_dislike_button", + StringResource("revanced_hide_dislike_button_title", "Hide dislike button"), + false, + StringResource("revanced_hide_dislike_button_summary_on", "Dislike button is hidden"), + StringResource("revanced_hide_dislike_button_summary_off", "Dislike button is shown") + ), + SwitchPreference( + "revanced_hide_download_button", + StringResource("revanced_hide_download_button_title", "Hide download button"), + false, + StringResource("revanced_hide_download_button_summary_on", "Download button is hidden"), + StringResource("revanced_hide_download_button_summary_off", "Download button is shown") + ), + SwitchPreference( + "revanced_hide_playlist_button", + StringResource("revanced_hide_playlist_button_title", "Hide playlist button"), + false, + StringResource("revanced_hide_playlist_button_summary_on", "Playlist button is hidden"), + StringResource("revanced_hide_playlist_button_summary_off", "Playlist button is shown") + ), + SwitchPreference( + "revanced_hide_action_button", + StringResource("revanced_hide_action_button_title", "Hide create, clip and thanks buttons"), + true, + StringResource("revanced_hide_action_button_summary_on", "Buttons are hidden"), + StringResource("revanced_hide_action_button_summary_off", "Buttons are shown") + ), + SwitchPreference( + "revanced_hide_share_button", + StringResource("revanced_hide_share_button_title", "Hide share button"), + false, + StringResource("revanced_hide_share_button_summary_on", "Share button is hidden"), + StringResource("revanced_hide_share_button_summaryoff", "Share button is shown") + ), + ), + StringResource("revanced_hide_buttons_summary", "Hide or show buttons under videos") + ) + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt new file mode 100644 index 0000000..86adb91 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/annotations/CastPatchCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.castbutton.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CastButtonCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt new file mode 100644 index 0000000..f1925c0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/castbutton/patch/HideCastButtonPatch.kt @@ -0,0 +1,57 @@ +package app.revanced.patches.youtube.layout.castbutton.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.castbutton.annotations.CastButtonCompatibility +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("hide-cast-button") +@Description("Hides the cast button in the video player.") +@CastButtonCompatibility +@Version("0.0.1") +class HideCastButtonPatch : BytecodePatch() { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_cast_button", + StringResource("revanced_hide_cast_button_title", "Hide cast button"), + true, + StringResource("revanced_hide_cast_button_summary_on", "Cast button is hidden"), + StringResource("revanced_hide_cast_button_summary_off", "Cast button is shown") + ) + ) + + with( + context.findClass("MediaRouteButton") + ?: return PatchResultError("MediaRouteButton class not found.") + ) { + with( + mutableClass.methods.find { it.name == "setVisibility" } + ?: return PatchResultError("setVisibility method not found.") + ) { + addInstructions( + 0, """ + invoke-static {p1}, Lapp/revanced/integrations/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I + move-result p1 + """ + ) + } + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt new file mode 100644 index 0000000..fb13661 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/annotations/CommentsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.comments.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CommentsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt new file mode 100644 index 0000000..878f112 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/fingerprints/ShortsCommentsButtonFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.youtube.layout.comments.bytecode.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.comments.resource.patch.CommentsResourcePatch +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object ShortsCommentsButtonFingerprint : MethodFingerprint( + "V", AccessFlags.PRIVATE or AccessFlags.FINAL, listOf("Z", "Z", "L"), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { + it.opcode.ordinal == Opcode.CONST.ordinal && (it as WideLiteralInstruction).wideLiteral == CommentsResourcePatch.shortsCommentsButtonId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt new file mode 100644 index 0000000..ddbcc78 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/bytecode/patch/CommentsPatch.kt @@ -0,0 +1,62 @@ +package app.revanced.patches.youtube.layout.comments.bytecode.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.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.youtube.layout.comments.annotations.CommentsCompatibility +import app.revanced.patches.youtube.layout.comments.bytecode.fingerprints.ShortsCommentsButtonFingerprint +import app.revanced.patches.youtube.layout.comments.resource.patch.CommentsResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, CommentsResourcePatch::class]) +@Name("comments") +@Description("Hides components related to comments.") +@CommentsCompatibility +@Version("0.0.1") +class CommentsPatch : BytecodePatch( + listOf( + ShortsCommentsButtonFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val shortsCommentsButtonResult = ShortsCommentsButtonFingerprint.result!! + val shortsCommentsButtonMethod = shortsCommentsButtonResult.mutableMethod + + val checkCastAnchorFingerprint = object : MethodFingerprint( + opcodes = listOf( + Opcode.CONST, + Opcode.CONST_HIGH16, + Opcode.IF_EQZ, + Opcode.CONST, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + ) + ) {} + + val checkCastAnchorIndex = checkCastAnchorFingerprint.also { + it.resolve(context, shortsCommentsButtonMethod, shortsCommentsButtonResult.classDef) + }.result!!.scanResult.patternScanResult!!.endIndex + + shortsCommentsButtonMethod.addInstructions( + checkCastAnchorIndex + 1, """ + invoke-static {v${(shortsCommentsButtonMethod.instruction(checkCastAnchorIndex) as OneRegisterInstruction).registerA}}, Lapp/revanced/integrations/patches/HideShortsCommentsButtonPatch;->hideShortsCommentsButton(Landroid/view/View;)V + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt new file mode 100644 index 0000000..052b130 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/comments/resource/patch/CommentsResourcePatch.kt @@ -0,0 +1,64 @@ +package app.revanced.patches.youtube.layout.comments.resource.patch + +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.layout.comments.annotations.CommentsCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Name("comments-resource-patch") +@CommentsCompatibility +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class CommentsResourcePatch : ResourcePatch { + companion object { + internal var shortsCommentsButtonId: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + PreferenceScreen( + "revanced_comments", + StringResource("revanced_comments_title", "Comments"), + listOf( + SwitchPreference( + "revanced_hide_comments_section", + StringResource("revanced_hide_comments_section_title", "Hide comments section"), + false, + StringResource("revanced_hide_comments_section_summary_on", "Comment section is hidden"), + StringResource("revanced_hide_comments_section_summary_off", "Comment section is shown") + ), + SwitchPreference( + "revanced_hide_preview_comment", + StringResource("revanced_hide_preview_comment_title", "Hide preview comment"), + false, + StringResource("revanced_hide_preview_comment_on", "Preview comment is hidden"), + StringResource("revanced_hide_preview_comment_off", "Preview comment is shown") + ), + SwitchPreference( + "revanced_hide_shorts_comments_button", + StringResource("revanced_hide_shorts_comments_button_title", "Hide shorts comments button"), + false, + StringResource("revanced_hide_shorts_comments_button_on", "Shorts comments button is hidden"), + StringResource("revanced_hide_shorts_comments_button_off", "Shorts comments button is shown") + ), + ), + StringResource("revanced_comments_summary", "Manage the visibility of comments section components") + ) + ) + + shortsCommentsButtonId = ResourceMappingPatch.resourceMappings.single { + it.type == "drawable" && it.name == "ic_right_comment_32c" + }.id + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt new file mode 100644 index 0000000..cd799d1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/annotations/FullscreenPanelsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.fullscreenpanels.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FullscreenPanelsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt new file mode 100644 index 0000000..ef0769d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object FullscreenViewAdderFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.IGET_BOOLEAN + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt new file mode 100644 index 0000000..ad59ea9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/fingerprints/FullscreenViewAdderParentFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object FullscreenViewAdderParentFingerprint : MethodFingerprint( + parameters = listOf("L", "L"), + opcodes = listOf( + Opcode.GOTO, + Opcode.IGET_BOOLEAN, + Opcode.IF_EQ, + Opcode.GOTO, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + ), + customFingerprint = { it.definingClass.endsWith("FullscreenEngagementPanelOverlay;") } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt new file mode 100644 index 0000000..e61de0f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/fullscreenpanels/patch/FullscreenPanelsRemoverPatch.kt @@ -0,0 +1,65 @@ +package app.revanced.patches.youtube.layout.fullscreenpanels.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.fullscreenpanels.annotations.FullscreenPanelsCompatibility +import app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints.FullscreenViewAdderFingerprint +import app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints.FullscreenViewAdderParentFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@Name("disable-fullscreen-panels") +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Description("Disables video description and comments panel in fullscreen view.") +@FullscreenPanelsCompatibility +@Version("0.0.1") +class FullscreenPanelsRemoverPatch : BytecodePatch( + listOf( + FullscreenViewAdderParentFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_fullscreen_panels", + StringResource("revanced_hide_fullscreen_panels_title", "Hide fullscreen panels"), + true, + StringResource("revanced_hide_fullscreen_panels_summary_on", "Fullscreen panels are hidden"), + StringResource("revanced_hide_fullscreen_panels_summary_off", "Fullscreen panels are shown") + ) + ) + + val parentResult = FullscreenViewAdderParentFingerprint.result!! + FullscreenViewAdderFingerprint.resolve(context, parentResult.method, parentResult.classDef) + val result = FullscreenViewAdderParentFingerprint.result + ?: return PatchResultError("Fingerprint not resolved!") + + val method = result.mutableMethod + + val ifIndex = result.scanResult.patternScanResult!!.startIndex + 2 + + method.removeInstruction(ifIndex) + method.addInstructions( + ifIndex, """ + invoke-static {}, Lapp/revanced/integrations/patches/FullscreenPanelsRemoverPatch;->getFullscreenPanelsVisibility()I + move-result p1 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt new file mode 100644 index 0000000..d82f26e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/annotations/AlbumCardsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.hidealbumcards.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class AlbumCardsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt new file mode 100644 index 0000000..505345f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/fingerprints/AlbumCardsFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.layout.hidealbumcards.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.hidealbumcards.resource.patch.AlbumCardsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object AlbumCardsFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == AlbumCardsResourcePatch.albumCardId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt new file mode 100644 index 0000000..9d7593d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/bytecode/patch/AlbumCardsPatch.kt @@ -0,0 +1,45 @@ +package app.revanced.patches.youtube.layout.hidealbumcards.bytecode.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.instruction +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.youtube.layout.hidealbumcards.annotations.AlbumCardsCompatibility +import app.revanced.patches.youtube.layout.hidealbumcards.bytecode.fingerprints.AlbumCardsFingerprint +import app.revanced.patches.youtube.layout.hidealbumcards.resource.patch.AlbumCardsResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, AlbumCardsResourcePatch::class]) +@Name("hide-album-cards") +@Description("Hides the album cards below the artist description.") +@AlbumCardsCompatibility +@Version("0.0.1") +class AlbumCardsPatch : BytecodePatch( + listOf( + AlbumCardsFingerprint, + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val albumCardsResult = AlbumCardsFingerprint.result!! + val albumCardsMethod = albumCardsResult.mutableMethod + + val checkCastAnchorIndex = albumCardsResult.scanResult.patternScanResult!!.endIndex + + albumCardsMethod.addInstruction( + checkCastAnchorIndex + 1, """ + invoke-static {v${(albumCardsMethod.instruction(checkCastAnchorIndex) as OneRegisterInstruction).registerA}}, Lapp/revanced/integrations/patches/HideAlbumCardsPatch;->hideAlbumCards(Landroid/view/View;)V + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt new file mode 100644 index 0000000..faf7575 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidealbumcards/resource/patch/AlbumCardsResourcePatch.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.youtube.layout.hidealbumcards.resource.patch + +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.layout.hidealbumcards.annotations.AlbumCardsCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Name("hide-album-cards-resource-patch") +@AlbumCardsCompatibility +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class AlbumCardsResourcePatch : ResourcePatch { + companion object { + internal var albumCardId: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_album_cards", + StringResource("revanced_hide_album_cards_title", "Hide album cards"), + false, + StringResource("revanced_hide_album_cards_summary_on", "Music album cards are hidden"), + StringResource("revanced_hide_album_cards_summary_off", "Music album cards are shown") + ) + ) + + albumCardId = ResourceMappingPatch.resourceMappings.single { + it.type == "layout" && it.name == "album_card" + }.id + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt new file mode 100644 index 0000000..6bbe049 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/annotations/HideArtistCardCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.buttons.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideArtistCardCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt new file mode 100644 index 0000000..9b7ff77 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideartistcard/patch/HideArtistCardPatch.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.youtube.layout.hideartistcard.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.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch +import app.revanced.patches.youtube.layout.buttons.annotations.HideArtistCardCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([ResourceMappingPatch::class, LithoFilterPatch::class]) +@Name("hide-artist-card") +@Description("Hides the artist card below the searchbar.") +@HideArtistCardCompatibility +@Version("0.0.1") +class HideArtistCardPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_artist_card", + StringResource("revanced_hide_hide_artist_card_title", "Hide artist card"), + false, + StringResource("revanced_hide_hide_artist_card_on", "Artist card is hidden"), + StringResource("revanced_hide_hide_artist_card_off", "Artist card is shown") + ), + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt new file mode 100644 index 0000000..a250036 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecaptionsbutton/patch/HideCaptionsButtonPatch.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.youtube.layout.hidecaptionsbutton.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.addInstructions +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.youtube.layout.autocaptions.annotations.AutoCaptionsCompatibility +import app.revanced.patches.youtube.layout.autocaptions.fingerprints.SubtitleButtonControllerFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.Opcode + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("hide-captions-button") +@Description("Hides the captions button on video player.") +@AutoCaptionsCompatibility +@Version("0.0.1") +class HideCaptionsButtonPatch : BytecodePatch(listOf( + SubtitleButtonControllerFingerprint, +)) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_captions_button", + StringResource("revanced_hide_captions_button_title", "Hide captions button"), + false, + StringResource("revanced_hide_captions_button_summary_on", "Captions button is hidden"), + StringResource("revanced_hide_captions_button_summary_off", "Captions button is shown") + ) + ) + + val subtitleButtonControllerMethod = SubtitleButtonControllerFingerprint.result!!.mutableMethod + + // Due to previously applied patches, scanResult index cannot be used in this context + val igetBooleanIndex = subtitleButtonControllerMethod.implementation!!.instructions.indexOfFirst { + it.opcode == Opcode.IGET_BOOLEAN + } + + subtitleButtonControllerMethod.addInstructions( + igetBooleanIndex + 1, """ + invoke-static {v0}, Lapp/revanced/integrations/patches/HideCaptionsButtonPatch;->hideCaptionsButton(Landroid/widget/ImageView;)V + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt new file mode 100644 index 0000000..455ef57 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/annotations/CrowdfundingBoxCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.hidecrowdfundingbox.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CrowdfundingBoxCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt new file mode 100644 index 0000000..ff76e7b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/fingerprints/CrowdfundingBoxFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.layout.hidecrowdfundingbox.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.hidecrowdfundingbox.resource.patch.CrowdfundingBoxResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object CrowdfundingBoxFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.CONST_4, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.CONST, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == CrowdfundingBoxResourcePatch.crowdfundingBoxId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt new file mode 100644 index 0000000..0058318 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/bytecode/patch/CrowdfundingBoxPatch.kt @@ -0,0 +1,46 @@ +package app.revanced.patches.youtube.layout.hidecrowdfundingbox.bytecode.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.instruction +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.youtube.layout.hidecrowdfundingbox.resource.patch.CrowdfundingBoxResourcePatch +import app.revanced.patches.youtube.layout.hidecrowdfundingbox.annotations.CrowdfundingBoxCompatibility +import app.revanced.patches.youtube.layout.hidecrowdfundingbox.bytecode.fingerprints.CrowdfundingBoxFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, CrowdfundingBoxResourcePatch::class]) +@Name("hide-crowdfunding-box") +@Description("Hides the crowdfunding box between the player and video description.") +@CrowdfundingBoxCompatibility +@Version("0.0.1") +class CrowdfundingBoxPatch : BytecodePatch( + listOf( + CrowdfundingBoxFingerprint, + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val crowdfundingBoxResult = CrowdfundingBoxFingerprint.result!! + val crowdfundingBoxMethod = crowdfundingBoxResult.mutableMethod + + val moveResultObjectIndex = + crowdfundingBoxResult.scanResult.patternScanResult!!.endIndex - 2 + + crowdfundingBoxMethod.addInstruction( + moveResultObjectIndex + 1, """ + invoke-static {v${(crowdfundingBoxMethod.instruction(moveResultObjectIndex) as OneRegisterInstruction).registerA}}, Lapp/revanced/integrations/patches/HideCrowdfundingBoxPatch;->hideCrowdfundingBox(Landroid/view/View;)V + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt new file mode 100644 index 0000000..2529b26 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidecrowdfundingbox/resource/patch/CrowdfundingBoxResourcePatch.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.youtube.layout.hidecrowdfundingbox.resource.patch + +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.layout.hidecrowdfundingbox.annotations.CrowdfundingBoxCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Name("crowdfunding-box-resource-patch") +@CrowdfundingBoxCompatibility +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class CrowdfundingBoxResourcePatch : ResourcePatch { + companion object { + internal var crowdfundingBoxId: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_crowdfunding_box", + StringResource("revanced_hide_crowdfunding_box_title", "Hide crowdfunding box"), + false, + StringResource("revanced_hide_crowdfunding_box_summary_on", "Crowdfunding box is hidden"), + StringResource("revanced_hide_crowdfunding_box_summary_off", "Crowdfunding box is shown") + ) + ) + + crowdfundingBoxId = ResourceMappingPatch.resourceMappings.single { + it.type == "layout" && it.name == "donation_companion" + }.id + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt new file mode 100644 index 0000000..03101c7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/annotations/HideEndScreenCardsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.hideendscreencards.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideEndscreenCardsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt new file mode 100644 index 0000000..5f75ca7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutCircleFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object LayoutCircleFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.CONST, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == HideEndscreenCardsResourcePatch.layoutCircle + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt new file mode 100644 index 0000000..8e5730b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutIconFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object LayoutIconFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.CONST_4, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == HideEndscreenCardsResourcePatch.layoutIcon + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt new file mode 100644 index 0000000..4d91d29 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/fingerprints/LayoutVideoFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object LayoutVideoFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.CONST, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == HideEndscreenCardsResourcePatch.layoutVideo + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt new file mode 100644 index 0000000..8bef7a9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/bytecode/patch/HideEndscreenCardsPatch.kt @@ -0,0 +1,57 @@ +package app.revanced.patches.youtube.layout.hideendscreencards.bytecode.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.instruction +import app.revanced.patcher.fingerprint.Fingerprint +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.layout.hideendscreencards.annotations.HideEndscreenCardsCompatibility +import app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints.LayoutCircleFingerprint +import app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints.LayoutIconFingerprint +import app.revanced.patches.youtube.layout.hideendscreencards.bytecode.fingerprints.LayoutVideoFingerprint +import app.revanced.patches.youtube.layout.hideendscreencards.resource.patch.HideEndscreenCardsResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.iface.instruction.formats.Instruction21c + +@Patch +@DependsOn([IntegrationsPatch::class, HideEndscreenCardsResourcePatch::class]) +@Name("hide-endscreen-cards") +@Description("Hides the suggested video cards at the end of a video in fullscreen.") +@HideEndscreenCardsCompatibility +@Version("0.0.1") +class HideEndscreenCardsPatch : BytecodePatch( + listOf( + LayoutCircleFingerprint, + LayoutIconFingerprint, + LayoutVideoFingerprint, + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + fun MethodFingerprint.injectHideCall() { + val layoutResult = result!! + val layoutMethod = layoutResult.mutableMethod + + val checkCastIndex = layoutResult.scanResult.patternScanResult!!.endIndex + val viewRegister = (layoutMethod.instruction(checkCastIndex) as Instruction21c).registerA + + layoutMethod.addInstruction( + checkCastIndex + 1, + "invoke-static { v$viewRegister }, Lapp/revanced/integrations/patches/HideEndscreenCardsPatch;->hideEndscreen(Landroid/view/View;)V" + ) + } + + listOf(LayoutCircleFingerprint, LayoutIconFingerprint, LayoutVideoFingerprint).forEach(MethodFingerprint::injectHideCall) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt new file mode 100644 index 0000000..949e584 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideendscreencards/resource/patch/HideEndscreenCardsResourcePatch.kt @@ -0,0 +1,48 @@ +package app.revanced.patches.youtube.layout.hideendscreencards.resource.patch + +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.layout.hideendscreencards.annotations.HideEndscreenCardsCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Name("hide-endscreen-cards-resource-patch") +@HideEndscreenCardsCompatibility +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class HideEndscreenCardsResourcePatch : ResourcePatch { + internal companion object { + var layoutCircle: Long = -1 + var layoutIcon: Long = -1 + var layoutVideo: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_endscreen_cards", + StringResource("revanced_hide_endscreen_cards_title", "Hide end-screen cards"), + true, + StringResource("revanced_hide_endscreen_cards_summary_on", "End-screen cards are hidden"), + StringResource("revanced_hide_endscreen_cards_summary_off", "End-screen cards are shown") + ), + ) + + fun findEndscreenResourceId(name: String) = ResourceMappingPatch.resourceMappings.single { + it.type == "layout" && it.name == "endscreen_element_layout_$name" + }.id + + layoutCircle = findEndscreenResourceId("circle") + layoutIcon = findEndscreenResourceId("icon") + layoutVideo = findEndscreenResourceId("video") + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt new file mode 100644 index 0000000..03b2030 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/annotations/HideInfocardsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.hideinfocards.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideInfocardsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt new file mode 100644 index 0000000..03df9af --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.hideinfocards.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object InfocardsIncognitoFingerprint : MethodFingerprint( + "Ljava/lang/Boolean;", + AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf("vibrator") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt new file mode 100644 index 0000000..53a6be4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsIncognitoParentFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.hideinfocards.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object InfocardsIncognitoParentFingerprint : MethodFingerprint( + "Ljava/lang/String;", + AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf("player_overlay_info_card_teaser"), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt new file mode 100644 index 0000000..222a18e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/fingerprints/InfocardsMethodCallFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.youtube.layout.hideinfocards.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.hideinfocards.resource.patch.HideInfocardsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object InfocardsMethodCallFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + (instruction as? WideLiteralInstruction)?.wideLiteral == HideInfocardsResourcePatch.drawerResourceId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt new file mode 100644 index 0000000..9d5f040 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/patch/HideInfocardsPatch.kt @@ -0,0 +1,77 @@ +package app.revanced.patches.youtube.layout.hideinfocards.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.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.extensions.replaceInstruction +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.youtube.layout.hideinfocards.annotations.HideInfocardsCompatibility +import app.revanced.patches.youtube.layout.hideinfocards.fingerprints.InfocardsIncognitoFingerprint +import app.revanced.patches.youtube.layout.hideinfocards.fingerprints.InfocardsIncognitoParentFingerprint +import app.revanced.patches.youtube.layout.hideinfocards.fingerprints.InfocardsMethodCallFingerprint +import app.revanced.patches.youtube.layout.hideinfocards.resource.patch.HideInfocardsResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction35c + +@Patch +@DependsOn([IntegrationsPatch::class, HideInfocardsResourcePatch::class]) +@Name("hide-info-cards") +@Description("Hides info-cards in videos.") +@HideInfocardsCompatibility +@Version("0.0.1") +class HideInfocardsPatch : BytecodePatch( + listOf( + InfocardsIncognitoParentFingerprint, + InfocardsMethodCallFingerprint, + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + with(InfocardsIncognitoFingerprint.also { + it.resolve(context, InfocardsIncognitoParentFingerprint.result!!.classDef) + }.result!!.mutableMethod) { + val invokeInstructionIndex = implementation!!.instructions.indexOfFirst { + it.opcode.ordinal == Opcode.INVOKE_VIRTUAL.ordinal && + ((it as? BuilderInstruction35c)?.reference.toString() == + "Landroid/view/View;->setVisibility(I)V") + } + + replaceInstruction( + invokeInstructionIndex, + "invoke-static {v${(instruction(invokeInstructionIndex) as? BuilderInstruction35c)?.registerC}}," + + " Lapp/revanced/integrations/patches/HideInfocardsPatch;->hideInfocardsIncognito(Landroid/view/View;)V" + ) + } + + with(InfocardsMethodCallFingerprint.result!!) { + val hideInfocardsCallMethod = mutableMethod + + val invokeInterfaceIndex = scanResult.patternScanResult!!.endIndex + val toggleRegister = hideInfocardsCallMethod.implementation!!.registerCount - 1 + + hideInfocardsCallMethod.addInstructions( + invokeInterfaceIndex, """ + invoke-static {}, Lapp/revanced/integrations/patches/HideInfocardsPatch;->hideInfocardsMethodCall()Z + move-result v$toggleRegister + if-nez v$toggleRegister, :hide_info_cards + """, + listOf( + ExternalLabel( + "hide_info_cards", hideInfocardsCallMethod.instruction(invokeInterfaceIndex + 1) + ) + ) + ) + } + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt new file mode 100644 index 0000000..ff58fb5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hideinfocards/resource/patch/HideInfocardsResourcePatch.kt @@ -0,0 +1,40 @@ +package app.revanced.patches.youtube.layout.hideinfocards.resource.patch + +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.layout.hideinfocards.annotations.HideInfocardsCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@HideInfocardsCompatibility +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class HideInfocardsResourcePatch : ResourcePatch { + internal companion object { + var drawerResourceId: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_infocards", + StringResource("revanced_hide_infocards_title", "Hide info-cards"), + true, + StringResource("revanced_hide_infocards_summary_on", "Info-cards are hidden"), + StringResource("revanced_hide_infocards_summary_off", "Info-cards are shown") + ) + ) + + drawerResourceId = ResourceMappingPatch.resourceMappings.single { + it.type == "id" && it.name == "info_cards_drawer_header" + }.id + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt new file mode 100644 index 0000000..030bc1e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/annotations/MixPlaylistsPatchCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.hidemixplaylists.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MixPlaylistsPatchCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt new file mode 100644 index 0000000..0105797 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/CreateMixPlaylistFingerprint.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.youtube.layout.hidemixplaylists.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 CreateMixPlaylistFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L", "L", "L", "L", "L"), listOf( + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_VIRTUAL, + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt new file mode 100644 index 0000000..dcbb906 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/fingerprints/SecondCreateMixPlaylistFingerprint.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.youtube.layout.hidemixplaylists.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 SecondCreateMixPlaylistFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, listOf("L", "L", "L", "L", "L", "L"), listOf( + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt new file mode 100644 index 0000000..e241848 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidemixplaylists/patch/MixPlaylistsPatch.kt @@ -0,0 +1,64 @@ +package app.revanced.patches.youtube.layout.hidemixplaylists.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.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.patcher.patch.BytecodePatch +import app.revanced.patches.youtube.layout.hidemixplaylists.annotations.MixPlaylistsPatchCompatibility +import app.revanced.patches.youtube.layout.hidemixplaylists.fingerprints.CreateMixPlaylistFingerprint +import app.revanced.patches.youtube.layout.hidemixplaylists.fingerprints.SecondCreateMixPlaylistFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class]) +@Name("hide-my-mix") +@Description("Hides mix playlists.") +@MixPlaylistsPatchCompatibility +@Version("0.0.1") +class MixPlaylistsPatch : BytecodePatch( + listOf( + CreateMixPlaylistFingerprint, SecondCreateMixPlaylistFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_mix_playlists", + StringResource("revanced_hide_mix_playlists_title", "Hide mix playlists"), + false, + StringResource("revanced_hide_mix_playlists_summary_on", "Mix playlists are hidden"), + StringResource("revanced_hide_mix_playlists_summary_off", "Mix playlists are shown") + ) + ) + + arrayOf(CreateMixPlaylistFingerprint, SecondCreateMixPlaylistFingerprint).forEach(::addHook) + + return PatchResultSuccess() + } + + private fun addHook(fingerprint: MethodFingerprint) { + with (fingerprint.result!!) { + val insertIndex = scanResult.patternScanResult!!.endIndex - 3 + + val register = (mutableMethod.instruction(insertIndex - 2) as OneRegisterInstruction).registerA + + mutableMethod.addInstruction( + insertIndex, + "invoke-static {v$register}, Lapp/revanced/integrations/patches/HideMixPlaylistsPatch;->hideMixPlaylists(Landroid/view/View;)V" + ) + } + + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt new file mode 100644 index 0000000..c6b4b2a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/annotations/HideTimeAndSeekbarCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.hidetimeandseekbar.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideTimeAndSeekbarCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt new file mode 100644 index 0000000..5bd00c5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/fingerprints/TimeCounterFingerprint.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.youtube.layout.hidetimeandseekbar.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(3) +object TimeCounterFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), listOf( + Opcode.IGET_OBJECT, + Opcode.IGET_WIDE, + Opcode.CONST_WIDE_16, + Opcode.CMP_LONG, + Opcode.IF_LEZ, + Opcode.IGET_OBJECT, + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.GOTO, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt new file mode 100644 index 0000000..8a7821a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hidetimeandseekbar/patch/HideTimeAndSeekbarPatch.kt @@ -0,0 +1,76 @@ +package app.revanced.patches.youtube.layout.hidetimeandseekbar.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.addInstructions +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.shared.fingerprints.SeekbarFingerprint +import app.revanced.patches.shared.fingerprints.SeekbarOnDrawFingerprint +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.layout.hidetimeandseekbar.annotations.HideTimeAndSeekbarCompatibility +import app.revanced.patches.youtube.layout.hidetimeandseekbar.fingerprints.TimeCounterFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("hide-time-and-seekbar") +@Description("Hides progress bar and time counter on videos.") +@HideTimeAndSeekbarCompatibility +@Version("0.0.1") +class HideTimeAndSeekbarPatch : BytecodePatch( + listOf( + SeekbarFingerprint, TimeCounterFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_time_and_seekbar", + StringResource("revanced_hide_time_and_seekbar_title", "Hide time and seekbar"), + false, + StringResource("revanced_hide_time_and_seekbar_summary_on", "Time and seekbar are hidden"), + StringResource("revanced_hide_time_and_seekbar_summary_off", "Time and seekbar are shown") + ) + ) + + val createVideoPlayerSeekbarMethod = SeekbarFingerprint.result!!.let { + SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) } + }.result!!.mutableMethod + + createVideoPlayerSeekbarMethod.addInstructions( + 0, """ + const/4 v0, 0x0 + invoke-static { }, Lapp/revanced/integrations/patches/HideTimeAndSeekbarPatch;->hideTimeAndSeekbar()Z + move-result v0 + if-eqz v0, :hide_time_and_seekbar + return-void + :hide_time_and_seekbar + nop + """ + ) + + val timeCounterMethod = TimeCounterFingerprint.result!!.mutableMethod + + timeCounterMethod.addInstructions( + 0, """ + invoke-static { }, Lapp/revanced/integrations/patches/HideTimeAndSeekbarPatch;->hideTimeAndSeekbar()Z + move-result v0 + if-eqz v0, :hide_time_and_seekbar + return-void + :hide_time_and_seekbar + nop + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt new file mode 100644 index 0000000..e6e5058 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/annotations/OldQualityLayoutCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.oldqualitylayout.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class OldQualityLayoutCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt new file mode 100644 index 0000000..4abb688 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/fingerprints/QualityMenuViewInflateFingerprint.kt @@ -0,0 +1,31 @@ +package app.revanced.patches.youtube.layout.oldqualitylayout.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 QualityMenuViewInflateFingerprint : MethodFingerprint( + "L", AccessFlags.FINAL or AccessFlags.PUBLIC, listOf("L", "L", "L"), listOf( + Opcode.INVOKE_SUPER, + Opcode.CONST, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST_16, + Opcode.INVOKE_VIRTUAL, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.CONST_STRING, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt new file mode 100644 index 0000000..dd4597b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/oldqualitylayout/patch/OldQualityLayoutPatch.kt @@ -0,0 +1,61 @@ +package app.revanced.patches.youtube.layout.oldqualitylayout.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.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.youtube.layout.oldqualitylayout.annotations.OldQualityLayoutCompatibility +import app.revanced.patches.youtube.layout.oldqualitylayout.fingerprints.QualityMenuViewInflateFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("old-quality-layout") +@Description("Enables the original video quality flyout in the video player settings") +@OldQualityLayoutCompatibility +@Version("0.0.1") +// new ReVanced users have no idea what it means to use the "old quality layout menu" +// maybe rename this patch to better describe what it provides (ie: user-selectable-video-resolution ) +class OldQualityLayoutPatch : BytecodePatch( + listOf(QualityMenuViewInflateFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_use_old_style_quality_settings", + StringResource("revanced_old_style_quality_settings_enabled_title", "Use old video quality player menu"), + true, + StringResource("revanced_old_style_quality_settings_summary_on", "Old video quality menu is used"), + StringResource("revanced_old_style_quality_settings_summary_off", "Old video quality menu is not used") + ) + ) + + val inflateFingerprintResult = QualityMenuViewInflateFingerprint.result!! + val method = inflateFingerprintResult.mutableMethod + val instructions = method.implementation!!.instructions + + // at this index the listener is added to the list view + val listenerInvokeRegister = instructions.size - 1 - 1 + + // get the register which stores the quality menu list view + val onItemClickViewRegister = (instructions[listenerInvokeRegister] as FiveRegisterInstruction).registerC + + // insert the integrations method + method.addInstruction( + listenerInvokeRegister, // insert the integrations instructions right before the listener + "invoke-static { v$onItemClickViewRegister }, Lapp/revanced/integrations/patches/playback/quality/OldQualityLayoutPatch;->showOldQualityMenu(Landroid/widget/ListView;)V" + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt new file mode 100644 index 0000000..527d1b8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/annotations/HideEmailAddressCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.personalinformation.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideEmailAddressCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt new file mode 100644 index 0000000..0f9de46 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/fingerprints/AccountSwitcherAccessibilityLabelFingerprint.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.youtube.layout.personalinformation.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.personalinformation.resource.patch.HideEmailAddressResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object AccountSwitcherAccessibilityLabelFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.NEW_ARRAY, + Opcode.CONST_4, + Opcode.APUT_OBJECT, + Opcode.CONST, + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == HideEmailAddressResourcePatch.accountSwitcherAccessibilityLabelId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt new file mode 100644 index 0000000..46234db --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/bytecode/patch/HideEmailAddressPatch.kt @@ -0,0 +1,53 @@ +package app.revanced.patches.youtube.layout.personalinformation.bytecode.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.addInstructions +import app.revanced.patcher.extensions.instruction +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.youtube.layout.personalinformation.annotations.HideEmailAddressCompatibility +import app.revanced.patches.youtube.layout.personalinformation.bytecode.fingerprints.AccountSwitcherAccessibilityLabelFingerprint +import app.revanced.patches.youtube.layout.personalinformation.resource.patch.HideEmailAddressResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, HideEmailAddressResourcePatch::class]) +@Name("hide-email-address") +@Description("Hides the email address in the account switcher.") +@HideEmailAddressCompatibility +@Version("0.0.1") +class HideEmailAddressPatch : BytecodePatch( + listOf( + AccountSwitcherAccessibilityLabelFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val accountSwitcherAccessibilityLabelResult = AccountSwitcherAccessibilityLabelFingerprint.result!! + val accountSwitcherAccessibilityLabelMethod = accountSwitcherAccessibilityLabelResult.mutableMethod + + val setVisibilityConstIndex = + accountSwitcherAccessibilityLabelResult.scanResult.patternScanResult!!.endIndex + + val setVisibilityConstRegister = ( + accountSwitcherAccessibilityLabelMethod.instruction + (setVisibilityConstIndex - 2) as OneRegisterInstruction + ).registerA + + accountSwitcherAccessibilityLabelMethod.addInstructions( + setVisibilityConstIndex, """ + invoke-static {v$setVisibilityConstRegister}, Lapp/revanced/integrations/patches/HideEmailAddressPatch;->hideEmailAddress(I)I + move-result v$setVisibilityConstRegister + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt new file mode 100644 index 0000000..4029b83 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/personalinformation/resource/patch/HideEmailAddressResourcePatch.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.youtube.layout.personalinformation.resource.patch + +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.layout.personalinformation.annotations.HideEmailAddressCompatibility +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Name("hide-email-address-resource-patch") +@HideEmailAddressCompatibility +@DependsOn([SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class HideEmailAddressResourcePatch : ResourcePatch { + companion object { + internal var accountSwitcherAccessibilityLabelId: Long = -1 + } + + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_email_address", + StringResource("revanced_hide_email_address_title", "Hide email in account switcher"), + false, + StringResource("revanced_hide_email_address_summary_on", "Email address is hidden"), + StringResource("revanced_hide_email_address_summary_off", "Email address is shown") + ) + ) + + accountSwitcherAccessibilityLabelId = ResourceMappingPatch.resourceMappings.single { + it.type == "string" && it.name == "account_switcher_accessibility_label" + }.id + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt new file mode 100644 index 0000000..1457062 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/annotations/PivotBarCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.pivotbar.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PivotBarCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt new file mode 100644 index 0000000..8be27c3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/fingerprints/PivotBarCreateButtonViewFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.layout.pivotbar.createbutton.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object PivotBarCreateButtonViewFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_DIRECT_RANGE, // unique instruction anchor + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt new file mode 100644 index 0000000..f3b70d5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/createbutton/patch/CreateButtonRemoverPatch.kt @@ -0,0 +1,71 @@ +package app.revanced.patches.youtube.layout.pivotbar.createbutton.patch + +import app.revanced.extensions.toErrorResult +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.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.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.layout.pivotbar.annotations.PivotBarCompatibility +import app.revanced.patches.youtube.layout.pivotbar.createbutton.fingerprints.PivotBarCreateButtonViewFingerprint +import app.revanced.patches.youtube.layout.pivotbar.fingerprints.InitializeButtonsFingerprint +import app.revanced.patches.youtube.layout.pivotbar.resource.patch.ResolvePivotBarFingerprintsPatch +import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.REGISTER_TEMPLATE_REPLACEMENT +import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.injectHook +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, ResourceMappingPatch::class, SettingsPatch::class, ResolvePivotBarFingerprintsPatch::class]) +@Name("hide-create-button") +@Description("Hides the create button in the navigation bar.") +@PivotBarCompatibility +@Version("0.0.1") +class CreateButtonRemoverPatch : BytecodePatch() { + private companion object { + const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/HideCreateButtonPatch;" + } + + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_create_button", + StringResource("revanced_hide_create_button_title", "Hide create button"), + true, + StringResource("revanced_hide_create_button_summary_on", "Create button is hidden"), + StringResource("revanced_hide_create_button_summary_off", "Create button is shown") + ) + ) + + /* + * Resolve fingerprints + */ + + InitializeButtonsFingerprint.result!!.let { + if (!PivotBarCreateButtonViewFingerprint.resolve(context, it.mutableMethod, it.mutableClass)) + return PivotBarCreateButtonViewFingerprint.toErrorResult() + } + + val createButtonResult = PivotBarCreateButtonViewFingerprint.result!! + val insertIndex = createButtonResult.scanResult.patternScanResult!!.endIndex + + /* + * Inject hooks + */ + + val hook = "invoke-static { v$REGISTER_TEMPLATE_REPLACEMENT }, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->hideCreateButton(Landroid/view/View;)V" + + createButtonResult.mutableMethod.injectHook(hook, insertIndex) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt new file mode 100644 index 0000000..32a77f7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/InitializeButtonsFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.layout.pivotbar.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.pivotbar.resource.patch.ResolvePivotBarFingerprintsPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object InitializeButtonsFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { + it.opcode == Opcode.CONST && (it as WideLiteralInstruction).wideLiteral == + ResolvePivotBarFingerprintsPatch.imageOnlyTabResourceId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt new file mode 100644 index 0000000..0609dd1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/fingerprints/PivotBarConstructorFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.layout.pivotbar.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object PivotBarConstructorFingerprint : MethodFingerprint( + access = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + strings = listOf("com.google.android.apps.youtube.app.endpoint.flags") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt new file mode 100644 index 0000000..785d61c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/resource/patch/ResolvePivotBarFingerprintsPatch.kt @@ -0,0 +1,45 @@ +package app.revanced.patches.youtube.layout.pivotbar.resource.patch + +import app.revanced.extensions.toErrorResult +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.layout.pivotbar.annotations.PivotBarCompatibility +import app.revanced.patches.youtube.layout.pivotbar.fingerprints.InitializeButtonsFingerprint +import app.revanced.patches.youtube.layout.pivotbar.fingerprints.PivotBarConstructorFingerprint + +@DependsOn([ResourceMappingPatch::class]) +@PivotBarCompatibility +@Description("Resolves necessary fingerprints.") +@Version("0.0.1") +class ResolvePivotBarFingerprintsPatch : BytecodePatch( + listOf(PivotBarConstructorFingerprint) +) { + internal companion object { + var imageOnlyTabResourceId: Long = -1 + } + + override fun execute(context: BytecodeContext): PatchResult { + // imageOnlyTabResourceId is used in InitializeButtonsFingerprint fingerprint + ResourceMappingPatch.resourceMappings.find { it.type == "layout" && it.name == "image_only_tab" } + ?.let { imageOnlyTabResourceId = it.id } ?: return PatchResultError("Failed to find resource") + + PivotBarConstructorFingerprint.result?.let { + // Resolve InitializeButtonsFingerprint on the class of the method + // which PivotBarConstructorFingerprint resolved to + if (!InitializeButtonsFingerprint.resolve( + context, + it.classDef + ) + ) return InitializeButtonsFingerprint.toErrorResult() + } ?: return PivotBarConstructorFingerprint.toErrorResult() + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt new file mode 100644 index 0000000..770d63c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarEnumFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object PivotBarEnumFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IF_NEZ, // target reference + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt new file mode 100644 index 0000000..cfa5e93 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/fingerprints/PivotBarShortsButtonViewFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object PivotBarShortsButtonViewFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL_RANGE, + Opcode.MOVE_RESULT_OBJECT, // target reference + Opcode.GOTO, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt new file mode 100644 index 0000000..259f1bc --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/shortsbutton/patch/ShortsButtonRemoverPatch.kt @@ -0,0 +1,93 @@ +package app.revanced.patches.youtube.layout.pivotbar.shortsbutton.patch + +import app.revanced.extensions.toErrorResult +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.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.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.layout.pivotbar.annotations.PivotBarCompatibility +import app.revanced.patches.youtube.layout.pivotbar.fingerprints.InitializeButtonsFingerprint +import app.revanced.patches.youtube.layout.pivotbar.resource.patch.ResolvePivotBarFingerprintsPatch +import app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints.PivotBarEnumFingerprint +import app.revanced.patches.youtube.layout.pivotbar.shortsbutton.fingerprints.PivotBarShortsButtonViewFingerprint +import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.REGISTER_TEMPLATE_REPLACEMENT +import app.revanced.patches.youtube.layout.pivotbar.utils.InjectionUtils.injectHook +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class, ResolvePivotBarFingerprintsPatch::class]) +@Name("hide-shorts-button") +@Description("Hides the shorts button on the navigation bar.") +@PivotBarCompatibility +@Version("0.0.1") +class ShortsButtonRemoverPatch : BytecodePatch() { + private companion object { + const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/HideShortsButtonPatch;" + } + + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_shorts_button", + StringResource("revanced_hide_shorts_button_title", "Hide shorts button"), + true, + StringResource("revanced_hide_shorts_button_summary_on", "Shorts button is hidden"), + StringResource("revanced_hide_shorts_button_summary_off", "Shorts button is shown") + ) + ) + + /* + * Resolve fingerprints + */ + + val initializeButtonsResult = InitializeButtonsFingerprint.result!! + + val fingerprintResults = + arrayOf(PivotBarEnumFingerprint, PivotBarShortsButtonViewFingerprint) + .onEach { + if (!it.resolve( + context, + initializeButtonsResult.mutableMethod, + initializeButtonsResult.mutableClass + ) + ) + return it.toErrorResult() + } + .map { it.result!!.scanResult.patternScanResult!! } + + + val enumScanResult = fingerprintResults[0] + val buttonViewResult = fingerprintResults[1] + + val enumHookInsertIndex = enumScanResult.startIndex + 2 + val buttonHookInsertIndex = buttonViewResult.endIndex + + /* + * Inject hooks + */ + + val enumHook = "sput-object v$REGISTER_TEMPLATE_REPLACEMENT, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->lastPivotTab:Ljava/lang/Enum;" + val buttonHook = "invoke-static { v$REGISTER_TEMPLATE_REPLACEMENT }, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->hideShortsButton(Landroid/view/View;)V" + + // Inject bottom to top to not mess up the indices + mapOf( + buttonHook to buttonHookInsertIndex, + enumHook to enumHookInsertIndex + ).forEach { (hook, insertIndex) -> + initializeButtonsResult.mutableMethod.injectHook(hook, insertIndex) + } + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt new file mode 100644 index 0000000..8667b10 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/pivotbar/utils/InjectionUtils.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.youtube.layout.pivotbar.utils + +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import org.jf.dexlib2.Opcode.MOVE_RESULT_OBJECT +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +internal object InjectionUtils { + const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX" + + /** + * Injects an instruction into insertIndex of the hook. + * @param hook The hook to insert. + * @param insertIndex The index to insert the instruction at. + * [MOVE_RESULT_OBJECT] has to be the previous instruction before [insertIndex]. + */ + fun MutableMethod.injectHook(hook: String, insertIndex: Int) { + val injectTarget = this + + // Register to pass to the hook + val registerIndex = insertIndex - 1 // MOVE_RESULT_OBJECT is always the previous instruction + val register = (injectTarget.instruction(registerIndex) as OneRegisterInstruction).registerA + + injectTarget.addInstruction( + insertIndex, + hook.replace("REGISTER_INDEX", register.toString()), + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt new file mode 100644 index 0000000..016ff8f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/annotations/PlayerButtonBackgroundCompatibility.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.layout.playerbuttonbackground.annotations +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.google.android.youtube", arrayOf("17.49.37")) + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlayerButtonBackgroundCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt new file mode 100644 index 0000000..c99615d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerbuttonbackground/patch/PlayerButtonBackgroundPatch.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.youtube.layout.playerbuttonbackground.patch + +import app.revanced.extensions.doRecursively +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.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patches.youtube.layout.playerbuttonbackground.annotations.PlayerButtonBackgroundCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import org.w3c.dom.Element + +@Patch +@DependsOn([FixLocaleConfigErrorPatch::class]) +@Name("remove-player-button-background") +@Description("Removes the background from the video player buttons.") +@PlayerButtonBackgroundCompatibility +@Version("0.0.1") +class PlayerButtonBackgroundPatch : ResourcePatch { + private companion object { + const val RESOURCE_FILE_PATH = "res/drawable/player_button_circle_background.xml" + } + + override fun execute(context: ResourceContext): PatchResult { + context.xmlEditor[RESOURCE_FILE_PATH].use { editor -> + editor.file.doRecursively node@{ node -> + if (node !is Element) return@node + + node.getAttributeNode("android:color")?.let { attribute -> + attribute.textContent = "@android:color/transparent" + } + } + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt new file mode 100644 index 0000000..aad154c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/annotations/PlayerPopupPanelsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.playerpopuppanels.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlayerPopupPanelsCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt new file mode 100644 index 0000000..4b9636f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/fingerprints/EngagementPanelControllerFingerprint.kt @@ -0,0 +1,36 @@ +package app.revanced.patches.youtube.layout.playerpopuppanels.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(3) +object EngagementPanelControllerFingerprint : MethodFingerprint( + "L", AccessFlags.PRIVATE or AccessFlags.FINAL, listOf("L", "L", "Z", "Z", "Z"), listOf( + Opcode.MOVE_OBJECT_FROM16, + Opcode.MOVE_OBJECT_FROM16, + Opcode.MOVE_FROM16, + Opcode.IGET_BOOLEAN, + Opcode.CONST_4, + Opcode.IF_NEZ, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.SGET_OBJECT, + Opcode.SGET_OBJECT, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.RETURN_OBJECT, + Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.CONST_4, + Opcode.CONST_4, + Opcode.CONST_4, + Opcode.CONST_4, + Opcode.CONST_4, + Opcode.CONST_4, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt new file mode 100644 index 0000000..9aca33f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/playerpopuppanels/patch/PlayerPopupPanelsPatch.kt @@ -0,0 +1,59 @@ +package app.revanced.patches.youtube.layout.playerpopuppanels.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.addInstructions +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.youtube.layout.playerpopuppanels.annotations.PlayerPopupPanelsCompatibility +import app.revanced.patches.youtube.layout.playerpopuppanels.fingerprints.EngagementPanelControllerFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("disable-auto-player-popup-panels") +@Description("Disable automatic popup panels (playlist or live chat) on video player.") +@PlayerPopupPanelsCompatibility +@Version("0.0.1") +class PlayerPopupPanelsPatch : BytecodePatch( + listOf( + EngagementPanelControllerFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_player_popup_panels_enabled", + StringResource("revanced_player_popup_panels_title", "Disable player popup panels"), + false, + StringResource("revanced_player_popup_panels_summary_on", "Player popup panels are disabled"), + StringResource("revanced_player_popup_panels_summary_off", "Player popup panels are enabled") + ) + ) + + val engagementPanelControllerMethod = EngagementPanelControllerFingerprint.result!!.mutableMethod + + engagementPanelControllerMethod.addInstructions( + 0, """ + invoke-static { }, Lapp/revanced/integrations/patches/DisablePlayerPopupPanelsPatch;->disablePlayerPopupPanels()Z + move-result v0 + if-eqz v0, :player_popup_panels + if-eqz p4, :player_popup_panels + const/4 v0, 0x0 + return-object v0 + :player_popup_panels + nop + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt new file mode 100644 index 0000000..280d5c1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/annotations/HideReelsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.reels.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideReelsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt new file mode 100644 index 0000000..af24744 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/fingerprints/HideReelsFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.layout.reels.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object HideReelsFingerprint : MethodFingerprint( + access = AccessFlags.PROTECTED or AccessFlags.FINAL, parameters = listOf("L", "L"), + strings = listOf("multiReelDismissalCallback", "reelItemRenderers", "reelDismissalInfo") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt new file mode 100644 index 0000000..65ed14c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/reels/patch/HideReelsPatch.kt @@ -0,0 +1,51 @@ +package app.revanced.patches.youtube.layout.reels.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.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.youtube.layout.reels.annotations.HideReelsCompatibility +import app.revanced.patches.youtube.layout.reels.fingerprints.HideReelsFingerprint +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +//@Patch TODO: this is currently in the general-bytecode-ads patch due to the integrations having a preference for including reels or not. Move it here. +@Name("hide-reels") +@Description("Hides reels on the home page.") +@DependsOn([SettingsPatch::class]) +@HideReelsCompatibility +@Version("0.0.1") +class HideReelsPatch : BytecodePatch( + listOf( + HideReelsFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_reel_button", + StringResource("revanced_hide_reel_button_title", "Hide reels button"), + true, + StringResource("revanced_hide_reel_button_summary_on", "Reels button is hidden"), + StringResource("revanced_hide_reel_button_summary_off", "Reels button is shown") + ) + ) + + val result = HideReelsFingerprint.result!! + + // HideReel will hide the reel view before it is being used, + // so we pass the view to the HideReel method + result.mutableMethod.addInstruction( + result.scanResult.patternScanResult!!.endIndex, + "invoke-static { v2 }, Lapp/revanced/integrations/patches/HideReelsPatch;->HideReel(Landroid/view/View;)V" + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt new file mode 100644 index 0000000..393f8ed --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/annotations/ReturnYouTubeDislikeCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ReturnYouTubeDislikeCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt new file mode 100644 index 0000000..81c5699 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/DislikeFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object DislikeFingerprint : MethodFingerprint( + "V", + AccessFlags.PROTECTED or AccessFlags.CONSTRUCTOR, + strings = listOf("like/dislike") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt new file mode 100644 index 0000000..f3d9a8f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/LikeFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + + +@FuzzyPatternScanMethod(2) +object LikeFingerprint : MethodFingerprint( + "V", + AccessFlags.PROTECTED or AccessFlags.CONSTRUCTOR, + strings = listOf("like/like") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt new file mode 100644 index 0000000..17b43b2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/RemoveLikeFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object RemoveLikeFingerprint : MethodFingerprint( + "V", + AccessFlags.PROTECTED or AccessFlags.CONSTRUCTOR, + strings = listOf("like/removelike") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt new file mode 100644 index 0000000..dadd8e6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object TextComponentFingerprint : MethodFingerprint( + strings = listOf("com.google.android.apps.youtube.music") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt new file mode 100644 index 0000000..631ca5f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/fingerprints/TextComponentSpecParentFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object TextComponentSpecParentFingerprint : MethodFingerprint( + strings = listOf("TextComponentSpec: No converter for extension: ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt new file mode 100644 index 0000000..de92cf1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/patch/ReturnYouTubeDislikePatch.kt @@ -0,0 +1,85 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.patch + +import app.revanced.extensions.toErrorResult +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.MethodFingerprintExtensions.name +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility +import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.* +import app.revanced.patches.youtube.layout.returnyoutubedislike.resource.patch.ReturnYouTubeDislikeResourcePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch + +@Patch +@DependsOn([IntegrationsPatch::class, VideoIdPatch::class, ReturnYouTubeDislikeResourcePatch::class]) +@Name("return-youtube-dislike") +@Description("Shows the dislike count of videos using the Return YouTube Dislike API.") +@ReturnYouTubeDislikeCompatibility +@Version("0.0.1") +class ReturnYouTubeDislikePatch : BytecodePatch( + listOf( + TextComponentSpecParentFingerprint, LikeFingerprint, DislikeFingerprint, RemoveLikeFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + listOf( + LikeFingerprint.toPatch(Vote.LIKE), + DislikeFingerprint.toPatch(Vote.DISLIKE), + RemoveLikeFingerprint.toPatch(Vote.REMOVE_LIKE) + ).forEach { (fingerprint, vote) -> + with(fingerprint.result ?: return PatchResultError("Failed to find ${fingerprint.name} method.")) { + mutableMethod.addInstructions( + 0, + """ + const/4 v0, ${vote.value} + invoke-static {v0}, Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->sendVote(I)V + """ + ) + } + } + + VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->newVideoLoaded(Ljava/lang/String;)V") + + with(TextComponentFingerprint + .apply { resolve(context, TextComponentSpecParentFingerprint.result!!.classDef) } + .result ?: return TextComponentFingerprint.toErrorResult() + ) { + val createComponentMethod = mutableMethod + + val conversionContextParam = 5 + val textRefParam = createComponentMethod.parameters.size - 2 + val insertIndex = scanResult.stringsScanResult!!.matches.first().index - 2 + + createComponentMethod.addInstructions( + insertIndex, + """ + move-object/from16 v7, p$conversionContextParam + move-object/from16 v8, p$textRefParam + invoke-static {v7, v8}, Lapp/revanced/integrations/patches/ReturnYouTubeDislikePatch;->onComponentCreated(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;)V + """ + ) + } + return PatchResultSuccess() + } + + private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind) + + private data class VotePatch(val fingerprint: MethodFingerprint, val voteKind: Vote) + + private enum class Vote(val value: Int) { + LIKE(1), + DISLIKE(-1), + REMOVE_LIKE(0) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt new file mode 100644 index 0000000..35dd0af --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/resource/patch/ReturnYouTubeDislikeResourcePatch.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.youtube.layout.returnyoutubedislike.resource.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.shared.settings.preference.impl.Preference +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.util.resources.ResourceUtils.mergeStrings + +@DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class]) +@Name("return-youtube-dislike-resource-patch") +@Description("Adds the preferences for Return YouTube Dislike.") +@ReturnYouTubeDislikeCompatibility +@Version("0.0.1") +class ReturnYouTubeDislikeResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + val youtubePackage = "com.google.android.youtube" + SettingsPatch.addPreference( + Preference( + StringResource("revanced_ryd_settings_title", "Return YouTube Dislike"), + Preference.Intent( + youtubePackage, + "ryd_settings", + "com.google.android.libraries.social.licenses.LicenseActivity" + ), + StringResource("revanced_ryd_settings_summary", "Settings for Return YouTube Dislike"), + ) + ) + // merge strings + context.mergeStrings("returnyoutubedislike/host/values/strings.xml") + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt new file mode 100644 index 0000000..710fb22 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/annotations/SponsorBlockCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.sponsorblock.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SponsorBlockCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt new file mode 100644 index 0000000..2c6d472 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/AppendTimeFingerprint.kt @@ -0,0 +1,29 @@ +package app.revanced.patches.youtube.layout.sponsorblock.bytecode.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 AppendTimeFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("L", "L", "L"), + listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.IGET_OBJECT, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt new file mode 100644 index 0000000..d173337 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/NextGenWatchLayoutFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.util.MethodUtil + +object NextGenWatchLayoutFingerprint : MethodFingerprint( + "V", // constructors return void, in favour of speed of matching, this fingerprint has been added + customFingerprint = { methodDef -> MethodUtil.isConstructor(methodDef) && methodDef.parameterTypes.size == 3 && methodDef.definingClass.endsWith("NextGenWatchLayout;") } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt new file mode 100644 index 0000000..975bdfe --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/PlayerOverlaysLayoutInitFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PlayerOverlaysLayoutInitFingerprint : MethodFingerprint( + + customFingerprint = { methodDef -> methodDef.returnType.endsWith("YouTubePlayerOverlaysLayout;") } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt new file mode 100644 index 0000000..822484a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/RectangleFieldInvalidatorFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.MethodReference + +object RectangleFieldInvalidatorFingerprint : MethodFingerprint( + "V", + customFingerprint = custom@{ methodDef -> + val instructions = methodDef.implementation?.instructions!! + val instructionCount = instructions.count() + + // the method has definitely more than 5 instructions + if (instructionCount < 5) return@custom false + + val referenceInstruction = instructions.elementAt(instructionCount - 2) // the second to last instruction + val reference = ((referenceInstruction as? ReferenceInstruction)?.reference as? MethodReference) + + reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt new file mode 100644 index 0000000..8620559 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/fingerprints/ShortsPlayerConstructorFingerprint.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + + +@FuzzyPatternScanMethod(3) +object ShortsPlayerConstructorFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + opcodes = listOf( + Opcode.MOVE_OBJECT_FROM16, + Opcode.MOVE_OBJECT_FROM16, + Opcode.MOVE_OBJECT_FROM16, + Opcode.INVOKE_DIRECT_RANGE, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IPUT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.CONST_4 + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == SponsorBlockResourcePatch.reelButtonGroupResourceId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt new file mode 100644 index 0000000..15e40ff --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt @@ -0,0 +1,293 @@ +package app.revanced.patches.youtube.layout.sponsorblock.bytecode.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.data.toMethodWalker +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.replaceInstruction +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.shared.fingerprints.SeekbarFingerprint +import app.revanced.patches.shared.fingerprints.SeekbarOnDrawFingerprint +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +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.bytecode.fingerprints.* +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.playercontrols.bytecode.patch.PlayerControlsBytecodePatch +import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch +import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.* +import org.jf.dexlib2.iface.instruction.formats.Instruction35c +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.MethodReference +import org.jf.dexlib2.iface.reference.StringReference + +@Patch +@DependsOn( + dependencies = [ + VideoInformationPatch::class, // updates video information and adds method to seek in video + PlayerControlsBytecodePatch::class, + IntegrationsPatch::class, + SponsorBlockResourcePatch::class, + VideoIdPatch::class + ] +) +@Name("sponsorblock") +@Description("Integrate SponsorBlock.") +@SponsorBlockCompatibility +@Version("0.0.1") +class SponsorBlockBytecodePatch : BytecodePatch( + listOf( + SeekbarFingerprint, + NextGenWatchLayoutFingerprint, + AppendTimeFingerprint, + PlayerOverlaysLayoutInitFingerprint, + ShortsPlayerConstructorFingerprint, + StartVideoInformerFingerprint + ) +) { + + private companion object { + const val INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR = + "Lapp/revanced/integrations/sponsorblock/PlayerController;" + } + + override fun execute(context: BytecodeContext): PatchResult { + /* + Hook the video time methods + */ + with(VideoInformationPatch) { + videoTimeHook( + INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR, + "setVideoTime" + ) + highPrecisionTimeHook( + INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR, + "setHighPrecisionVideoTime" + ) + } + + /* + Set current video id + */ + VideoIdPatch.injectCall("$INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") + + /* + Seekbar drawing + */ + val seekbarSignatureResult = SeekbarFingerprint.result!!.let { + SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) } + }.result!! + val seekbarMethod = seekbarSignatureResult.mutableMethod + val seekbarMethodInstructions = seekbarMethod.implementation!!.instructions + + /* + Get the instance of the seekbar rectangle + */ + for ((index, instruction) in seekbarMethodInstructions.withIndex()) { + if (instruction.opcode != Opcode.MOVE_OBJECT_FROM16) continue + seekbarMethod.addInstruction( + index + 1, + "invoke-static/range {p0 .. p0}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;)V" + ) + break + } + + for ((index, instruction) in seekbarMethodInstructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_STATIC) continue + + val invokeInstruction = instruction as Instruction35c + if ((invokeInstruction.reference as MethodReference).name != "round") continue + + val insertIndex = index + 2 + + // set the thickness of the segment + seekbarMethod.addInstruction( + insertIndex, + "invoke-static {v${invokeInstruction.registerC}}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarThickness(I)V" + ) + break + } + + /* + Set rectangle absolute left and right positions + */ + val drawRectangleInstructions = seekbarMethodInstructions.withIndex().filter { (_, instruction) -> + instruction is ReferenceInstruction && (instruction.reference as? MethodReference)?.name == "drawRect" + }.map { (index, instruction) -> // TODO: improve code + index to (instruction as FiveRegisterInstruction).registerD + } + + val (indexRight, rectangleRightRegister) = drawRectangleInstructions[0] + val (indexLeft, rectangleLeftRegister) = drawRectangleInstructions[3] + + // order of operation is important here due to the code above which has to be improved + // the reason for that is that we get the index, add instructions and then the offset would be wrong + seekbarMethod.addInstruction( + indexLeft + 1, + "invoke-static {v$rectangleLeftRegister}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarAbsoluteLeft(Landroid/graphics/Rect;)V" + ) + seekbarMethod.addInstruction( + indexRight + 1, + "invoke-static {v$rectangleRightRegister}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarAbsoluteRight(Landroid/graphics/Rect;)V" + ) + + /* + Draw segment + */ + val drawSegmentInstructionInsertIndex = (seekbarMethodInstructions.size - 1 - 2) + val (canvasInstance, centerY) = (seekbarMethodInstructions[drawSegmentInstructionInsertIndex] as FiveRegisterInstruction).let { + it.registerC to it.registerE + } + seekbarMethod.addInstruction( + drawSegmentInstructionInsertIndex, + "invoke-static {v$canvasInstance, v$centerY}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V" + ) + + /* + Voting & Shield button + */ + val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult + + val controlsLayoutStubResourceId = + ResourceMappingPatch.resourceMappings.single { it.type == "id" && it.name == "controls_layout_stub" }.id + val zoomOverlayResourceId = + ResourceMappingPatch.resourceMappings.single { it.type == "id" && it.name == "video_zoom_overlay_stub" }.id + + methods@ for (method in controlsMethodResult.mutableClass.methods) { + val instructions = method.implementation?.instructions!! + instructions@ for ((index, instruction) in instructions.withIndex()) { + // search for method which inflates the controls layout view + if (instruction.opcode != Opcode.CONST) continue@instructions + + when ((instruction as NarrowLiteralInstruction).wideLiteral) { + controlsLayoutStubResourceId -> { + // replace the view with the YouTubeControlsOverlay + val moveResultInstructionIndex = index + 5 + val inflatedViewRegister = + (instructions[moveResultInstructionIndex] as OneRegisterInstruction).registerA + // initialize with the player overlay object + method.addInstructions( + moveResultInstructionIndex + 1, // insert right after moving the view to the register and use that register + """ + invoke-static {v$inflatedViewRegister}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->initialize(Ljava/lang/Object;)V + invoke-static {v$inflatedViewRegister}, Lapp/revanced/integrations/sponsorblock/VotingButton;->initialize(Ljava/lang/Object;)V + """ + ) + } + + zoomOverlayResourceId -> { + val invertVisibilityMethod = + context.toMethodWalker(method).nextMethod(index - 6, true).getMethod() as MutableMethod + // change visibility of the buttons + invertVisibilityMethod.addInstructions( + 0, """ + invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibilityNegatedImmediate(Z)V + invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibilityNegatedImmediate(Z)V + """.trimIndent() + ) + } + } + } + } + + // change visibility of the buttons + PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibility(Z)V") + PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibility(Z)V") + + // set SegmentHelperLayout.context to the player layout instance + val instanceRegister = 0 + NextGenWatchLayoutFingerprint.result!!.mutableMethod.addInstruction( + 3, // after super call + "invoke-static/range {p$instanceRegister}, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->addSkipSponsorView15(Landroid/view/View;)V" + ) + + // append the new time to the player layout + val appendTimeFingerprintResult = AppendTimeFingerprint.result!! + val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex + val targetRegister = + (appendTimeFingerprintResult.method.implementation!!.instructions.elementAt(appendTimePatternScanStartIndex + 1) as OneRegisterInstruction).registerA + + appendTimeFingerprintResult.mutableMethod.addInstructions( + appendTimePatternScanStartIndex + 2, """ + invoke-static {v$targetRegister}, Lapp/revanced/integrations/sponsorblock/SponsorBlockUtils;->appendTimeWithoutSegments(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$targetRegister + """ + ) + + // initialize the player controller + VideoInformationPatch.onCreateHook(INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR, "initialize") + + // initialize the sponsorblock view + PlayerOverlaysLayoutInitFingerprint.result!!.mutableMethod.addInstruction( + 6, // after inflating the view + "invoke-static {p0}, Lapp/revanced/integrations/sponsorblock/player/ui/SponsorBlockView;->initialize(Ljava/lang/Object;)V" + ) + + // get rectangle field name + RectangleFieldInvalidatorFingerprint.resolve(context, seekbarSignatureResult.classDef) + val rectangleFieldInvalidatorInstructions = + RectangleFieldInvalidatorFingerprint.result!!.method.implementation!!.instructions + val rectangleFieldName = + ((rectangleFieldInvalidatorInstructions.elementAt(rectangleFieldInvalidatorInstructions.count() - 3) as ReferenceInstruction).reference as FieldReference).name + + // replace the "replaceMeWith*" strings + context + .proxy(context.classes.first { it.type.endsWith("PlayerController;") }) + .mutableClass + .methods + .find { it.name == "setSponsorBarRect" } + ?.let { method -> + fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) { + val register = (instruction as OneRegisterInstruction).registerA + this.replaceInstruction( + 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 + startVideoInformerMethod.addInstructions( + 0, """ + const/4 v0, 0x0 + sput-boolean v0, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->shorts_playing:Z + """ + ) + + val shortsPlayerConstructorMethod = ShortsPlayerConstructorFingerprint.result!!.mutableMethod + + shortsPlayerConstructorMethod.addInstructions( + 0, """ + const/4 v0, 0x1 + sput-boolean v0, $INTEGRATIONS_PLAYER_CONTROLLER_CLASS_DESCRIPTOR->shorts_playing:Z + """ + ) + + // TODO: isSBChannelWhitelisting implementation + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt new file mode 100644 index 0000000..7b0d58f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/resource/patch/SponsorBlockResourcePatch.kt @@ -0,0 +1,116 @@ +package app.revanced.patches.youtube.layout.sponsorblock.resource.patch + +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.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.shared.settings.preference.impl.Preference +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +import app.revanced.util.resources.ResourceUtils.copyXmlNode +import app.revanced.util.resources.ResourceUtils.mergeStrings + +@Name("sponsorblock-resource-patch") +@SponsorBlockCompatibility +@DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class, ResourceMappingPatch::class]) +@Version("0.0.1") +class SponsorBlockResourcePatch : ResourcePatch { + companion object { + internal var reelButtonGroupResourceId: Long = 0 + } + + override fun execute(context: ResourceContext): PatchResult { + val youtubePackage = "com.google.android.youtube" + SettingsPatch.addPreference( + Preference( + StringResource("sb_settings", "SponsorBlock"), + Preference.Intent( + youtubePackage, + "sponsorblock_settings", + "com.google.android.libraries.social.licenses.LicenseActivity" + ), + StringResource("revanced_sponsorblock_settings_summary", "SponsorBlock related settings"), + ) + ) + val classLoader = this.javaClass.classLoader + + /* + merge SponsorBlock strings to main strings + */ + context.mergeStrings("sponsorblock/host/values/strings.xml") + + /* + merge SponsorBlock drawables to main drawables + */ + + arrayOf( + ResourceUtils.ResourceGroup( + "layout", + "inline_sponsor_overlay.xml", + "new_segment.xml", + "skip_sponsor_button.xml" + ), + ResourceUtils.ResourceGroup( + // required resource for back button, because when the base APK is used, this resource will not exist + "drawable", + "ic_sb_adjust.xml", + "ic_sb_compare.xml", + "ic_sb_edit.xml", + "ic_sb_logo.xml", + "ic_sb_publish.xml", + "ic_sb_voting.xml" + ), + ResourceUtils.ResourceGroup( + // required resource for back button, because when the base APK is used, this resource will not exist + "drawable-xxxhdpi", "quantum_ic_skip_next_white_24.png" + ) + ).forEach { resourceGroup -> + context.copyResources("sponsorblock", resourceGroup) + } + + /* + merge xml nodes from the host to their real xml files + */ + + // copy nodes from host resources to their real xml files + val hostingResourceStream = + classLoader.getResourceAsStream("sponsorblock/host/layout/youtube_controls_layout.xml")!! + + val targetXmlEditor = context.xmlEditor["res/layout/youtube_controls_layout.xml"] + "RelativeLayout".copyXmlNode( + context.xmlEditor[hostingResourceStream], + targetXmlEditor + ).also { + val children = targetXmlEditor.file.getElementsByTagName("RelativeLayout").item(0).childNodes + + // Replace the startOf with the voting button view so that the button does not overlap + for (i in 1 until children.length) { + val view = children.item(i) + + // Replace the attribute for a specific node only + if (!(view.hasAttributes() && view.attributes.getNamedItem("android:id").nodeValue.endsWith("live_chat_overlay_button"))) continue + + // voting button id from the voting button view from the youtube_controls_layout.xml host file + val votingButtonId = "@+id/voting_button" + + view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId + + break + } + }.close() // close afterwards + + reelButtonGroupResourceId = ResourceMappingPatch.resourceMappings.single { + it.type == "id" && it.name == "reel_persistent_edu_button_group" + }.id + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt new file mode 100644 index 0000000..c974f49 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/annotations/StartupShortsResetCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.startupshortsreset.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class StartupShortsResetCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt new file mode 100644 index 0000000..a994a26 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/fingerprints/UserWasInShortsFingerprint.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.youtube.layout.startupshortsreset.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(3) +object UserWasInShortsFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + ), + strings = listOf("Failed to read user_was_in_shorts proto after successful warmup"), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt new file mode 100644 index 0000000..924c078 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/startupshortsreset/patch/DisableShortsOnStartupPatch.kt @@ -0,0 +1,59 @@ +package app.revanced.patches.youtube.layout.startupshortsreset.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.addInstructions +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.youtube.layout.startupshortsreset.annotations.StartupShortsResetCompatibility +import app.revanced.patches.youtube.layout.startupshortsreset.fingerprints.UserWasInShortsFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("disable-startup-shorts-player") +@Description("Disables playing YouTube Shorts when launching YouTube.") +@StartupShortsResetCompatibility +@Version("0.0.1") +class DisableShortsOnStartupPatch : BytecodePatch( + listOf( + UserWasInShortsFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_startup_shorts_player_enabled", + StringResource("revanced_startup_shorts_player_title", "Disable shorts player at app startup"), + false, + StringResource("revanced_startup_shorts_player_summary_on", "Shorts player is disabled at app startup"), + StringResource("revanced_startup_shorts_player_summary_off", "Shorts player is enabled at app startup") + ) + ) + + val userWasInShortsResult = UserWasInShortsFingerprint.result!! + val userWasInShortsMethod = userWasInShortsResult.mutableMethod + val moveResultIndex = userWasInShortsResult.scanResult.patternScanResult!!.endIndex + + userWasInShortsMethod.addInstructions( + moveResultIndex + 1, """ + invoke-static { }, Lapp/revanced/integrations/patches/DisableStartupShortsPlayerPatch;->disableStartupShortsPlayer()Z + move-result v5 + if-eqz v5, :disable_shorts_player + return-void + :disable_shorts_player + nop + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt new file mode 100644 index 0000000..8d5a0fb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/annotations/TabletMiniPlayerCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.tabletminiplayer.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class TabletMiniPlayerCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt new file mode 100644 index 0000000..a378eaf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerDimensionsCalculatorFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // TODO: Find a good threshold value +object MiniPlayerDimensionsCalculatorFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("L"), + listOf( + Opcode.INVOKE_DIRECT, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.FLOAT_TO_DOUBLE, + Opcode.CONST_WIDE_HIGH16, + Opcode.CMPL_DOUBLE, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt new file mode 100644 index 0000000..59350da --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.tabletminiplayer.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 MiniPlayerOverrideFingerprint : MethodFingerprint( + "Z", AccessFlags.STATIC or AccessFlags.PUBLIC, + opcodes = listOf(Opcode.RETURN), // anchor to insert the instruction +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt new file mode 100644 index 0000000..aba50b3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerOverrideNoContextFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.layout.tabletminiplayer.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 MiniPlayerOverrideNoContextFingerprint : MethodFingerprint( + "Z", AccessFlags.FINAL or AccessFlags.PRIVATE, + opcodes = listOf(Opcode.RETURN), // anchor to insert the instruction +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt new file mode 100644 index 0000000..e4777c4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/fingerprints/MiniPlayerResponseModelSizeCheckFingerprint.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.youtube.layout.tabletminiplayer.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 MiniPlayerResponseModelSizeCheckFingerprint : MethodFingerprint( + "L", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("L", "L"), + listOf( + Opcode.RETURN_OBJECT, + Opcode.CHECK_CAST, + Opcode.CHECK_CAST, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt new file mode 100644 index 0000000..6f0b2a5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/tabletminiplayer/patch/TabletMiniPlayerPatch.kt @@ -0,0 +1,105 @@ +package app.revanced.patches.youtube.layout.tabletminiplayer.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.addInstructions +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.layout.tabletminiplayer.annotations.TabletMiniPlayerCompatibility +import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerDimensionsCalculatorFingerprint +import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerOverrideFingerprint +import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerOverrideNoContextFingerprint +import app.revanced.patches.youtube.layout.tabletminiplayer.fingerprints.MiniPlayerResponseModelSizeCheckFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("tablet-mini-player") +@Description("Enables the tablet mini player layout.") +@TabletMiniPlayerCompatibility +@Version("0.0.1") +class TabletMiniPlayerPatch : BytecodePatch( + listOf( + MiniPlayerDimensionsCalculatorFingerprint, + MiniPlayerResponseModelSizeCheckFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_tablet_miniplayer", + StringResource("revanced_tablet_miniplayer_title", "Enable the tablet Mini-player"), + false, + StringResource("revanced_tablet_miniplayer_summary_on", "Tablet Mini-player is enabled"), + StringResource("revanced_tablet_miniplayer_summary_off", "Tablet Mini-player is disabled") + ) + ) + + // first resolve the fingerprints via the parent fingerprint + val miniPlayerClass = MiniPlayerDimensionsCalculatorFingerprint.result!!.classDef + + /* + * no context parameter method + */ + MiniPlayerOverrideNoContextFingerprint.resolve(context, miniPlayerClass) + val (method, _, parameterRegister) = MiniPlayerOverrideNoContextFingerprint.addProxyCall() + // - 1 means to insert before the return instruction + val secondInsertIndex = method.implementation!!.instructions.size - 1 + method.insertOverride(secondInsertIndex, parameterRegister /** same register used to return **/) + + /* + * method with context parameter + */ + MiniPlayerOverrideFingerprint.resolve(context, miniPlayerClass) + val (_, _, _) = MiniPlayerOverrideFingerprint.addProxyCall() + + /* + * size check return value override + */ + val (_, _, _) = MiniPlayerResponseModelSizeCheckFingerprint.addProxyCall() + + return PatchResultSuccess() + } + + // helper methods + private companion object { + fun MethodFingerprint.addProxyCall(): Triple { + val (method, scanIndex, parameterRegister) = this.unwrap() + method.insertOverride(scanIndex, parameterRegister) + + return Triple(method, scanIndex, parameterRegister) + } + + fun MutableMethod.insertOverride(index: Int, overrideRegister: Int) { + this.addInstructions( + index, + """ + invoke-static {v$overrideRegister}, Lapp/revanced/integrations/patches/TabletMiniPlayerOverridePatch;->getTabletMiniPlayerOverride(Z)Z + move-result v$overrideRegister + """ + ) + } + + fun MethodFingerprint.unwrap(): Triple { + val result = this.result!! + val scanIndex = result.scanResult.patternScanResult!!.endIndex + val method = result.mutableMethod + val instructions = method.implementation!!.instructions + val parameterRegister = (instructions[scanIndex] as OneRegisterInstruction).registerA + + return Triple(method, scanIndex, parameterRegister) + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt new file mode 100644 index 0000000..5bf3350 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/annotations/ThemeCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.theme.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ThemeCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt new file mode 100644 index 0000000..ba6e29c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/fingerprints/LithoThemeFingerprint.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.youtube.layout.theme.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 LithoThemeFingerprint : MethodFingerprint( + "V", AccessFlags.PROTECTED or AccessFlags.FINAL, listOf("L"), listOf( + Opcode.APUT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.IGET_OBJECT, + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IPUT_OBJECT, + Opcode.IGET, + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt new file mode 100644 index 0000000..6575ff4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/LithoThemePatch.kt @@ -0,0 +1,36 @@ +package app.revanced.patches.youtube.layout.theme.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.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility +import app.revanced.patches.youtube.layout.theme.fingerprints.LithoThemeFingerprint + +@Name("litho-components-theme") +@Description("Applies a custom theme to litho components.") +@ThemeCompatibility +@Version("0.0.1") +class LithoThemePatch : BytecodePatch( + listOf( + LithoThemeFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = LithoThemeFingerprint.result!! + val method = result.mutableMethod + val patchIndex = result.scanResult.patternScanResult!!.endIndex - 1 + + method.addInstructions( + patchIndex, """ + invoke-static {p1}, Lapp/revanced/integrations/patches/LithoThemePatch;->applyLithoTheme(I)I + move-result p1 + """ + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt new file mode 100644 index 0000000..919342c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/theme/patch/ThemePatch.kt @@ -0,0 +1,74 @@ +package app.revanced.patches.youtube.layout.theme.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.* +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.theme.annotations.ThemeCompatibility +import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +import org.w3c.dom.Element + +@Patch(include = false) +@DependsOn([LithoThemePatch::class, FixLocaleConfigErrorPatch::class]) +@Name("theme") +@Description("Applies a custom theme.") +@ThemeCompatibility +@Version("0.0.1") +class ThemePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + val darkThemeBackgroundColor = darkThemeBackgroundColor!! + val lightThemeBackgroundColor = lightThemeBackgroundColor!! + + context.xmlEditor["res/values/colors.xml"].use { editor -> + val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element + + for (i in 0 until resourcesNode.childNodes.length) { + val node = resourcesNode.childNodes.item(i) as? Element ?: continue + + node.textContent = when (node.getAttribute("name")) { + "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", + "yt_black4", "yt_status_bar_background_dark", "material_grey_100", "material_grey_50", + "material_grey_600", "material_grey_800", "material_grey_850", "material_grey_900", + "material_grey_white_1000", "sud_glif_v3_dialog_background_color_dark" -> darkThemeBackgroundColor + + "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", "yt_white2", "yt_white3", "yt_white4", + "sud_glif_v3_dialog_background_color_light" -> lightThemeBackgroundColor + + else -> continue + } + } + } + + // copies the resource file to change the splash screen color + context.copyResources("theme", + ResourceUtils.ResourceGroup("values-night-v31", "styles.xml") + ) + + return PatchResultSuccess() + } + + companion object : OptionsContainer() { + var darkThemeBackgroundColor: String? by option( + PatchOption.StringOption( + key = "darkThemeBackgroundColor", + default = "@android:color/black", + title = "Background color for the dark theme", + description = "The background color of the dark theme. Can be a hex color or a resource reference.", + ) + ) + + var lightThemeBackgroundColor: String? by option( + PatchOption.StringOption( + key = "lightThemeBackgroundColor", + default = "@android:color/white", + title = "Background color for the light theme", + description = "The background color of the light theme. Can be a hex color or a resource reference.", + ) + ) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt new file mode 100644 index 0000000..313a0df --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/annotations/WatchinVRCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.watchinvr.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class WatchinVRCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt new file mode 100644 index 0000000..febf7b3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/fingerprints/WatchinVRFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.layout.watchinvr.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object WatchinVRFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("Z"), + strings = listOf("menu_item_cardboard_vr") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt new file mode 100644 index 0000000..d2915f3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watchinvr/patch/WatchinVRPatch.kt @@ -0,0 +1,55 @@ +package app.revanced.patches.youtube.layout.watchinvr.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.addInstructions +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.youtube.layout.watchinvr.annotations.WatchinVRCompatibility +import app.revanced.patches.youtube.layout.watchinvr.fingerprints.WatchinVRFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("hide-watch-in-vr") +@Description("Hides the Watch in VR option in the player settings flyout panel.") +@WatchinVRCompatibility +@Version("0.0.1") +class WatchinVRPatch : BytecodePatch( + listOf( + WatchinVRFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_watch_in_vr", + StringResource("revanced_hide_watch_in_vr_title", "Hide watch in VR"), + false, + StringResource("revanced_hide_watch_in_vr_summary_on", "Watch in VR player setting is hidden"), + StringResource("revanced_hide_watch_in_vr_summary_off", "Watch in VR player setting is shown") + ) + ) + + WatchinVRFingerprint.result!!.mutableMethod.addInstructions( + 0, """ + invoke-static {}, Lapp/revanced/integrations/patches/HideWatchinVRPatch;->hideWatchinVR()Z + move-result v0 + if-eqz v0, :shown + return-void + :shown + nop + """ + ) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt new file mode 100644 index 0000000..509e146 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/annotations/HideWaterwarkCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.watermark.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HideWatermarkCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt new file mode 100644 index 0000000..088edb7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.watermark.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object HideWatermarkFingerprint : MethodFingerprint ( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L", "L") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt new file mode 100644 index 0000000..9d03758 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/fingerprints/HideWatermarkParentFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.watermark.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object HideWatermarkParentFingerprint : MethodFingerprint ( + "L", AccessFlags.PUBLIC or AccessFlags.FINAL, strings = listOf("player_overlay_in_video_programming") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt new file mode 100644 index 0000000..ec5c1b3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/watermark/patch/HideWatermarkPatch.kt @@ -0,0 +1,63 @@ +package app.revanced.patches.youtube.layout.watermark.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.layout.watermark.annotations.HideWatermarkCompatibility +import app.revanced.patches.youtube.layout.watermark.fingerprints.HideWatermarkFingerprint +import app.revanced.patches.youtube.layout.watermark.fingerprints.HideWatermarkParentFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("hide-watermark") +@Description("Hides creator's watermarks on videos.") +@HideWatermarkCompatibility +@Version("0.0.1") +class HideWatermarkPatch : BytecodePatch( + listOf( + HideWatermarkParentFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_hide_video_watermark", + StringResource("revanced_hide_video_watermark_title", "Hide creator watermark on videos"), + true, + StringResource("revanced_hide_video_watermark_summary_on", "Watermark is hidden"), + StringResource("revanced_hide_video_watermark_summary_off", "Watermark is shown") + ) + ) + + HideWatermarkFingerprint.resolve(context, HideWatermarkParentFingerprint.result!!.classDef) + val result = HideWatermarkFingerprint.result + ?: return PatchResultError("Required parent method could not be found.") + + val method = result.mutableMethod + val line = method.implementation!!.instructions.size - 5 + + method.removeInstruction(line) + method.addInstructions( + line, """ + invoke-static {}, Lapp/revanced/integrations/patches/BrandingWaterMarkPatch;->isBrandingWatermarkShown()Z + move-result p2 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt new file mode 100644 index 0000000..2a070b2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/annotations/WideSearchbarCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.layout.widesearchbar.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class WideSearchbarCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt new file mode 100644 index 0000000..5ea1ffa --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/DrawActionBarFingerprint.kt @@ -0,0 +1,27 @@ +package app.revanced.patches.youtube.layout.widesearchbar.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 DrawActionBarFingerprint : MethodFingerprint( + "V", + AccessFlags.PRIVATE or AccessFlags.FINAL, + listOf("I", "I"), + listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.INVOKE_STATIC, + ), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt new file mode 100644 index 0000000..84ee999 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/fingerprints/SetWordmarkHeaderFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.layout.widesearchbar.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 SetWordmarkHeaderFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.IGET_BOOLEAN, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.CONST, + Opcode.INVOKE_STATIC, + ), + customFingerprint = { methodDef -> methodDef.parameterTypes.first() == "Landroid/widget/ImageView;" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt new file mode 100644 index 0000000..50263ba --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/widesearchbar/patch/WideSearchbarPatch.kt @@ -0,0 +1,88 @@ +package app.revanced.patches.youtube.layout.widesearchbar.patch + +import app.revanced.extensions.toErrorResult +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.data.toMethodWalker +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.layout.widesearchbar.annotations.WideSearchbarCompatibility +import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.DrawActionBarFingerprint +import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.SetWordmarkHeaderFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("enable-wide-searchbar") +@Description("Replaces the search icon with a wide search bar. This will hide the YouTube logo when active.") +@WideSearchbarCompatibility +@Version("0.0.1") +class WideSearchbarPatch : BytecodePatch( + listOf( + SetWordmarkHeaderFingerprint, DrawActionBarFingerprint + ) +) { + private companion object { + /** + * Walk a fingerprints method at a given index mutably. + * + * @param index The index to walk at. + * @param fromFingerprint The fingerprint to walk the method on. + * @return The [MutableMethod] which was walked on. + */ + fun BytecodeContext.walkMutable(index: Int, fromFingerprint: MethodFingerprint) = + fromFingerprint.result?.let { + toMethodWalker(it.method).nextMethod(index, true).getMethod() as MutableMethod + } ?: throw SetWordmarkHeaderFingerprint.toErrorResult() + + + /** + * Injects instructions required for certain methods. + * + */ + fun MutableMethod.injectSearchBarHook() { + addInstructions( + implementation!!.instructions.size - 1, + """ + invoke-static {}, Lapp/revanced/integrations/patches/NewActionbarPatch;->getNewActionBar()Z + move-result p0 + """ + ) + } + } + + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( + SwitchPreference( + "revanced_wide_searchbar", + StringResource("revanced_wide_searchbar_enabled_title", "Enable wide search bar"), + false, + StringResource("revanced_wide_searchbar_summary_on", "Wide search bar is enabled"), + StringResource("revanced_wide_searchbar_summary_off", "Wide search bar is disabled") + ) + ) + + val result = DrawActionBarFingerprint.result ?: return DrawActionBarFingerprint.toErrorResult() + + // patch methods + mapOf( + SetWordmarkHeaderFingerprint to 1, + DrawActionBarFingerprint to result.scanResult.patternScanResult!!.endIndex + ).forEach { (fingerprint, callIndex) -> + context.walkMutable(callIndex, fingerprint).injectSearchBarHook() + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt new file mode 100644 index 0000000..18de2e6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/annotations/AutoRepeatCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.autorepeat.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class AutoRepeatCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt new file mode 100644 index 0000000..a16683b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.misc.autorepeat.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object AutoRepeatFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf(), + customFingerprint = { methodDef -> methodDef.implementation!!.instructions.count() == 3 && methodDef.annotations.isEmpty()} +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt new file mode 100644 index 0000000..81d6b00 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/fingerprints/AutoRepeatParentFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.misc.autorepeat.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object AutoRepeatParentFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf( + "play() called when the player wasn't loaded.", + "play() blocked because Background Playability failed" + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt new file mode 100644 index 0000000..3861796 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/autorepeat/patch/AutoRepeatPatch.kt @@ -0,0 +1,88 @@ +package app.revanced.patches.youtube.misc.autorepeat.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.addInstructions +import app.revanced.patcher.extensions.removeInstruction +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.misc.autorepeat.annotations.AutoRepeatCompatibility +import app.revanced.patches.youtube.misc.autorepeat.fingerprints.AutoRepeatFingerprint +import app.revanced.patches.youtube.misc.autorepeat.fingerprints.AutoRepeatParentFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference + +@Patch +@DependsOn([IntegrationsPatch::class]) +@Name("always-autorepeat") +@Description("Always repeats the playing video again.") +@AutoRepeatCompatibility +@Version("0.0.1") +class AutoRepeatPatch : BytecodePatch( + listOf( + AutoRepeatParentFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + SwitchPreference( + "revanced_pref_auto_repeat", + StringResource("revanced_auto_repeat_enabled_title", "Enable auto-repeat"), + false, + StringResource("revanced_auto_repeat_summary_on", "Auto-repeat is enabled"), + StringResource("revanced_auto_repeat_summary_off", "Auto-repeat is disabled") + ) + ) + + //Get Result from the ParentFingerprint which is the playMethod we need to get. + val parentResult = AutoRepeatParentFingerprint.result + ?: return PatchResultError("ParentFingerprint did not resolve.") + + //this one needs to be called when app/revanced/integrations/patches/AutoRepeatPatch;->shouldAutoRepeat() returns true + val playMethod = parentResult.mutableMethod + AutoRepeatFingerprint.resolve(context, parentResult.classDef) + //String is: Laamp;->E()V + val methodToCall = playMethod.definingClass + "->" + playMethod.name + "()V"; + + //This is the method we search for + val result = AutoRepeatFingerprint.result + ?: return PatchResultError("FingerPrint did not resolve.") + val method = result.mutableMethod + + //Instructions to add to the smali code + val instructions = """ + invoke-static {}, Lapp/revanced/integrations/patches/AutoRepeatPatch;->shouldAutoRepeat()Z + move-result v0 + if-eqz v0, :noautorepeat + invoke-virtual {p0}, $methodToCall + :noautorepeat + return-void + """ + + //Get the implementation so we can do a check for null and get instructions size. + val implementation = method.implementation + ?: return PatchResultError("No Method Implementation found!") + + //Since addInstructions needs an index which starts counting at 0 and size starts counting at 1, + //we have to remove 1 to get the latest instruction + val index = implementation.instructions.size - 1 + + + //remove last instruction which is return-void + method.removeInstruction(index) + // Add our own instructions there + method.addInstructions(index, instructions) + + //Everything worked as expected, return Success + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt new file mode 100644 index 0000000..cac120b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/annotations/DebuggingCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.debugging.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class DebuggingCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt new file mode 100644 index 0000000..34cdde2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/patch/DebuggingPatch.kt @@ -0,0 +1,77 @@ +package app.revanced.patches.youtube.misc.debugging.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.* +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.misc.debugging.annotations.DebuggingCompatibility +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import org.w3c.dom.Element + +@Patch +@Name("debugging") +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Description("Adds debugging options.") +@DebuggingCompatibility +@Version("0.0.1") +class DebuggingPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + app.revanced.patches.shared.settings.preference.impl.PreferenceScreen( + "revanced_debug", + StringResource("revanced_debug_title", "Debugging"), + listOf( + SwitchPreference( + "revanced_debug_enabled", + StringResource("revanced_debug_enabled_title", "Enable debug logs"), + false, + StringResource("revanced_debug_summary_on", "Debug logs are enabled"), + StringResource("revanced_debug_summary_off", "Debug logs are disabled") + ), + SwitchPreference( + "revanced_debug_stacktrace_enabled", + StringResource( + "revanced_debug_stacktrace_enabled_title", + "Print stack traces" + ), + false, + StringResource("revanced_debug_stacktrace_summary_on", "Enabled printing stack traces"), + StringResource("revanced_debug_stacktrace_summary_off", "Disabled printing stack traces") + ), + ), + StringResource("revanced_debug_summary", "Enable or disable debugging options") + ) + ) + + if (debuggable == true) { + context.xmlEditor["AndroidManifest.xml"].use { dom -> + val applicationNode = dom + .file + .getElementsByTagName("application") + .item(0) as Element + + // set application as debuggable + applicationNode.setAttribute("android:debuggable", "true") + } + } + + return PatchResultSuccess() + } + + companion object : OptionsContainer() { + var debuggable: Boolean? by option( + PatchOption.BooleanOption( + key = "debuggable", + default = false, + title = "App debugging", + description = "Whether to make the app debuggable on Android.", + ) + ) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt new file mode 100644 index 0000000..8a2bb46 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/annotations/FixPlaybackCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.fix.playback.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FixPlaybackCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt new file mode 100644 index 0000000..1483956 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/patch/FixPlaybackPatch.kt @@ -0,0 +1,52 @@ +package app.revanced.patches.youtube.misc.fix.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.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.misc.fix.playback.annotations.FixPlaybackCompatibility +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch +import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch + +@DependsOn([ + IntegrationsPatch::class, + VideoInformationPatch::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() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt new file mode 100644 index 0000000..9d2760b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/annotations/ClientSpoofCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.misc.fix.spoof.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.google.android.youtube"), + Package("com.vanced.android.youtube") + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ClientSpoofCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt new file mode 100644 index 0000000..0c1dcdb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/fingerprints/UserAgentHeaderBuilderFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.misc.fix.spoof.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object UserAgentHeaderBuilderFingerprint : MethodFingerprint( + parameters = listOf("L", "L", "L"), + opcodes = listOf(Opcode.MOVE_RESULT_OBJECT, Opcode.INVOKE_VIRTUAL), + strings = listOf("(Linux; U; Android "), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt new file mode 100644 index 0000000..0b6a1eb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/spoof/patch/ClientSpoofPatch.kt @@ -0,0 +1,37 @@ +package app.revanced.patches.youtube.misc.fix.spoof.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.instruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.misc.fix.spoof.annotations.ClientSpoofCompatibility +import app.revanced.patches.youtube.misc.fix.spoof.fingerprints.UserAgentHeaderBuilderFingerprint +import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction + +@Patch +@Name("client-spoof") +@Description("Spoofs the YouTube or Vanced client to prevent playback issues.") +@ClientSpoofCompatibility +@Version("0.0.1") +class ClientSpoofPatch : BytecodePatch( + listOf(UserAgentHeaderBuilderFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = UserAgentHeaderBuilderFingerprint.result!! + val method = result.mutableMethod + + val insertIndex = result.scanResult.patternScanResult!!.endIndex + val packageNameRegister = (method.instruction(insertIndex) as FiveRegisterInstruction).registerD + + val originalPackageName = "com.google.android.youtube" + method.addInstruction(insertIndex, "const-string v$packageNameRegister, \"$originalPackageName\"") + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt new file mode 100644 index 0000000..12a4b74 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/annotations/VerticalScrollCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.fix.verticalscroll.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [ + Package("com.google.android.youtube"), + ] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class VerticalScrollCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt new file mode 100644 index 0000000..1f87924 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/fingerprints/CanScrollVerticallyFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.misc.fix.verticalscroll.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object CanScrollVerticallyFingerprint : MethodFingerprint( + "Z", + parameters = emptyList(), + opcodes = listOf(Opcode.INSTANCE_OF), + customFingerprint = { methodDef -> methodDef.definingClass.endsWith("SwipeRefreshLayout;") } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt new file mode 100644 index 0000000..3619ea6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/verticalscroll/patch/VerticalScrollPatch.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.youtube.misc.fix.verticalscroll.patch + +import app.revanced.extensions.toErrorResult +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patches.youtube.misc.fix.verticalscroll.annotations.VerticalScrollCompatibility +import app.revanced.patches.youtube.misc.fix.verticalscroll.fingerprints.CanScrollVerticallyFingerprint + +@Description("Fixes issues with scrolling on the home screen when the first component is of type EmptyComponent.") +@VerticalScrollCompatibility +@Version("0.0.1") +class VerticalScrollPatch : BytecodePatch( + listOf(CanScrollVerticallyFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + val result = CanScrollVerticallyFingerprint.result ?: return CanScrollVerticallyFingerprint.toErrorResult() + + result.mutableMethod.addInstructions( + 0, + """ + const/4 v0, 0x0 + return v0 + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt new file mode 100644 index 0000000..3687269 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/annotations/HDRBrightnessCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.hdrbrightness.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class HDRBrightnessCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt new file mode 100644 index 0000000..979f4e8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/fingerprints/HDRBrightnessFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.hdrbrightness.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object HDRBrightnessFingerprint : MethodFingerprint( + "V", + opcodes = listOf(Opcode.CMPL_FLOAT), + strings = listOf("c.SettingNotFound;", "screen_brightness", "android.mediaview"), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt new file mode 100644 index 0000000..f84efbe --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/hdrbrightness/patch/HDRBrightnessPatch.kt @@ -0,0 +1,64 @@ +package app.revanced.patches.youtube.misc.hdrbrightness.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.addInstructions +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.youtube.misc.hdrbrightness.annotations.HDRBrightnessCompatibility +import app.revanced.patches.youtube.misc.hdrbrightness.fingerprints.HDRBrightnessFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction +import org.jf.dexlib2.iface.reference.FieldReference + +@Patch +@Name("hdr-auto-brightness") +@Description("Makes the brightness of HDR videos follow the system default.") +@HDRBrightnessCompatibility +@Version("0.0.2") +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +class HDRBrightnessPatch : BytecodePatch( + listOf(HDRBrightnessFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + SwitchPreference( + "revanced_pref_hdr_autobrightness", + StringResource("revanced_hdr_autobrightness_enabled_title", "Enable auto HDR brightness"), + true, + StringResource("revanced_hdr_autobrightness_summary_on", "Auto HDR brightness is enabled"), + StringResource("revanced_hdr_autobrightness_summary_off", "Auto HDR brightness is disabled") + ) + ) + + val method = HDRBrightnessFingerprint.result!!.mutableMethod + + method.implementation!!.instructions.filter { instruction -> + val fieldReference = (instruction as? ReferenceInstruction)?.reference as? FieldReference + fieldReference?.let { it.name == "screenBrightness" } == true + }.forEach { instruction -> + val brightnessRegisterIndex = method.implementation!!.instructions.indexOf(instruction) + val register = (instruction as TwoRegisterInstruction).registerA + + val insertIndex = brightnessRegisterIndex + 1 + method.addInstructions( + insertIndex, + """ + invoke-static {v$register}, Lapp/revanced/integrations/patches/HDRAutoBrightnessPatch;->getHDRBrightness(F)F + move-result v$register + """ + ) + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt new file mode 100644 index 0000000..d8e9cd7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/annotations/IntegrationsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.integrations.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class IntegrationsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt new file mode 100644 index 0000000..730e856 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/InitFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.youtube.misc.integrations.fingerprints + +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint + +object InitFingerprint : IntegrationsFingerprint( + strings = listOf("Application creation"), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt new file mode 100644 index 0000000..813aaf5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/ServiceFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.integrations.fingerprints + +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint + +object ServiceFingerprint : IntegrationsFingerprint( + customFingerprint = { methodDef -> methodDef.definingClass.endsWith("ApiPlayerService;") && methodDef.name == "" }, + contextRegisterResolver = { it.implementation!!.registerCount - it.parameters.size } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt new file mode 100644 index 0000000..ff71061 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/fingerprints/StandalonePlayerFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.integrations.fingerprints + +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch.IntegrationsFingerprint + +object StandalonePlayerFingerprint : IntegrationsFingerprint( + strings = listOf( + "Invalid PlaybackStartDescriptor. Returning the instance itself.", + "com.google.android.music", + ), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt new file mode 100644 index 0000000..a5d219f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/integrations/patch/IntegrationsPatch.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.misc.integrations.patch + +import app.revanced.patcher.annotation.Name +import app.revanced.patches.youtube.misc.integrations.annotations.IntegrationsCompatibility +import app.revanced.patches.youtube.misc.integrations.fingerprints.InitFingerprint +import app.revanced.patches.youtube.misc.integrations.fingerprints.ServiceFingerprint +import app.revanced.patches.youtube.misc.integrations.fingerprints.StandalonePlayerFingerprint +import app.revanced.patches.shared.integrations.patch.AbstractIntegrationsPatch + +@Name("integrations") +@IntegrationsCompatibility +class IntegrationsPatch : AbstractIntegrationsPatch( + "Lapp/revanced/integrations/utils/ReVancedUtils;", + listOf(InitFingerprint, StandalonePlayerFingerprint, ServiceFingerprint), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt new file mode 100644 index 0000000..ea1772e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/annotation/LithoFilterCompatibility.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.misc.litho.filter.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class LithoFilterCompatibility + diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt new file mode 100644 index 0000000..7f661ac --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/ComponentContextParserFingerprint.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.youtube.misc.litho.filter.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object ComponentContextParserFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.GOTO, + Opcode.INVOKE_VIRTUAL, + Opcode.CONST_16, + Opcode.INVOKE_VIRTUAL, + Opcode.IPUT_OBJECT, + Opcode.NEW_INSTANCE + ), + strings = listOf("LoggingProperties are not in proto format") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt new file mode 100644 index 0000000..1842275 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/fingerprints/EmptyComponentBuilderFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.litho.filter.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object EmptyComponentBuilderFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_STATIC_RANGE + ), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt new file mode 100644 index 0000000..9ea24e4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/patch/LithoFilterPatch.kt @@ -0,0 +1,81 @@ +package app.revanced.patches.youtube.misc.litho.filter.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.BytecodeContext +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.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.litho.filter.annotation.LithoFilterCompatibility +import app.revanced.patches.youtube.misc.litho.filter.fingerprints.ComponentContextParserFingerprint +import app.revanced.patches.youtube.misc.litho.filter.fingerprints.EmptyComponentBuilderFingerprint +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.MethodReference + +@DependsOn([IntegrationsPatch::class]) +@Description("Hooks the method which parses the bytes into a ComponentContext to filter components.") +@LithoFilterCompatibility +@Version("0.0.1") +class LithoFilterPatch : BytecodePatch( + listOf(ComponentContextParserFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + ComponentContextParserFingerprint.result?.let { result -> + val builderMethodIndex = EmptyComponentBuilderFingerprint + .also { it.resolve(context, result.mutableMethod, result.mutableClass) } + .let { it.result!!.scanResult.patternScanResult!!.startIndex } + + val emptyComponentFieldIndex = builderMethodIndex + 2 + + with(result.mutableMethod) { + val insertHookIndex = result.scanResult.patternScanResult!!.endIndex + val clobberedRegister = (instruction(insertHookIndex - 3) as OneRegisterInstruction).registerA + + val builderMethodDescriptor = instruction(builderMethodIndex).toDescriptor() + val emptyComponentFieldDescriptor = instruction(emptyComponentFieldIndex).toDescriptor() + + val stringBuilderRegister = (instruction(insertHookIndex - 1) as TwoRegisterInstruction).registerA + + addInstructions( + insertHookIndex, // right after setting the component.pathBuilder field, + """ + invoke-static {v$stringBuilderRegister, v0}, Lapp/revanced/integrations/patches/LithoFilterPatch;->filter(Ljava/lang/StringBuilder;Ljava/lang/String;)Z + move-result v$clobberedRegister + if-eqz v$clobberedRegister, :not_an_ad + move-object/from16 v0, p1 + invoke-static {v0}, $builderMethodDescriptor + move-result-object v0 + iget-object v0, v0, $emptyComponentFieldDescriptor + return-object v0 + """, + listOf(ExternalLabel("not_an_ad", instruction(insertHookIndex))) + ) + } + } ?: return PatchResultError("Could not find the method to hook.") + + return PatchResultSuccess() + } + + private companion object { + fun Instruction.toDescriptor() = when (val reference = (this as? ReferenceInstruction)?.reference) { + is MethodReference -> "${reference.definingClass}->${reference.name}(${ + reference.parameterTypes.joinToString( + "" + ) { it } + })${reference.returnType}" + is FieldReference -> "${reference.definingClass}->${reference.name}:${reference.type}" + else -> throw PatchResultError("Unsupported reference type") + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt new file mode 100644 index 0000000..a304b71 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/annotations/FixLocaleConfigErrorCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.manifest.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class FixLocaleConfigErrorCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt new file mode 100644 index 0000000..801e444 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/manifest/patch/FixLocaleConfigErrorPatch.kt @@ -0,0 +1,36 @@ +package app.revanced.patches.youtube.misc.manifest.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.patches.youtube.misc.manifest.annotations.FixLocaleConfigErrorCompatibility +import org.w3c.dom.Element + +@Name("locale-config-fix") +@Description("Fixes an error when building the resources by patching the manifest file.") +@FixLocaleConfigErrorCompatibility +@Version("0.0.1") +class FixLocaleConfigErrorPatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + // create an xml editor instance + context.xmlEditor["AndroidManifest.xml"].use { + // edit the application nodes attribute... + val applicationNode = it + .file + .getElementsByTagName("application") + .item(0) as Element + + + // by replacing the attributes name + val attribute = "android:localeConfig" + applicationNode.setAttribute("localeConfig", applicationNode.getAttribute(attribute)) + applicationNode.removeAttribute(attribute) + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt new file mode 100644 index 0000000..4f4fa0d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/annotations/MicroGPatchCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.microg.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MicroGPatchCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt new file mode 100644 index 0000000..577f101 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastContextFetchFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CastContextFetchFingerprint : MethodFingerprint( + strings = listOf("Error fetching CastContext.") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt new file mode 100644 index 0000000..bf1f58c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CastDynamiteModuleFingerprint : MethodFingerprint( + strings = listOf("com.google.android.gms.cast.framework.internal.CastDynamiteModuleImpl") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt new file mode 100644 index 0000000..8939593 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/CastDynamiteModuleV2Fingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CastDynamiteModuleV2Fingerprint : MethodFingerprint( + strings = listOf("Failed to load module via V2: ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt new file mode 100644 index 0000000..e50f656 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/GooglePlayUtilityFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object GooglePlayUtilityFingerprint : MethodFingerprint( + "I", + AccessFlags.PUBLIC or AccessFlags.STATIC, + listOf("L", "L"), + strings = listOf("This should never happen.", "MetadataValueReader", "com.google.android.gms") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt new file mode 100644 index 0000000..f3d4119 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/IntegrityCheckFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object IntegrityCheckFingerprint : MethodFingerprint( + "L", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L", "L"), + strings = listOf("This should never happen.", "GooglePlayServicesUtil", "Google Play Store signature invalid.") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt new file mode 100644 index 0000000..04fcf86 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/PrimeFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PrimeFingerprint : MethodFingerprint( + strings = listOf("com.google.android.GoogleCamera", "com.android.vending") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt new file mode 100644 index 0000000..9a07e25 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/fingerprints/ServiceCheckFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.microg.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object ServiceCheckFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L", "I"), + strings = listOf("Google Play Services not available", "GooglePlayServices not available due to error ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt new file mode 100644 index 0000000..f4869c7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/bytecode/MicroGBytecodePatch.kt @@ -0,0 +1,75 @@ +package app.revanced.patches.youtube.misc.microg.patch.bytecode + +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.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.shared.fingerprints.WatchWhileActivityFingerprint +import app.revanced.patches.youtube.layout.castbutton.patch.HideCastButtonPatch +import app.revanced.patches.youtube.misc.fix.spoof.patch.ClientSpoofPatch +import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibility +import app.revanced.patches.youtube.misc.microg.fingerprints.* +import app.revanced.patches.youtube.misc.microg.patch.resource.MicroGResourcePatch +import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME +import app.revanced.util.microg.MicroGBytecodeHelper + +@Patch +@DependsOn( + [ + MicroGResourcePatch::class, + HideCastButtonPatch::class, + ClientSpoofPatch::class + ] +) +@Name("microg-support") +@Description("Allows YouTube ReVanced to run without root and under a different package name with Vanced MicroG.") +@MicroGPatchCompatibility +@Version("0.0.1") +class MicroGBytecodePatch : BytecodePatch( + listOf( + IntegrityCheckFingerprint, + ServiceCheckFingerprint, + GooglePlayUtilityFingerprint, + CastDynamiteModuleFingerprint, + CastDynamiteModuleV2Fingerprint, + CastContextFetchFingerprint, + PrimeFingerprint, + WatchWhileActivityFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + // apply common microG patch + MicroGBytecodeHelper.patchBytecode( + context, arrayOf( + MicroGBytecodeHelper.packageNameTransform( + PACKAGE_NAME, + REVANCED_PACKAGE_NAME + ) + ), + MicroGBytecodeHelper.PrimeMethodTransformationData( + PrimeFingerprint, + PACKAGE_NAME, + REVANCED_PACKAGE_NAME + ), + listOf( + IntegrityCheckFingerprint, + ServiceCheckFingerprint, + GooglePlayUtilityFingerprint, + CastDynamiteModuleFingerprint, + CastDynamiteModuleV2Fingerprint, + CastContextFetchFingerprint + ) + ) + + // inject the notice for MicroG + MicroGBytecodeHelper.injectNotice(WatchWhileActivityFingerprint) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt new file mode 100644 index 0000000..1cdc47f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/patch/resource/MicroGResourcePatch.kt @@ -0,0 +1,62 @@ +package app.revanced.patches.youtube.misc.microg.patch.resource + +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.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibility +import app.revanced.patches.youtube.misc.microg.shared.Constants.PACKAGE_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_APP_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants.SPOOFED_PACKAGE_NAME +import app.revanced.patches.youtube.misc.microg.shared.Constants.SPOOFED_PACKAGE_SIGNATURE +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.Preference +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch +import app.revanced.util.microg.Constants.MICROG_VENDOR +import app.revanced.util.microg.MicroGManifestHelper +import app.revanced.util.microg.MicroGResourceHelper + +@Name("microg-resource-patch") +@DependsOn([FixLocaleConfigErrorPatch::class, SettingsResourcePatch::class]) +@Description("Resource patch to allow YouTube ReVanced to run without root and under a different package name.") +@MicroGPatchCompatibility +@Version("0.0.1") +class MicroGResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + SettingsPatch.addPreference( + Preference( + StringResource("microg_settings", "MicroG Settings"), + Preference.Intent("$MICROG_VENDOR.android.gms", "", "org.microg.gms.ui.SettingsActivity"), + StringResource("microg_settings_summary", "Settings for MicroG"), + ) + ) + SettingsPatch.renameIntentsTargetPackage(REVANCED_PACKAGE_NAME) + + // update manifest + MicroGResourceHelper.patchManifest( + context, + PACKAGE_NAME, + REVANCED_PACKAGE_NAME, + REVANCED_APP_NAME + ) + + // add metadata to manifest + MicroGManifestHelper.addSpoofingMetadata( + context, + SPOOFED_PACKAGE_NAME, + SPOOFED_PACKAGE_SIGNATURE + ) + + // add strings + MicroGResourceHelper.addStrings(context) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt new file mode 100644 index 0000000..84b48de --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/microg/shared/Constants.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.microg.shared + +object Constants { + internal const val REVANCED_APP_NAME = "YouTube ReVanced" + internal const val REVANCED_PACKAGE_NAME = "app.revanced.android.youtube" + internal const val PACKAGE_NAME = "com.google.android.youtube" + internal const val SPOOFED_PACKAGE_NAME = PACKAGE_NAME + internal const val SPOOFED_PACKAGE_SIGNATURE = "24bb24c05e47e0aefa68a58a766179d9b613a600" +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt new file mode 100644 index 0000000..c64a1be --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/annotations/MinimizedPlaybackCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.minimizedplayback.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class MinimizedPlaybackCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt new file mode 100644 index 0000000..860925e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackKidsFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.minimizedplayback.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 MinimizedPlaybackKidsFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("I", "L", "L"), + listOf( + Opcode.IF_EQZ, + Opcode.SGET_OBJECT, + Opcode.IF_NE, + Opcode.CONST_4, + Opcode.IPUT_BOOLEAN, + Opcode.IF_EQZ, + Opcode.IGET, + Opcode.INVOKE_STATIC + ), + customFingerprint = { it.definingClass.endsWith("MinimizedPlaybackPolicyController;") } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt new file mode 100644 index 0000000..015736b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackManagerFingerprint.kt @@ -0,0 +1,42 @@ +package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value. +object MinimizedPlaybackManagerFingerprint : MethodFingerprint( + "Z", + AccessFlags.PUBLIC or AccessFlags.STATIC, + listOf("L"), + listOf( + Opcode.CONST_4, + Opcode.IF_EQZ, + Opcode.IGET, + Opcode.AND_INT_LIT16, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.IGET, + Opcode.CONST, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.IGET, + Opcode.IF_NE, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.GOTO, + Opcode.SGET_OBJECT, + Opcode.GOTO, + Opcode.CONST_4, + Opcode.IF_EQZ, + Opcode.IGET_BOOLEAN, + Opcode.IF_EQZ + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt new file mode 100644 index 0000000..af3840d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/fingerprints/MinimizedPlaybackSettingsFingerprint.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + + +@FuzzyPatternScanMethod(2) +object MinimizedPlaybackSettingsFingerprint : MethodFingerprint( + "L", + AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.IF_NEZ, + Opcode.GOTO, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST + ), +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt new file mode 100644 index 0000000..94e9924 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/minimizedplayback/patch/MinimizedPlaybackPatch.kt @@ -0,0 +1,88 @@ +package app.revanced.patches.youtube.misc.minimizedplayback.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.data.toMethodWalker +import app.revanced.patcher.extensions.addInstructions +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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.minimizedplayback.annotations.MinimizedPlaybackCompatibility +import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackKidsFingerprint +import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackManagerFingerprint +import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackSettingsFingerprint +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.MethodReference + + +@Patch +@Name("minimized-playback") +@Description("Enables minimized and background playback.") +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@MinimizedPlaybackCompatibility +@Version("0.0.1") +class MinimizedPlaybackPatch : BytecodePatch( + listOf( + MinimizedPlaybackKidsFingerprint, MinimizedPlaybackManagerFingerprint, MinimizedPlaybackSettingsFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + SwitchPreference( + "revanced_enable_minimized_playback", + StringResource("revanced_minimized_playback_enabled_title", "Enable minimized playback"), + true, + StringResource("revanced_minimized_playback_summary_on", "Minimized playback is enabled"), + StringResource("revanced_minimized_playback_summary_off", "Minimized playback is disabled") + ) + ) + + // Instead of removing all instructions like Vanced, + // we return the method at the beginning instead + MinimizedPlaybackManagerFingerprint.result!!.mutableMethod.addInstructions( + 0, """ + invoke-static {}, Lapp/revanced/integrations/patches/MinimizedPlaybackPatch;->isMinimizedPlaybackEnabled()Z + move-result v0 + return v0 + """ + ) + + val method = MinimizedPlaybackSettingsFingerprint.result!!.mutableMethod + val booleanCalls = method.implementation!!.instructions.withIndex() + .filter { ((it.value as? ReferenceInstruction)?.reference as? MethodReference)?.returnType == "Z" } + + val settingsBooleanIndex = booleanCalls.elementAt(1).index + val settingsBooleanMethod = + context.toMethodWalker(method).nextMethod(settingsBooleanIndex, true).getMethod() as MutableMethod + + settingsBooleanMethod.addInstructions( + 0, """ + invoke-static {}, Lapp/revanced/integrations/patches/MinimizedPlaybackPatch;->isMinimizedPlaybackEnabled()Z + move-result v0 + return v0 + """ + ) + + MinimizedPlaybackKidsFingerprint.result!!.mutableMethod.addInstructions( + 0, """ + invoke-static {}, Lapp/revanced/integrations/patches/MinimizedPlaybackPatch;->isMinimizedPlaybackEnabled()Z + move-result v0 + if-eqz v0, :enable + return-void + :enable + nop + """ + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt new file mode 100644 index 0000000..20241f3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/annotations/OpenLinksDirectlyCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.openlinksdirectly.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class OpenLinksDirectlyCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt new file mode 100644 index 0000000..e5c497f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.openlinksdirectly.annotations.OpenLinksDirectlyCompatibility +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@Name("open-links-directly-primary-fingerprint") +@OpenLinksDirectlyCompatibility +@Version("0.0.1") +object OpenLinksDirectlyFingerprintPrimary : MethodFingerprint( + "L", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf( + Opcode.CHECK_CAST, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.RETURN_OBJECT, + Opcode.CHECK_CAST, + Opcode.SGET + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt new file mode 100644 index 0000000..2dda99b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.openlinksdirectly.annotations.OpenLinksDirectlyCompatibility +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@Name("open-links-directly-secondary-fingerprint") +@OpenLinksDirectlyCompatibility +@Version("0.0.1") +object OpenLinksDirectlyFingerprintSecondary : MethodFingerprint( + "L", AccessFlags.PUBLIC or AccessFlags.STATIC, listOf("L"), listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.RETURN_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.CONST_STRING + ), + strings = listOf("Uri must have an absolute scheme") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt new file mode 100644 index 0000000..fc4fb51 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/openlinksdirectly/patch/OpenLinksDirectlyPatch.kt @@ -0,0 +1,72 @@ +package app.revanced.patches.youtube.misc.openlinksdirectly.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.addInstructions +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.patcher.patch.BytecodePatch +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.openlinksdirectly.annotations.OpenLinksDirectlyCompatibility +import app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints.OpenLinksDirectlyFingerprintPrimary +import app.revanced.patches.youtube.misc.openlinksdirectly.fingerprints.OpenLinksDirectlyFingerprintSecondary +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.iface.instruction.formats.Instruction11x +import org.jf.dexlib2.iface.instruction.formats.Instruction35c + +@Patch +@DependsOn([IntegrationsPatch::class, SettingsPatch::class]) +@Name("open-links-directly") +@Description("Bypasses URL redirects and opens links directly inside YouTube app.") +@OpenLinksDirectlyCompatibility +@Version("0.0.1") +class OpenLinksDirectlyPatch : BytecodePatch( + listOf( + OpenLinksDirectlyFingerprintPrimary, OpenLinksDirectlyFingerprintSecondary + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + SwitchPreference( + "revanced_uri_redirect", + StringResource("revanced_uri_redirect_title", "Open YouTube links inside app"), + true, + StringResource("revanced_uri_redirect_summary_on", "Links opened inside YouTube ReVanced"), + StringResource("revanced_uri_redirect_summary_off", "Links opened in web browser") + ) + ) + + OpenLinksDirectlyFingerprintPrimary.hookUriParser(true) + OpenLinksDirectlyFingerprintSecondary.hookUriParser(false) + + return PatchResultSuccess() + } +} + +fun MethodFingerprint.hookUriParser(isPrimaryFingerprint: Boolean) { + fun getTargetRegister(instruction: Instruction): Int { + if (isPrimaryFingerprint) return (instruction as Instruction35c).registerC + return (instruction as Instruction11x).registerA + } + with(this.result!!) { + val startIndex = scanResult.patternScanResult!!.startIndex + val instruction = method.implementation!!.instructions.elementAt(startIndex + 1) + val insertIndex = if (isPrimaryFingerprint) 1 else 2 + val targetRegister = getTargetRegister(instruction) + + mutableMethod.addInstructions( + startIndex + insertIndex, """ + invoke-static {v$targetRegister}, Lapp/revanced/integrations/patches/OpenLinksDirectlyPatch;->parseRedirectUri(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$targetRegister + """ + ) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt new file mode 100644 index 0000000..9149a05 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/annotation/PlayerControlsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.playercontrols.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlayerControlsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt new file mode 100644 index 0000000..50b3d5c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/bytecode/patch/PlayerControlsBytecodePatch.kt @@ -0,0 +1,83 @@ +package app.revanced.patches.youtube.misc.playercontrols.bytecode.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.fingerprint.method.impl.MethodFingerprint.Companion.resolve +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +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.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint +import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Name("player-controls-bytecode-patch") +@DependsOn([ResourceMappingPatch::class]) +@Description("Manages the code for the player controls of the YouTube player.") +@PlayerControlsCompatibility +@Version("0.0.1") +class PlayerControlsBytecodePatch : BytecodePatch( + listOf(PlayerControlsVisibilityFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!! + + bottomUiContainerResourceId = ResourceMappingPatch + .resourceMappings + .single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id + + // TODO: another solution is required, this is hacky + listOf(BottomControlsInflateFingerprint).resolve(context, context.classes) + inflateFingerprintResult = BottomControlsInflateFingerprint.result!! + + return PatchResultSuccess() + } + + internal companion object { + var bottomUiContainerResourceId: Long = 0 + + lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult + + private var inflateFingerprintResult: MethodFingerprintResult? = null + set(fingerprint) { + field = fingerprint!!.also { + moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex + viewRegister = + (it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA + } + } + + private var moveToRegisterInstructionIndex: Int = 0 + private var viewRegister: Int = 0 + + /** + * Injects the code to change the visibility of controls. + * @param descriptor The descriptor of the method which should be called. + */ + fun injectVisibilityCheckCall(descriptor: String) { + showPlayerControlsFingerprintResult.mutableMethod.addInstruction( + 0, + """ + invoke-static {p1}, $descriptor + """ + ) + } + + /** + * Injects the code to initialize the controls. + * @param descriptor The descriptor of the method which should be calleed. + */ + fun initializeControl(descriptor: String) { + inflateFingerprintResult!!.mutableMethod.addInstruction( + moveToRegisterInstructionIndex + 1, + "invoke-static {v$viewRegister}, $descriptor" + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt new file mode 100644 index 0000000..9df6bcf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/BottomControlsInflateFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object BottomControlsInflateFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT + ), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { instruction -> + instruction.opcode.ordinal == Opcode.CONST.ordinal && + (instruction as? WideLiteralInstruction)?.wideLiteral == PlayerControlsBytecodePatch.bottomUiContainerResourceId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt new file mode 100644 index 0000000..91e0acb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/fingerprints/PlayerControlsVisibilityFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.misc.playercontrols.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PlayerControlsVisibilityFingerprint : MethodFingerprint( + "V", + parameters = listOf("Z", "Z"), + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("YouTubeControlsOverlay;") + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt new file mode 100644 index 0000000..fbba6ef --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/resource/patch/BottomControlsResourcePatch.kt @@ -0,0 +1,85 @@ +package app.revanced.patches.youtube.misc.playercontrols.resource.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.DomFileEditor +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.manifest.patch.FixLocaleConfigErrorPatch +import app.revanced.patches.youtube.misc.playercontrols.annotation.PlayerControlsCompatibility + +@Name("bottom-controls-resource-patch") +@DependsOn([FixLocaleConfigErrorPatch::class]) +@Description("Manages the resources for the bottom controls of the YouTube player.") +@PlayerControlsCompatibility +@Version("0.0.1") +class BottomControlsResourcePatch : ResourcePatch { + override fun execute(context: ResourceContext): PatchResult { + resourceContext = context + targetXmlEditor = context.xmlEditor[TARGET_RESOURCE] + + return PatchResultSuccess() + } + + companion object { + internal const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml" + private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME" + + private lateinit var resourceContext: ResourceContext + private lateinit var targetXmlEditor: DomFileEditor + + // The element to which to add the new elements to + private var lastLeftOf = "fullscreen_button" + + + + /** + * Add new controls to the bottom of the YouTube player. + * @param hostYouTubeControlsBottomUiResourceName The hosting resource name containing the elements. + */ + internal fun addControls(hostYouTubeControlsBottomUiResourceName: String) { + val sourceXmlEditor = + resourceContext.xmlEditor[this::class.java.classLoader.getResourceAsStream( + hostYouTubeControlsBottomUiResourceName + )!!] + + val targetElement = + "android.support.constraint.ConstraintLayout" + + val hostElements = sourceXmlEditor.file.getElementsByTagName(targetElement).item(0).childNodes + + val destinationResourceFile = this.targetXmlEditor.file + val destinationElement = + destinationResourceFile.getElementsByTagName(targetElement).item(0) + + for (index in 1 until hostElements.length) { + val element = hostElements.item(index).cloneNode(true) + + // if the element has no attributes theres no point to adding it to the destination + if (!element.hasAttributes()) continue + + // set the elements lastLeftOf attribute to the lastLeftOf value + val namespace = "@+id" + element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue = + "$namespace/$lastLeftOf" + + // set lastLeftOf attribute to the the current element + val nameSpaceLength = 4 + lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength) + + // copy the element + destinationResourceFile.adoptNode(element) + destinationElement.appendChild(element) + } + sourceXmlEditor.close() + } + } + + override fun close() { + targetXmlEditor.close() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt new file mode 100644 index 0000000..d74d8bb --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/annotation/PlayerOverlaysHookCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.playeroverlay.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlayerOverlaysHookCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt new file mode 100644 index 0000000..1defdfe --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/fingerprint/PlayerOverlaysOnFinishInflateFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.playeroverlay.fingerprint + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PlayerOverlaysOnFinishInflateFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("YouTubePlayerOverlaysLayout;") && methodDef.name == "onFinishInflate" + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt new file mode 100644 index 0000000..9b1c95b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playeroverlay/patch/PlayerOverlaysHookPatch.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.youtube.misc.playeroverlay.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.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.playeroverlay.annotation.PlayerOverlaysHookCompatibility +import app.revanced.patches.youtube.misc.playeroverlay.fingerprint.PlayerOverlaysOnFinishInflateFingerprint + +@Name("player-overlays-hook") +@Description("Hook for adding custom overlays to the video player.") +@PlayerOverlaysHookCompatibility +@Version("0.0.1") +@DependsOn([IntegrationsPatch::class]) +class PlayerOverlaysHookPatch : BytecodePatch( + listOf( + PlayerOverlaysOnFinishInflateFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + // hook YouTubePlayerOverlaysLayout.onFinishInflate() + val method = PlayerOverlaysOnFinishInflateFingerprint.result!!.mutableMethod + method.addInstruction( + method.implementation!!.instructions.size - 2, + "invoke-static { p0 }, Lapp/revanced/integrations/patches/PlayerOverlaysHookPatch;->YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava/lang/Object;)V" + ) + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt new file mode 100644 index 0000000..d9ce962 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/annotation/PlayerTypeHookCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.playertype.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class PlayerTypeHookCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt new file mode 100644 index 0000000..0eb8e9a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/fingerprint/UpdatePlayerTypeFingerprint.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.youtube.misc.playertype.fingerprint + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@FuzzyPatternScanMethod(2) +object UpdatePlayerTypeFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.IGET_OBJECT, + Opcode.IF_NE, + Opcode.RETURN_VOID, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_VIRTUAL, + Opcode.INVOKE_VIRTUAL, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.CONST_4, + Opcode.INVOKE_STATIC, + Opcode.RETURN_VOID, + Opcode.CONST_4, + Opcode.INVOKE_STATIC, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt new file mode 100644 index 0000000..75e7220 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/patch/PlayerTypeHookPatch.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.youtube.misc.playertype.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.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.playertype.annotation.PlayerTypeHookCompatibility +import app.revanced.patches.youtube.misc.playertype.fingerprint.UpdatePlayerTypeFingerprint + +@Name("player-type-hook") +@Description("Hook to get the current player type of WatchWhileActivity") +@PlayerTypeHookCompatibility +@Version("0.0.1") +@DependsOn([IntegrationsPatch::class]) +class PlayerTypeHookPatch : BytecodePatch( + listOf( + UpdatePlayerTypeFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + // hook YouTubePlayerOverlaysLayout.updatePlayerLayout() + UpdatePlayerTypeFingerprint.result!!.mutableMethod.addInstruction( + 0, + "invoke-static { p1 }, Lapp/revanced/integrations/patches/PlayerTypeHookPatch;->YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(Ljava/lang/Object;)V" + ) + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt new file mode 100644 index 0000000..5fa1408 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/annotations/SettingsCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.settings.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SettingsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt new file mode 100644 index 0000000..1139510 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/LicenseActivityFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object LicenseActivityFingerprint : MethodFingerprint( + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("LicenseActivity;") && methodDef.name == "onCreate" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt new file mode 100644 index 0000000..599314a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeConstructorFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags + +object ThemeConstructorFingerprint : MethodFingerprint( + "L", + AccessFlags.PUBLIC or AccessFlags.STATIC, + listOf("L"), + strings = listOf("settings.SettingsActivity", ":android:show_fragment", "settings.GeneralPrefsFragment") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt new file mode 100644 index 0000000..9fc0065 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterAppFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.misc.settings.bytecode.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 ThemeSetterAppFingerprint : MethodFingerprint( + "L", AccessFlags.PUBLIC or AccessFlags.STATIC, opcodes = listOf( + Opcode.CONST, //target reference + Opcode.GOTO, + Opcode.CONST, //target reference + Opcode.INVOKE_DIRECT, + Opcode.RETURN_OBJECT, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.SGET_OBJECT, + Opcode.IF_NE, + Opcode.CONST, //target reference + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt new file mode 100644 index 0000000..d578927 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/fingerprints/ThemeSetterSystemFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.instruction.WideLiteralInstruction + +object ThemeSetterSystemFingerprint : MethodFingerprint( + "L", + opcodes = listOf(Opcode.RETURN_OBJECT), + customFingerprint = { methodDef -> + methodDef.implementation?.instructions?.any { + it.opcode.ordinal == Opcode.CONST.ordinal && (it as WideLiteralInstruction).wideLiteral == SettingsResourcePatch.appearanceStringId + } == true + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt new file mode 100644 index 0000000..2c1ee88 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/bytecode/patch/SettingsPatch.kt @@ -0,0 +1,160 @@ +package app.revanced.patches.youtube.misc.settings.bytecode.patch + +import app.revanced.extensions.toErrorResult +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.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.shared.settings.preference.impl.Preference +import app.revanced.patches.shared.settings.util.AbstractPreferenceScreen +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility +import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.LicenseActivityFingerprint +import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeConstructorFingerprint +import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterAppFingerprint +import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterSystemFingerprint +import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch +import org.jf.dexlib2.util.MethodUtil + +@Patch +@DependsOn( + [ + IntegrationsPatch::class, + SettingsResourcePatch::class, + ] +) +@Name("settings") +@Description("Adds settings for ReVanced to YouTube.") +@SettingsCompatibility +@Version("0.0.1") +class SettingsPatch : BytecodePatch( + listOf(LicenseActivityFingerprint, ThemeSetterSystemFingerprint, ThemeConstructorFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + fun buildInvokeInstructionsString( + registers: String = "v0", + classDescriptor: String = THEME_HELPER_DESCRIPTOR, + methodName: String = SET_THEME_METHOD_NAME, + parameters: String = "Ljava/lang/Object;" + ) = "invoke-static {$registers}, $classDescriptor->$methodName($parameters)V" + + // apply the current theme of the settings page + ThemeSetterSystemFingerprint.result!!.let { result -> + val call = buildInvokeInstructionsString() + result.mutableMethod.apply { + addInstruction( + result.scanResult.patternScanResult!!.startIndex, call + ) + addInstructions( + implementation!!.instructions.size - 1, call + ) + } + } + + // set the theme based on the preference of the app + with((ThemeConstructorFingerprint.result?.let { + ThemeSetterAppFingerprint.apply { + if (!resolve(context, it.classDef)) return ThemeSetterAppFingerprint.toErrorResult() + } + } ?: return ThemeConstructorFingerprint.toErrorResult()).result!!) { + fun buildInstructionsString(theme: Int) = """ + const/4 v0, 0x$theme + ${buildInvokeInstructionsString(parameters = "I")} + """ + + val patternScanResult = scanResult.patternScanResult!! + + mutableMethod.apply { + addInstructions( + patternScanResult.endIndex + 1, buildInstructionsString(1) + ) + addInstructions( + patternScanResult.endIndex - 7, buildInstructionsString(0) + ) + + addInstructions( + patternScanResult.endIndex - 9, buildInstructionsString(1) + ) + addInstructions( + implementation!!.instructions.size - 2, buildInstructionsString(0) + ) + } + + } + + // set the theme based on the preference of the device + with(LicenseActivityFingerprint.result!!) licenseActivity@{ + with(mutableMethod) { + fun buildSettingsActivityInvokeString( + registers: String = "p0", + classDescriptor: String = SETTINGS_ACTIVITY_DESCRIPTOR, + methodName: String = "initializeSettings", + parameters: String = this@licenseActivity.mutableClass.type + ) = buildInvokeInstructionsString(registers, classDescriptor, methodName, parameters) + + // initialize the settings + addInstructions( + 1, """ + ${buildSettingsActivityInvokeString()} + return-void + """ + ) + + // set the current theme + addInstruction(0, buildSettingsActivityInvokeString(methodName = "setTheme")) + } + + // remove method overrides + with(mutableClass) { + methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) } + } + } + + return PatchResultSuccess() + } + + internal companion object { + private const val INTEGRATIONS_PACKAGE = "app/revanced/integrations" + + private const val SETTINGS_ACTIVITY_DESCRIPTOR = "L$INTEGRATIONS_PACKAGE/settingsmenu/ReVancedSettingActivity;" + + private const val THEME_HELPER_DESCRIPTOR = "L$INTEGRATIONS_PACKAGE/utils/ThemeHelper;" + private const val SET_THEME_METHOD_NAME = "setTheme" + + fun addString(identifier: String, value: String, formatted: Boolean = true) = + SettingsResourcePatch.addString(identifier, value, formatted) + + fun addPreferenceScreen(preferenceScreen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) = + SettingsResourcePatch.addPreferenceScreen(preferenceScreen) + + fun addPreference(preference: Preference) = SettingsResourcePatch.addPreference(preference) + + fun renameIntentsTargetPackage(newPackage: String) { + SettingsResourcePatch.overrideIntentsTargetPackage = newPackage + } + } + + /** + * Preference screens patches should add their settings to. + */ + internal object PreferenceScreen : AbstractPreferenceScreen() { + val ADS = Screen("ads", "Ads", "Ad related settings") + val INTERACTIONS = Screen("interactions", "Interaction", "Settings related to interactions") + val LAYOUT = Screen("layout", "Layout", "Settings related to the layout") + val MISC = Screen("misc", "Misc", "Miscellaneous patches") + + override fun commit(screen: app.revanced.patches.shared.settings.preference.impl.PreferenceScreen) { + addPreferenceScreen(screen) + } + } + + override fun close() = PreferenceScreen.close() +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt new file mode 100644 index 0000000..2bd580d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/resource/patch/SettingsResourcePatch.kt @@ -0,0 +1,169 @@ +package app.revanced.patches.youtube.misc.settings.resource.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.DomFileEditor +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.shared.mapping.misc.patch.ResourceMappingPatch +import app.revanced.patches.shared.settings.preference.addPreference +import app.revanced.patches.shared.settings.preference.impl.ArrayResource +import app.revanced.patches.shared.settings.preference.impl.Preference +import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.resource.patch.AbstractSettingsResourcePatch +import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +import org.w3c.dom.Node + +@Name("settings-resource-patch") +@DependsOn([ResourceMappingPatch::class]) +@SettingsCompatibility +@Description("Applies mandatory patches to implement ReVanced settings into the application.") +@Version("0.0.1") +class SettingsResourcePatch : AbstractSettingsResourcePatch( + "revanced_prefs", + "settings" +) { + override fun execute(context: ResourceContext): PatchResult { + super.execute(context) + + /* + * used by a fingerprint of SettingsPatch + */ + appearanceStringId = ResourceMappingPatch.resourceMappings.find { + it.type == "string" && it.name == "app_theme_appearance_dark" + }!!.id + + /* + * create missing directory for the resources + */ + context["res/drawable-ldrtl-xxxhdpi"].mkdirs() + + /* + * copy layout resources + */ + arrayOf( + ResourceUtils.ResourceGroup( + "layout", + "revanced_settings_toolbar.xml", + "revanced_settings_with_toolbar.xml", + "revanced_settings_with_toolbar_layout.xml" + ), ResourceUtils.ResourceGroup( + // required resource for back button, because when the base APK is used, this resource will not exist + "drawable-xxxhdpi", "quantum_ic_arrow_back_white_24.png" + ), ResourceUtils.ResourceGroup( + // required resource for back button, because when the base APK is used, this resource will not exist + "drawable-ldrtl-xxxhdpi", "quantum_ic_arrow_back_white_24.png" + ) + ).forEach { resourceGroup -> + context.copyResources("settings", resourceGroup) + } + + preferencesEditor = context.xmlEditor["res/xml/settings_fragment.xml"] + + // Add the ReVanced settings to the YouTube settings + val youtubePackage = "com.google.android.youtube" + SettingsPatch.addPreference( + Preference( + StringResource("revanced_settings", "ReVanced"), + Preference.Intent( + youtubePackage, "revanced_settings", "com.google.android.libraries.social.licenses.LicenseActivity" + ), + StringResource("revanced_settings_summary", "ReVanced specific settings"), + ) + ) + + return PatchResultSuccess() + } + + + internal companion object { + // Used by a fingerprint of SettingsPatch + // this field is located in the SettingsResourcePatch + // because if it were to be defined in the SettingsPatch companion object, + // the companion object could be initialized before ResourceMappingResourcePatch has executed. + internal var appearanceStringId: Long = -1 + + // if this is not null, all intents will be renamed to this + var overrideIntentsTargetPackage: String? = null + + private var preferencesNode: Node? = null + + private var preferencesEditor: DomFileEditor? = null + set(value) { + field = value + preferencesNode = value.getNode("PreferenceScreen") + } + + /* Companion delegates */ + + /** + * Add a preference fragment to the main preferences. + * + * @param preference The preference to add. + */ + fun addPreference(preference: Preference) = + preferencesNode!!.addPreference(preference) { it.include() } + + /** + * Add a new string to the resources. + * + * @param identifier The key of the string. + * @param value The value of the string. + * @throws IllegalArgumentException if the string already exists. + */ + fun addString(identifier: String, value: String, formatted: Boolean) = + AbstractSettingsResourcePatch.addString(identifier, value, formatted) + + /** + * Add an array to the resources. + * + * @param arrayResource The array resource to add. + */ + fun addArray(arrayResource: ArrayResource) = AbstractSettingsResourcePatch.addArray(arrayResource) + + /** + * Add a preference to the settings. + * + * @param preferenceScreen The name of the preference screen. + */ + fun addPreferenceScreen(preferenceScreen: PreferenceScreen) = addPreference(preferenceScreen) + } + + override fun close() { + super.close() + + // rename the intent package names if it was set + overrideIntentsTargetPackage?.let { packageName -> + val preferences = preferencesEditor!!.getNode("PreferenceScreen").childNodes + for (i in 1 until preferences.length) { + val preferenceNode = preferences.item(i) + // preferences have a child node with the intent tag, skip over every other node + if (preferenceNode.childNodes.length == 0) continue + + val intentNode = preferenceNode.firstChild + + // if the node doesn't have a target package attribute, skip it + val targetPackageAttribute = intentNode.attributes.getNamedItem("android:targetPackage") ?: continue + + // do not replace intent target package if the package name is not from YouTube + val youtubePackage = "com.google.android.youtube" + if (targetPackageAttribute.nodeValue != youtubePackage) continue + + // replace the target package name + intentNode.attributes.setNamedItem(preferenceNode.ownerDocument.createAttribute("android:targetPackage") + .also { attribute -> + attribute.value = packageName + }) + } + } + + preferencesEditor?.close() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt new file mode 100644 index 0000000..e642800 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/annotation/VideoInformationCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.video.information.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class VideoInformationCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt new file mode 100644 index 0000000..13f82a9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/CreateVideoPlayerSeekbarFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.video.information.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object CreateVideoPlayerSeekbarFingerprint : MethodFingerprint( + "V", + strings = listOf("timed_markers_width") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt new file mode 100644 index 0000000..fe0355d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.misc.video.information.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +import org.jf.dexlib2.Opcode + +object PlayerControllerSetTimeReferenceFingerprint : MethodFingerprint( + opcodes = listOf(Opcode.INVOKE_DIRECT_RANGE, Opcode.IGET_OBJECT), + strings = listOf("Media progress reported outside media playback: ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt new file mode 100644 index 0000000..7e64855 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/PlayerInitFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.youtube.misc.video.information.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PlayerInitFingerprint : MethodFingerprint( + strings = listOf( + "playVideo called on player response with no videoStreamingData." + ), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt new file mode 100644 index 0000000..8f2ba96 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/SeekFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.video.information.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SeekFingerprint : MethodFingerprint( + strings = listOf("Attempting to seek during an ad") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt new file mode 100644 index 0000000..2d9e986 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoLengthFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.video.information.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +import org.jf.dexlib2.Opcode + +object VideoLengthFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.MOVE_RESULT_WIDE, + Opcode.CMP_LONG, + Opcode.IF_LEZ, + Opcode.IGET_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.GOTO, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.CONST_4, + Opcode.INVOKE_VIRTUAL + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt new file mode 100644 index 0000000..d04b753 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/fingerprints/VideoTimeFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.video.information.fingerprints + + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object VideoTimeFingerprint : MethodFingerprint( + strings = listOf("MedialibPlayerTimeInfo{currentPositionMillis=") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt new file mode 100644 index 0000000..4df446b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/information/patch/VideoInformationPatch.kt @@ -0,0 +1,178 @@ +package app.revanced.patches.youtube.misc.video.information.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.data.toMethodWalker +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 +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.video.information.annotation.VideoInformationCompatibility +import app.revanced.patches.youtube.misc.video.information.fingerprints.* +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("video-information") +@Description("Hooks YouTube to get information about the current playing video.") +@VideoInformationCompatibility +@Version("0.0.1") +@DependsOn([IntegrationsPatch::class]) +class VideoInformationPatch : BytecodePatch( + listOf( + PlayerInitFingerprint, + CreateVideoPlayerSeekbarFingerprint, + PlayerControllerSetTimeReferenceFingerprint, + VideoTimeFingerprint, + ) +) { + 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->setVideoLength(J)V" + ) + } + } + + /* + Set the video time method + */ + with(PlayerControllerSetTimeReferenceFingerprint.result!!) { + timeMethod = context.toMethodWalker(method) + .nextMethod(scanResult.patternScanResult!!.startIndex, true) + .getMethod() as MutableMethod + } + + /* + Set the high precision video time method + */ + highPrecisionTimeMethod = + (object : MethodFingerprint("V", null, listOf("J", "J", "J", "J", "I", "L"), null) {}).also { + it.resolve(context, VideoTimeFingerprint.result!!.classDef) + }.result!!.mutableMethod + + /* + Hook the methods which set the time + */ + highPrecisionTimeHook(INTEGRATIONS_CLASS_DESCRIPTOR, "setVideoTime") + + return PatchResultSuccess() + } + + companion object { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/VideoInformation;" + + private lateinit var playerInitMethod: MutableMethod + private lateinit var timeMethod: MutableMethod + private lateinit var highPrecisionTimeMethod: MutableMethod + + private fun MutableMethod.insert(insert: InsertIndex, register: String, descriptor: String) = + addInstruction(insert.index, "invoke-static { $register }, $descriptor") + + private fun MutableMethod.insertTimeHook(insert: InsertIndex, descriptor: String) = + insert(insert, "p1, p2", descriptor) + + /** + * Hook the player controller. + * + * @param targetMethodClass The descriptor for the class to invoke when the player controller is created. + * @param targetMethodName The name of the static method to invoke when the player controller is created. + */ + internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) = + playerInitMethod.insert( + InsertIndex.CREATE, + "v0", + "$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V" + ) + + /** + * Hook the video time. + * + * @param targetMethodClass The descriptor for the static method to invoke when the player controller is created. + * @param targetMethodName The name of the static method to invoke when the player controller is created. + */ + internal fun videoTimeHook(targetMethodClass: String, targetMethodName: String) = + timeMethod.insertTimeHook( + InsertIndex.TIME, + "$targetMethodClass->$targetMethodName(J)V" + ) + + /** + * Hook the high precision video time. + * + * @param targetMethodClass The descriptor for the static method to invoke when the player controller is created. + * @param targetMethodName The name of the static method to invoke when the player controller is created. + */ + internal fun highPrecisionTimeHook(targetMethodClass: String, targetMethodName: String) = + highPrecisionTimeMethod.insertTimeHook( + InsertIndex.HIGH_PRECISION_TIME, + "$targetMethodClass->$targetMethodName(J)V" + ) + + enum class InsertIndex(internal val index: Int) { + CREATE(4), + TIME(2), + HIGH_PRECISION_TIME(0), + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt new file mode 100644 index 0000000..147801e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/annotations/RememberVideoQualityCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.video.quality.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class RememberVideoQualityCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt new file mode 100644 index 0000000..3ea366f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualityReferenceFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.youtube.misc.video.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 VideoQualityReferenceFingerprint : MethodFingerprint( + "V", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf("L"), listOf( + Opcode.IPUT_OBJECT, Opcode.RETURN_VOID + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt new file mode 100644 index 0000000..db1853c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoQualitySetterFingerprint.kt @@ -0,0 +1,20 @@ + +package app.revanced.patches.youtube.misc.video.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 VideoQualitySetterFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("[L", "I", "I", "Z", "I"), + listOf( + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.IPUT_BOOLEAN, + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt new file mode 100644 index 0000000..2575d5d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/fingerprints/VideoUserQualityChangeFingerprint.kt @@ -0,0 +1,20 @@ + +package app.revanced.patches.youtube.misc.video.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 VideoUserQualityChangeFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("L","L","I","J"), + listOf( + Opcode.MOVE, + Opcode.MOVE_WIDE, + Opcode.INVOKE_INTERFACE_RANGE, + Opcode.RETURN_VOID + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt new file mode 100644 index 0000000..08dd672 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/quality/patch/RememberVideoQualityPatch.kt @@ -0,0 +1,88 @@ +package app.revanced.patches.youtube.misc.video.quality.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.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.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.video.quality.annotations.RememberVideoQualityCompatibility +import app.revanced.patches.youtube.misc.video.quality.fingerprints.VideoQualityReferenceFingerprint +import app.revanced.patches.youtube.misc.video.quality.fingerprints.VideoQualitySetterFingerprint +import app.revanced.patches.youtube.misc.video.quality.fingerprints.VideoUserQualityChangeFingerprint +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.FieldReference + +@Patch +@DependsOn([IntegrationsPatch::class, VideoIdPatch::class, SettingsPatch::class]) +@Name("remember-video-quality") +@Description("Adds the ability to remember the video quality you chose in the video quality flyout.") +@RememberVideoQualityCompatibility +@Version("0.0.1") +class RememberVideoQualityPatch : BytecodePatch( + listOf( + VideoQualitySetterFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + SwitchPreference( + "revanced_remember_video_quality_last_selected", + StringResource("revanced_remember_video_quality_last_selected_title", "Remember video quality changes"), + true, + StringResource( + "revanced_remember_video_quality_last_selected_summary_on", + "Quality changes apply to all videos" + ), + StringResource( + "revanced_remember_video_quality_last_selected_summary_off", + "Quality changes only apply to the current video and are reverted back to the last remembered quality for future playbacks" + ) + ) + ) + + val setterMethod = VideoQualitySetterFingerprint.result!! + + VideoUserQualityChangeFingerprint.resolve(context, setterMethod.classDef) + val userQualityMethod = VideoUserQualityChangeFingerprint.result!! + + VideoQualityReferenceFingerprint.resolve(context, setterMethod.classDef) + val qualityFieldReference = + VideoQualityReferenceFingerprint.result!!.method.let { method -> + (method.implementation!!.instructions.elementAt(0) as ReferenceInstruction).reference as FieldReference + } + + VideoIdPatch.injectCall("Lapp/revanced/integrations/patches/playback/quality/RememberVideoQualityPatch;->newVideoStarted(Ljava/lang/String;)V") + + val qIndexMethodName = + context.classes.single { it.type == qualityFieldReference.type }.methods.single { it.parameterTypes.first() == "I" }.name + + setterMethod.mutableMethod.addInstructions( + 0, + """ + iget-object v0, p0, ${setterMethod.classDef.type}->${qualityFieldReference.name}:${qualityFieldReference.type} + const-string v1, "$qIndexMethodName" + invoke-static {p1, p2, v0, v1}, Lapp/revanced/integrations/patches/playback/quality/RememberVideoQualityPatch;->setVideoQuality([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I + move-result p2 + """, + ) + + userQualityMethod.mutableMethod.addInstruction( + 0, + "invoke-static {p3}, Lapp/revanced/integrations/patches/playback/quality/RememberVideoQualityPatch;->userChangedQuality(I)V" + ) + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt new file mode 100644 index 0000000..bcecd7d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/annotations/CustomPlaybackSpeedCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.video.speed.custom.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CustomPlaybackSpeedCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt new file mode 100644 index 0000000..e4e3368 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedArrayGeneratorFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.misc.video.speed.custom.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@FuzzyPatternScanMethod(2) +object SpeedArrayGeneratorFingerprint : MethodFingerprint( + "[L", + AccessFlags.PUBLIC or AccessFlags.STATIC, + opcodes = listOf( + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.GOTO, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, + ), + strings = listOf("0.0#") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt new file mode 100644 index 0000000..4cb98cd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/SpeedLimiterFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.youtube.misc.video.speed.custom.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 SpeedLimiterFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("F"), + listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.CONST_HIGH16, + Opcode.GOTO, + Opcode.CONST_HIGH16, + Opcode.CONST_HIGH16, + Opcode.INVOKE_STATIC, + ), +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt new file mode 100644 index 0000000..541601f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/fingerprints/VideoSpeedPatchFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.youtube.misc.video.speed.custom.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import org.jf.dexlib2.Opcode + +object VideoSpeedPatchFingerprint : MethodFingerprint( + opcodes = listOf(Opcode.FILL_ARRAY_DATA), + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("CustomVideoSpeedPatch;") && methodDef.name == "" + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt new file mode 100644 index 0000000..1b76883 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/speed/custom/patch/CustomVideoSpeedPatch.kt @@ -0,0 +1,195 @@ +package app.revanced.patches.youtube.misc.video.speed.custom.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.addInstructions +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.patch.* +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.youtube.misc.video.speed.custom.annotations.CustomPlaybackSpeedCompatibility +import app.revanced.patches.youtube.misc.video.speed.custom.fingerprints.SpeedArrayGeneratorFingerprint +import app.revanced.patches.youtube.misc.video.speed.custom.fingerprints.SpeedLimiterFingerprint +import app.revanced.patches.youtube.misc.video.speed.custom.fingerprints.VideoSpeedPatchFingerprint +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.builder.instruction.BuilderArrayPayload +import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.MethodReference +import java.util.stream.DoubleStream +import kotlin.math.roundToInt + +@Patch +@Name("custom-video-speed") +@Description("Adds more video speed options.") +@DependsOn([IntegrationsPatch::class]) +@CustomPlaybackSpeedCompatibility +@Version("0.0.1") +class CustomVideoSpeedPatch : BytecodePatch( + listOf( + SpeedArrayGeneratorFingerprint, SpeedLimiterFingerprint, VideoSpeedPatchFingerprint + ) +) { + + override fun execute(context: BytecodeContext): PatchResult { + val speedLimitMin = minVideoSpeed!!.toFloat() + val speedLimitMax = maxVideoSpeed!!.toFloat().coerceAtLeast(speedLimitMin) + val speedsGranularity = videoSpeedsGranularity!!.toFloat() + + val arrayGenMethod = SpeedArrayGeneratorFingerprint.result?.mutableMethod!! + val arrayGenMethodImpl = arrayGenMethod.implementation!! + + val sizeCallIndex = arrayGenMethodImpl.instructions + .indexOfFirst { ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "size" } + + if (sizeCallIndex == -1) return PatchResultError("Couldn't find call to size()") + + val sizeCallResultRegister = + (arrayGenMethodImpl.instructions.elementAt(sizeCallIndex + 1) as OneRegisterInstruction).registerA + + arrayGenMethod.replaceInstruction( + sizeCallIndex + 1, + "const/4 v$sizeCallResultRegister, 0x0" + ) + + val (arrayLengthConstIndex, arrayLengthConst) = arrayGenMethodImpl.instructions.withIndex() + .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 7 } + + val arrayLengthConstDestination = (arrayLengthConst as OneRegisterInstruction).registerA + + val videoSpeedsArrayType = "Lapp/revanced/integrations/patches/playback/speed/CustomVideoSpeedPatch;->videoSpeeds:[F" + + arrayGenMethod.addInstructions( + arrayLengthConstIndex + 1, + """ + sget-object v$arrayLengthConstDestination, $videoSpeedsArrayType + array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination + """ + ) + + val (originalArrayFetchIndex, originalArrayFetch) = arrayGenMethodImpl.instructions.withIndex() + .first { + val reference = ((it.value as? ReferenceInstruction)?.reference as? FieldReference) + reference?.definingClass?.contains("PlayerConfigModel") ?: false && + reference?.type == "[F" + } + + val originalArrayFetchDestination = (originalArrayFetch as OneRegisterInstruction).registerA + + arrayGenMethod.replaceInstruction( + originalArrayFetchIndex, + "sget-object v$originalArrayFetchDestination, $videoSpeedsArrayType" + ) + + val limiterMethod = SpeedLimiterFingerprint.result?.mutableMethod!! + val limiterMethodImpl = limiterMethod.implementation!! + + val (limiterMinConstIndex, limiterMinConst) = limiterMethodImpl.instructions.withIndex() + .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 0.25f.toRawBits() } + val (limiterMaxConstIndex, limiterMaxConst) = limiterMethodImpl.instructions.withIndex() + .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 2.0f.toRawBits() } + + val limiterMinConstDestination = (limiterMinConst as OneRegisterInstruction).registerA + val limiterMaxConstDestination = (limiterMaxConst as OneRegisterInstruction).registerA + + fun hexFloat(float: Float): String = "0x%08x".format(float.toRawBits()) + + limiterMethod.replaceInstruction( + limiterMinConstIndex, + "const/high16 v$limiterMinConstDestination, ${hexFloat(speedLimitMin)}" + ) + limiterMethod.replaceInstruction( + limiterMaxConstIndex, + "const/high16 v$limiterMaxConstDestination, ${hexFloat(speedLimitMax)}" + ) + + val constructorResult = VideoSpeedPatchFingerprint.result!! + val constructor = constructorResult.mutableMethod + val implementation = constructor.implementation!! + + val stepsGranularity = 8F + val step = speedLimitMax + .minus(speedLimitMin) // calculate the range of the speeds + .div(speedsGranularity) + .times(stepsGranularity) + .roundToInt() + .div(stepsGranularity)// round to nearest multiple of stepsGranularity + .coerceAtLeast(1 / stepsGranularity) // ensure steps are at least 1/8th of the step granularity + + val videoSpeedsArray = buildList { + DoubleStream + .iterate(speedLimitMin.toDouble()) { it + step } // create a stream of speeds + .let { speedStream -> + for (speed in speedStream) { + if (speed > speedLimitMax) break + add(speed.toFloat().toRawBits()) + } + } + } + + // adjust the new array of speeds size + constructor.replaceInstruction( + 0, + "const/16 v0, ${videoSpeedsArray.size}" + ) + + // create the payload with the new speeds + val arrayPayloadIndex = implementation.instructions.size - 1 + implementation.replaceInstruction( + arrayPayloadIndex, + BuilderArrayPayload( + 4, + videoSpeedsArray + ) + ) + + return PatchResultSuccess() + } + + companion object : OptionsContainer() { + private fun String?.validate(max: Int? = null) = this?.toFloatOrNull() != null && + toFloat().let { float -> + float > 0 && max?.let { max -> float <= max } ?: true + } + + val videoSpeedsGranularity by option( + PatchOption.StringOption( + "granularity", + "16", + "Video speed granularity", + "The granularity of the video speeds. The higher the value, the more speeds will be available.", + true + ) { + it.validate() + } + ) + + val minVideoSpeed by option( + PatchOption.StringOption( + "min", + "0.25", + "Minimum video speed", + "The minimum video speed.", + true + ) { + it.validate() + } + ) + + val maxVideoSpeed by option( + PatchOption.StringOption( + "max", + "5.0", + "Maximum video speed", + "The maximum video speed. Must be greater than the minimum video speed and smaller than 5.", + true + ) { + it.validate(5) + } + ) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt new file mode 100644 index 0000000..8f7401f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/annotation/VideoIdCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.video.videoid.annotation + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class VideoIdCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt new file mode 100644 index 0000000..b688fb7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/fingerprint/VideoIdFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.misc.video.videoid.fingerprint + +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 VideoIdFingerprint : MethodFingerprint( + "V", + AccessFlags.DECLARED_SYNCHRONIZED or AccessFlags.FINAL or AccessFlags.PUBLIC, + listOf("L"), + listOf(Opcode.INVOKE_INTERFACE), + customFingerprint = { + it.definingClass.endsWith("PlaybackLifecycleMonitor;") + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt new file mode 100644 index 0000000..da21078 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/video/videoid/patch/VideoIdPatch.kt @@ -0,0 +1,58 @@ +package app.revanced.patches.youtube.misc.video.videoid.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.addInstructions +import app.revanced.patcher.extensions.instruction +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.patches.youtube.misc.integrations.patch.IntegrationsPatch +import app.revanced.patches.youtube.misc.video.videoid.annotation.VideoIdCompatibility +import app.revanced.patches.youtube.misc.video.videoid.fingerprint.VideoIdFingerprint +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Name("video-id-hook") +@Description("Hooks to detect when the video id changes") +@VideoIdCompatibility +@Version("0.0.1") +@DependsOn([IntegrationsPatch::class]) +class VideoIdPatch : BytecodePatch( + listOf( + VideoIdFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + with(VideoIdFingerprint.result!!) { + insertMethod = mutableMethod + insertIndex = scanResult.patternScanResult!!.endIndex + 2 + + videoIdRegister = (insertMethod.instruction(insertIndex - 1) as OneRegisterInstruction).registerA + } + + return PatchResultSuccess() + } + + companion object { + private var videoIdRegister = 0 + private var insertIndex = 0 + + private lateinit var insertMethod: MutableMethod + + /** + * Adds an invoke-static instruction, called with the new id when the video changes + * @param methodDescriptor which method to call. Params have to be `Ljava/lang/String;` + */ + fun injectCall( + methodDescriptor: String + ) = insertMethod.addInstructions( + insertIndex, // move-result-object offset + "invoke-static {v$videoIdRegister}, $methodDescriptor" + ) + } +} + diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt new file mode 100644 index 0000000..92f3ae0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/annotations/CustomVideoBufferCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.videobuffer.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.49.37") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CustomVideoBufferCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt new file mode 100644 index 0000000..9f194e9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/MaxBufferFingerprint.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.youtube.misc.videobuffer.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 +import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction + +object MaxBufferFingerprint : MethodFingerprint( + "I", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), + listOf(Opcode.SGET_OBJECT, Opcode.IGET, Opcode.IF_EQZ, Opcode.RETURN), + customFingerprint = { methodDef -> + methodDef.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" + && methodDef.implementation!!.instructions.any { + ((it as? NarrowLiteralInstruction)?.narrowLiteral == 120000) + && methodDef.name == "r" + } + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt new file mode 100644 index 0000000..b4de743 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/PlaybackBufferFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.misc.videobuffer.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 +import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction + +object PlaybackBufferFingerprint : MethodFingerprint( + "I", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), + listOf(Opcode.IF_LEZ, Opcode.RETURN), + customFingerprint = { methodDef -> + methodDef.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" + && methodDef.implementation!!.instructions.any { + ((it as? NarrowLiteralInstruction)?.narrowLiteral == 1600) + } + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt new file mode 100644 index 0000000..d9754dd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/fingerprints/ReBufferFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.misc.videobuffer.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 +import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction + +object ReBufferFingerprint : MethodFingerprint( + "I", AccessFlags.PUBLIC or AccessFlags.FINAL, listOf(), + listOf(Opcode.IF_LEZ, Opcode.RETURN), + customFingerprint = { methodDef -> + methodDef.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" + && methodDef.implementation!!.instructions.any { + ((it as? NarrowLiteralInstruction)?.narrowLiteral == 5000) + } + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt new file mode 100644 index 0000000..4562f93 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/videobuffer/patch/CustomVideoBufferPatch.kt @@ -0,0 +1,137 @@ +package app.revanced.patches.youtube.misc.videobuffer.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.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.InputType +import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.TextPreference +import app.revanced.patches.youtube.misc.videobuffer.annotations.CustomVideoBufferCompatibility +import app.revanced.patches.youtube.misc.videobuffer.fingerprints.MaxBufferFingerprint +import app.revanced.patches.youtube.misc.videobuffer.fingerprints.PlaybackBufferFingerprint +import app.revanced.patches.youtube.misc.videobuffer.fingerprints.ReBufferFingerprint +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction + +@Patch +@Name("custom-video-buffer") +@Description("Lets you change the buffers of videos.") +@DependsOn([SettingsPatch::class]) +@CustomVideoBufferCompatibility +@Version("0.0.1") +class CustomVideoBufferPatch : BytecodePatch( + listOf( + MaxBufferFingerprint, PlaybackBufferFingerprint, ReBufferFingerprint + ) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + PreferenceScreen( + "revanced_custom_video_buffer", + StringResource("revanced_custom_video_buffer_title", "Video buffer settings"), + listOf( + TextPreference( + "revanced_pref_max_buffer_ms", + StringResource("revanced_pref_max_buffer_ms_title", "Maximum buffer size"), + InputType.NUMBER, + "120000", + StringResource( + "revanced_pref_max_buffer_ms_summary", + "The maximum size of a buffer for playback" + ) + ), + TextPreference( + "revanced_pref_buffer_for_playback_ms", + StringResource("revanced_pref_buffer_for_playback_ms_title", "Maximum buffer for playback"), + InputType.NUMBER, + "2500", + StringResource( + "revanced_pref_buffer_for_playback_ms_summary", + "Maximum size of a buffer for playback" + ) + ), + TextPreference( + "revanced_pref_buffer_for_playback_after_rebuffer_ms", + StringResource( + "revanced_pref_buffer_for_playback_after_rebuffer_ms_title", + "Maximum buffer for playback after rebuffer" + ), + InputType.NUMBER, + "5000", + StringResource( + "revanced_pref_buffer_for_playback_after_rebuffer_ms_summary", + "Maximum size of a buffer for playback after rebuffering" + ) + ) + ), + StringResource("revanced_custom_video_buffer_summary", "Custom settings for video buffer") + ) + ) + + execMaxBuffer() + execPlaybackBuffer() + execReBuffer() + return PatchResultSuccess() + } + + private fun execMaxBuffer() { + val (method, result) = MaxBufferFingerprint.unwrap(true, -1) + val (index, register) = result + + method.addInstructions( + index + 1, """ + invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getMaxBuffer()I + move-result v$register + """ + ) + } + + private fun execPlaybackBuffer() { + val (method, result) = PlaybackBufferFingerprint.unwrap() + val (index, register) = result + + method.addInstructions( + index + 1, """ + invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getPlaybackBuffer()I + move-result v$register + """ + ) + } + + private fun execReBuffer() { + val (method, result) = ReBufferFingerprint.unwrap() + val (index, register) = result + + method.addInstructions( + index + 1, """ + invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getReBuffer()I + move-result v$register + """ + ) + } + + private fun MethodFingerprint.unwrap( + forEndIndex: Boolean = false, + offset: Int = 0 + ): Pair> { + val result = this.result!! + val method = result.mutableMethod + val scanResult = result.scanResult.patternScanResult!! + val index = (if (forEndIndex) scanResult.endIndex else scanResult.startIndex) + offset + + val register = (method.instruction(index) as OneRegisterInstruction).registerA + + return method to (index to register) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt new file mode 100644 index 0000000..86819cd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/annotations/ZoomHapticsCompatibility.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.misc.zoomhaptics.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility([Package("com.google.android.youtube")]) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class ZoomHapticsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt new file mode 100644 index 0000000..493db9c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/fingerprints/ZoomHapticsFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.youtube.misc.zoomhaptics.fingerprints +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object ZoomHapticsFingerprint : MethodFingerprint( + strings = listOf( + "Failed to haptics vibrate for video zoom" + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt new file mode 100644 index 0000000..89cccda --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/zoomhaptics/patch/ZoomHapticsPatch.kt @@ -0,0 +1,54 @@ +package app.revanced.patches.youtube.misc.zoomhaptics.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.addInstructions +import app.revanced.patcher.extensions.instruction +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.patcher.util.smali.ExternalLabel +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.shared.settings.preference.impl.SwitchPreference +import app.revanced.patches.youtube.misc.zoomhaptics.annotations.ZoomHapticsCompatibility +import app.revanced.patches.youtube.misc.zoomhaptics.fingerprints.ZoomHapticsFingerprint + +@Patch +@Name("disable-zoom-haptics") +@Description("Disables haptics when zooming.") +@DependsOn([SettingsPatch::class]) +@ZoomHapticsCompatibility +@Version("0.0.1") +class ZoomHapticsPatch : BytecodePatch( + listOf(ZoomHapticsFingerprint) +) { + override fun execute(context: BytecodeContext): PatchResult { + SettingsPatch.PreferenceScreen.MISC.addPreferences( + SwitchPreference( + "revanced_disable_zoom_haptics", + StringResource("revanced_disable_zoom_haptics_title", "Disable zoom haptics"), + true, + StringResource("revanced_disable_zoom_haptics_summary_on", "Haptics are disabled"), + StringResource("revanced_disable_zoom_haptics_summary_off", "Haptics are enabled") + ) + ) + + val zoomHapticsFingerprintMethod = ZoomHapticsFingerprint.result!!.mutableMethod + + zoomHapticsFingerprintMethod.addInstructions( + 0, """ + invoke-static { }, Lapp/revanced/integrations/patches/ZoomHapticsPatch;->shouldVibrate()Z + move-result v0 + if-nez v0, :vibrate + return-void + """, listOf(ExternalLabel("vibrate", zoomHapticsFingerprintMethod.instruction(0))) + ) + + return PatchResultSuccess() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/microg/Constants.kt b/src/main/kotlin/app/revanced/util/microg/Constants.kt new file mode 100644 index 0000000..912b975 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/Constants.kt @@ -0,0 +1,130 @@ +package app.revanced.util.microg + +/** + * constants for microG builds with signature spoofing + */ +internal object Constants { + /** + * microG vendor name + * aka. package prefix / package base + */ + const val MICROG_VENDOR = "com.mgoogle" + + /** + * meta-data for microG package name spoofing on patched builds + */ + const val META_SPOOFED_PACKAGE_NAME = "$MICROG_VENDOR.android.gms.SPOOFED_PACKAGE_NAME" + + /** + * meta-data for microG package signature spoofing on patched builds + */ + const val META_SPOOFED_PACKAGE_SIGNATURE = "$MICROG_VENDOR.android.gms.SPOOFED_PACKAGE_SIGNATURE" + + /** + * meta-data for microG package detection + */ + const val META_GMS_PACKAGE_NAME = "app.revanced.MICROG_PACKAGE_NAME" + + /** + * a list of all permissions in microG + */ + val PERMISSIONS = listOf( + // C2DM / GCM + "com.google.android.c2dm.permission.RECEIVE", + "com.google.android.c2dm.permission.SEND", + "com.google.android.gtalkservice.permission.GTALK_SERVICE", + + // GAuth + "com.google.android.googleapps.permission.GOOGLE_AUTH", + "com.google.android.googleapps.permission.GOOGLE_AUTH.cp", + "com.google.android.googleapps.permission.GOOGLE_AUTH.local", + "com.google.android.googleapps.permission.GOOGLE_AUTH.mail", + "com.google.android.googleapps.permission.GOOGLE_AUTH.writely", + ) + + /** + * a list of all (intent) actions in microG + */ + val ACTIONS = listOf( + // location + "com.google.android.gms.location.places.ui.PICK_PLACE", + "com.google.android.gms.location.places.GeoDataApi", + "com.google.android.gms.location.places.PlacesApi", + "com.google.android.gms.location.places.PlaceDetectionApi", + "com.google.android.gms.wearable.MESSAGE_RECEIVED", + + // C2DM / GCM + "com.google.android.c2dm.intent.REGISTER", + "com.google.android.c2dm.intent.REGISTRATION", + "com.google.android.c2dm.intent.UNREGISTER", + "com.google.android.c2dm.intent.RECEIVE", + "com.google.iid.TOKEN_REQUEST", + "com.google.android.gcm.intent.SEND", + + // car + "com.google.android.gms.car.service.START", + + // people + "com.google.android.gms.people.service.START", + + // wearable + "com.google.android.gms.wearable.BIND", + + // auth + "com.google.android.gsf.login", + "com.google.android.gsf.action.GET_GLS", + "com.google.android.gms.common.account.CHOOSE_ACCOUNT", + "com.google.android.gms.auth.login.LOGIN", + "com.google.android.gms.auth.api.credentials.PICKER", + "com.google.android.gms.auth.api.credentials.service.START", + "com.google.android.gms.auth.service.START", + "com.google.firebase.auth.api.gms.service.START", + "com.google.android.gms.auth.be.appcert.AppCertService", + + // fido + "com.google.android.gms.fido.fido2.privileged.START", + + // games + "com.google.android.gms.games.service.START", + "com.google.android.gms.games.PLAY_GAMES_UPGRADE", + + // chimera + "com.google.android.gms.chimera", + + // fonts + "com.google.android.gms.fonts", + + // phenotype + "com.google.android.gms.phenotype.service.START", + + // location + "com.google.android.gms.location.reporting.service.START", + + // misc + "com.google.android.gms.gmscompliance.service.START", + "com.google.android.gms.oss.licenses.service.START", + "com.google.android.gms.safetynet.service.START", + "com.google.android.gms.tapandpay.service.BIND" + ) + + /** + * a list of all content provider authorities in microG + */ + val AUTHORITIES = listOf( + // gsf + "com.google.android.gsf.gservices", + "com.google.settings", + + // auth + "com.google.android.gms.auth.accounts", + + // chimera + "com.google.android.gms.chimera", + + // fonts + "com.google.android.gms.fonts", + + // phenotype + "com.google.android.gms.phenotype" + ) +} diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt new file mode 100644 index 0000000..b4b4038 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/MicroGBytecodeHelper.kt @@ -0,0 +1,241 @@ +package app.revanced.util.microg + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.addInstruction +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.util.proxy.mutableTypes.MutableClass +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.util.microg.Constants.ACTIONS +import app.revanced.util.microg.Constants.AUTHORITIES +import app.revanced.util.microg.Constants.MICROG_VENDOR +import app.revanced.util.microg.Constants.PERMISSIONS +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.instruction.BuilderInstruction21c +import org.jf.dexlib2.iface.instruction.formats.Instruction21c +import org.jf.dexlib2.iface.reference.StringReference +import org.jf.dexlib2.immutable.reference.ImmutableStringReference + +/** + * Helper class for applying bytecode patches needed for the microg-support patches. + */ +internal object MicroGBytecodeHelper { + /** + * Hook a method to check the availability of MicroG. + * + * @param fingerprint The fingerprint of the method to add the call for the notice in. + */ + fun injectNotice(fingerprint: MethodFingerprint) { + fingerprint.result!!.mutableMethod.addInstruction( + 0, + "invoke-static {}, Lapp/revanced/integrations/patches/MicroGSupport;->checkAvailability()V" + ) + } + + /** + * Transform strings with package name out of [fromPackageName] and [toPackageName]. + * + * @param fromPackageName Original package name. + * @param toPackageName The package name to accept. + **/ + fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? { + return { referencedString -> + when (referencedString) { + "$fromPackageName.SuggestionsProvider", + "$fromPackageName.fileprovider" -> referencedString.replace(fromPackageName, toPackageName) + else -> null + } + } + } + + /** + * Prime method data class for the [MicroGBytecodeHelper] class. + * + * @param primeMethodFingerprint The prime methods [MethodFingerprint]. + * @param fromPackageName Original package name. + * @param toPackageName The package name to accept. + **/ + data class PrimeMethodTransformationData( + val primeMethodFingerprint: MethodFingerprint, + val fromPackageName: String, + val toPackageName: String + ) { + /** + * Patch the prime method to accept the new package name. + */ + fun transformPrimeMethodPackageName() { + val primeMethod = primeMethodFingerprint.result!!.mutableMethod + val implementation = primeMethod.implementation!! + + var register = 2 + val index = implementation.instructions.indexOfFirst { + if (it.opcode != Opcode.CONST_STRING) return@indexOfFirst false + + val instructionString = ((it as Instruction21c).reference as StringReference).string + if (instructionString != fromPackageName) return@indexOfFirst false + + register = it.registerA + return@indexOfFirst true + } + + primeMethod.replaceInstruction( + index, "const-string v$register, \"$toPackageName\"" + ) + } + } + + /** + * Patch the bytecode to work with MicroG. + * Note: this only handles string constants to gms (intent actions, authorities, ...). + * If the app employs additional checks to validate the installed gms package, you'll have to handle those in the app- specific patch + * + * @param context The context. + * @param additionalStringTransforms Additional transformations applied to all const-string references. + * @param primeMethodTransformationData Data to patch the prime method. + * @param earlyReturns List of [MethodFingerprint] to return the resolved methods early. + */ + fun patchBytecode( + context: BytecodeContext, + additionalStringTransforms: Array<(str: String) -> String?>, + primeMethodTransformationData: PrimeMethodTransformationData, + earlyReturns: List + ) { + earlyReturns.returnEarly() + primeMethodTransformationData.transformPrimeMethodPackageName() + + val allTransforms = arrayOf( + MicroGBytecodeHelper::commonTransform, + MicroGBytecodeHelper::contentUrisTransform, + *additionalStringTransforms + ) + + // transform all strings using all provided transforms, first match wins + context.transformStringReferences transform@{ + for (transformFn in allTransforms) { + val s = transformFn(it) + if (s != null) return@transform s + } + + return@transform null + } + } + + /** + * const-string transform function for common gms string references. + * + * @param referencedString The string to transform. + */ + private fun commonTransform(referencedString: String): String? = + when (referencedString) { + "com.google", + "com.google.android.gms", + in PERMISSIONS, + in ACTIONS, + in AUTHORITIES -> referencedString.replace("com.google", MICROG_VENDOR) + + // subscribedfeeds has no vendor prefix for whatever reason... + "subscribedfeeds" -> "${MICROG_VENDOR}.subscribedfeeds" + else -> null + } + + + /** + * const-string transform function for strings containing gms content uris / authorities. + */ + private fun contentUrisTransform(str: String): String? { + // only when content:// uri + if (str.startsWith("content://")) { + // check if matches any authority + for (authority in AUTHORITIES) { + val uriPrefix = "content://$authority" + if (str.startsWith(uriPrefix)) { + return str.replace( + uriPrefix, + "content://${authority.replace("com.google", MICROG_VENDOR)}" + ) + } + } + + // gms also has a 'subscribedfeeds' authority, check for that one too + val subFeedsUriPrefix = "content://subscribedfeeds" + if (str.startsWith(subFeedsUriPrefix)) { + return str.replace(subFeedsUriPrefix, "content://${MICROG_VENDOR}.subscribedfeeds") + } + } + + return null + } + + /** + * Transform all constant string references using a transformation function. + * + * @param transformFn string transformation function. if null, string is not changed. + */ + private fun BytecodeContext.transformStringReferences(transformFn: (str: String) -> String?) { + classes.forEach { classDef -> + var mutableClass: MutableClass? = null + + // enumerate all methods + classDef.methods.forEach classLoop@{ methodDef -> + var mutableMethod: MutableMethod? = null + val implementation = methodDef.implementation ?: return@classLoop + + // enumerate all instructions and find const-string + implementation.instructions.forEachIndexed implLoop@{ index, instruction -> + // skip all that are not const-string + if (instruction.opcode != Opcode.CONST_STRING) return@implLoop + val str = ((instruction as Instruction21c).reference as StringReference).string + + // call transform function + val transformedStr = transformFn(str) + if (transformedStr != null) { + // make class and method mutable, if not already + mutableClass = mutableClass ?: proxy(classDef).mutableClass + mutableMethod = mutableMethod ?: mutableClass!!.methods.first { + it.name == methodDef.name && it.parameterTypes.containsAll(methodDef.parameterTypes) + } + + // replace instruction with updated string + mutableMethod!!.implementation!!.replaceInstruction( + index, + BuilderInstruction21c( + Opcode.CONST_STRING, + instruction.registerA, + ImmutableStringReference( + transformedStr + ) + ) + ) + } + } + } + } + } + + /** + * Return the resolved methods of a list of [MethodFingerprint] early. + */ + private fun List.returnEarly() { + this.forEach { fingerprint -> + val result = fingerprint.result!! + val stringInstructions = when (result.method.returnType.first()) { + 'L' -> """ + const/4 v0, 0x0 + return-object v0 + """ + + 'V' -> "return-void" + 'I' -> """ + const/4 v0, 0x0 + return v0 + """ + + else -> throw Exception("This case should never happen.") + } + result.mutableMethod.addInstructions( + 0, stringInstructions + ) + } + } +} diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt new file mode 100644 index 0000000..003fa00 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/MicroGManifestHelper.kt @@ -0,0 +1,58 @@ +package app.revanced.util.microg + +import app.revanced.patcher.data.ResourceContext +import app.revanced.util.microg.Constants.META_GMS_PACKAGE_NAME +import app.revanced.util.microg.Constants.META_SPOOFED_PACKAGE_NAME +import app.revanced.util.microg.Constants.META_SPOOFED_PACKAGE_SIGNATURE +import app.revanced.util.microg.Constants.MICROG_VENDOR +import org.w3c.dom.Element +import org.w3c.dom.Node + +/** + * helper class for adding manifest metadata needed for microG builds with signature spoofing + */ +internal object MicroGManifestHelper { + + /** + * Add manifest entries needed for package and signature spoofing when using MicroG. + * Note: this only adds metadata entries for signature spoofing, other changes may still be required to make a microG patch work. + * + * @param context Resource context. + * @param spoofedPackage The package to spoof. + * @param spoofedSignature The signature to spoof. + */ + fun addSpoofingMetadata( + context: ResourceContext, + spoofedPackage: String, + spoofedSignature: String + ) { + context.xmlEditor["AndroidManifest.xml"].use { + val applicationNode = it + .file + .getElementsByTagName("application") + .item(0) + + // package spoofing + applicationNode.adoptChild("meta-data") { + setAttribute("android:name", META_SPOOFED_PACKAGE_NAME) + setAttribute("android:value", spoofedPackage) + } + applicationNode.adoptChild("meta-data") { + setAttribute("android:name", META_SPOOFED_PACKAGE_SIGNATURE) + setAttribute("android:value", spoofedSignature) + } + + // microG presence detection in integrations + applicationNode.adoptChild("meta-data") { + setAttribute("android:name", META_GMS_PACKAGE_NAME) + setAttribute("android:value", "${MICROG_VENDOR}.android.gms") + } + } + } + + private fun Node.adoptChild(tagName: String, block: Element.() -> Unit) { + val child = ownerDocument.createElement(tagName) + child.block() + appendChild(child) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt b/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt new file mode 100644 index 0000000..348e145 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/microg/MicroGResourceHelper.kt @@ -0,0 +1,59 @@ +package app.revanced.util.microg + +import app.revanced.patcher.data.ResourceContext +import app.revanced.util.resources.ResourceUtils.mergeStrings + +/** + * Helper class for applying resource patches needed for the microg-support patches. + */ +internal object MicroGResourceHelper { + + /** + * Add necessary strings to the strings.xml file. + * + * @param context The resource context. + * @param stringsHost The file which hosts the strings. + */ + fun addStrings(context: ResourceContext, stringsHost: String = "microg/host/values/strings.xml") = context.mergeStrings(stringsHost) + + /** + * Patch the manifest to work with MicroG. + * + * @param context The resource context. + * @param fromPackageName The original package name. + * @param toPackageName The package name to accept. + * @param toName The new name of the app. + */ + fun patchManifest( + context: ResourceContext, + fromPackageName: String, + toPackageName: String, + toName: String + ) { + val manifest = context["AndroidManifest.xml"].readText() + context["AndroidManifest.xml"].writeText( + manifest.replace( + "package=\"$fromPackageName", + "package=\"$toPackageName" + ).replace( + "android:label=\"@string/app_name", + "android:label=\"$toName" + ).replace( + "android:label=\"@string/app_launcher_name", + "android:label=\"$toName" + ).replace( + "android:authorities=\"$fromPackageName", + "android:authorities=\"$toPackageName" + ).replace( + "$fromPackageName.permission.C2D_MESSAGE", + "$toPackageName.permission.C2D_MESSAGE" + ).replace( + "com.google.android.c2dm", + "${Constants.MICROG_VENDOR}.android.c2dm" + ).replace( + "", + "" + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt b/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt new file mode 100644 index 0000000..be6a774 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/resources/ResourceUtils.kt @@ -0,0 +1,99 @@ +package app.revanced.util.resources + +import app.revanced.patcher.data.DomFileEditor +import app.revanced.patcher.data.ResourceContext +import app.revanced.patches.shared.settings.preference.impl.StringResource +import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch +import org.w3c.dom.Node +import java.nio.file.Files +import java.nio.file.StandardCopyOption + +internal object ResourceUtils { + + /** + * Merge strings. This manages [StringResource]s automatically. + * @param host The hosting xml resource. Needs to be a valid strings.xml resource. + */ + internal fun ResourceContext.mergeStrings(host: String) { + this.iterateXmlNodeChildren(host, "resources") { + // TODO: figure out why this is needed + if (!it.hasAttributes()) return@iterateXmlNodeChildren + + val attributes = it.attributes + val key = attributes.getNamedItem("name")!!.nodeValue!! + val value = it.textContent!! + + val formatted = attributes.getNamedItem("formatted") == null + + SettingsPatch.addString(key, value, formatted) + } + } + + /** + * Copy resources from the current class loader to the resource directory. + * @param sourceResourceDirectory The source resource directory name. + * @param resources The resources to copy. + */ + internal fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resources: ResourceGroup) { + val classLoader = ResourceUtils.javaClass.classLoader + val targetResourceDirectory = this["res"] + + for (resourceGroup in resources) { + resourceGroup.resources.forEach { resource -> + val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource" + Files.copy( + classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile")!!, + targetResourceDirectory.resolve(resourceFile).toPath(), StandardCopyOption.REPLACE_EXISTING + ) + } + } + } + + /** + * Resource names mapped to their corresponding resource data. + * @param resourceDirectoryName The name of the directory of the resource. + * @param resources A list of resource names. + */ + internal class ResourceGroup(val resourceDirectoryName: String, vararg val resources: String) + + /** + * Iterate through the children of a node by its tag. + * @param resource The xml resource. + * @param targetTag The target xml node. + * @param callback The callback to call when iterating over the nodes. + */ + internal fun ResourceContext.iterateXmlNodeChildren( + resource: String, + targetTag: String, + callback: (node: Node) -> Unit + ) = + xmlEditor[ResourceUtils.javaClass.classLoader.getResourceAsStream(resource)!!].use { + val stringsNode = it.file.getElementsByTagName(targetTag).item(0).childNodes + for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i)) + } + + + /** + * Copies the specified node of the source [DomFileEditor] to the target [DomFileEditor]. + * @param source the source [DomFileEditor]. + * @param target the target [DomFileEditor]- + * @return AutoCloseable that closes the target [DomFileEditor]s. + */ + fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable { + val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes + + val destinationResourceFile = target.file + val destinationNode = destinationResourceFile.getElementsByTagName(this).item(0) + + for (index in 0 until hostNodes.length) { + val node = hostNodes.item(index).cloneNode(true) + destinationResourceFile.adoptNode(node) + destinationNode.appendChild(node) + } + + return AutoCloseable { + source.close() + target.close() + } + } +} \ No newline at end of file diff --git a/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_background_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..e2dc8a53f7a24daf917c9ba172fe8ed23aa01881 GIT binary patch literal 3044 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 F0svGsm-GMt literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-hdpi/adaptiveproduct_youtube_foreground_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..e5a73b561046813b12ad77b27f4af7b3ec0b7bca GIT binary patch literal 5129 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-hdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dc967b7703117197f110db919bb390ea5d40cbb6 GIT binary patch literal 4571 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_background_color_108.png b/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_background_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..4dee1752a39cdc6a0cf032128945586b6720a2d6 GIT binary patch literal 2937 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-mdpi/adaptiveproduct_youtube_foreground_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..3d26f5b2b3f61baabc12a4b7e02b04623c408442 GIT binary patch literal 4366 zcmeAS@N?(olHy`uVBq!ia0y~yV8{Vs4mJh`hW@nhvkVLjoCO|{#S9EAejv=~)IIqR z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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�PV*|l=S%D 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�PV*|l=S%D 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-xhdpi/adaptiveproduct_youtube_foreground_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..4f66f60a390fdd50e21de67c28f54cf93571abf0 GIT binary patch literal 6199 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 Kb6Mw<&;$U@=_>dD literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-xxhdpi/adaptiveproduct_youtube_foreground_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..777daea51e45da543dba2fa1e951dfe34fdc6d0d GIT binary patch literal 7429 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-xxhdpi/ic_launcher.png b/src/main/resources/branding/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2178566d4ff9f8c2f9512b6bb85d9bcac3ab27 GIT binary patch literal 6719 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 E0JlHujQ{`u literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_foreground_color_108.png b/src/main/resources/branding/mipmap-xxxhdpi/adaptiveproduct_youtube_foreground_color_108.png new file mode 100644 index 0000000000000000000000000000000000000000..0abe542a1ad774189569a9098e32b1a24ca9da64 GIT binary patch literal 7945 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 T48Ir{7#KWV{an^LB{Ts5N~lsp literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher.png b/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..c0505bf87c101680afb0ebf7abba0fb0a38a7da6 GIT binary patch literal 8086 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher_round.png b/src/main/resources/branding/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..c0505bf87c101680afb0ebf7abba0fb0a38a7da6 GIT binary patch literal 8086 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/downloads/drawable/revanced_yt_download_button.xml b/src/main/resources/downloads/drawable/revanced_yt_download_button.xml new file mode 100644 index 0000000..69ab699 --- /dev/null +++ b/src/main/resources/downloads/drawable/revanced_yt_download_button.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml b/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml new file mode 100644 index 0000000..2325478 --- /dev/null +++ b/src/main/resources/downloads/host/layout/youtube_controls_bottom_ui_container.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/downloads/host/values/strings.xml b/src/main/resources/downloads/host/values/strings.xml new file mode 100644 index 0000000..957d490 --- /dev/null +++ b/src/main/resources/downloads/host/values/strings.xml @@ -0,0 +1,4 @@ + + + is not installed. Please install it. + diff --git a/src/main/resources/microg/host/values/strings.xml b/src/main/resources/microg/host/values/strings.xml new file mode 100644 index 0000000..c5c1f69 --- /dev/null +++ b/src/main/resources/microg/host/values/strings.xml @@ -0,0 +1,4 @@ + + + Vanced MicroG is not installed. Please install it. + diff --git a/src/main/resources/returnyoutubedislike/host/values/strings.xml b/src/main/resources/returnyoutubedislike/host/values/strings.xml new file mode 100644 index 0000000..314f5dc --- /dev/null +++ b/src/main/resources/returnyoutubedislike/host/values/strings.xml @@ -0,0 +1,12 @@ + + Return YouTube Dislike + Dislikes are shown + Dislikes are not shown + + Dislikes as percentage + Dislikes shown as percentage + Dislikes shown as number + + ReturnYouTubeDislike.com + Dislike data is provided by the Return YouTube Dislike API. Tap here to learn more. + \ No newline at end of file diff --git a/src/main/resources/settings/drawable-ldrtl-xxxhdpi/quantum_ic_arrow_back_white_24.png b/src/main/resources/settings/drawable-ldrtl-xxxhdpi/quantum_ic_arrow_back_white_24.png new file mode 100644 index 0000000000000000000000000000000000000000..d409b544b7f62950a69d7d1ea58e97ef6c5ea546 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CW^n1EEhEy=Vy>(IWAcF); zB5OvFdVM~Z^#jRC2hP?+A6%!>S+M&&0|?CY+5WHf<+c3=cYld5i{7uR*dFu3_k^80 zr;v)r1O>+?78s+Y^hvl+^?@)>&q}9DvlI?!n&d5>cl684%gg=e+tvPpF#gH@a;rDp m?0q==@?-VK`JbynR#-6BO<9^PT)eUc#Pf9Zb6Mw<&;$V5988D+ literal 0 HcmV?d00001 diff --git a/src/main/resources/settings/drawable-xxxhdpi/quantum_ic_arrow_back_white_24.png b/src/main/resources/settings/drawable-xxxhdpi/quantum_ic_arrow_back_white_24.png new file mode 100644 index 0000000000000000000000000000000000000000..e27034d67874687a900f0f960c662e94cd633e2a GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWbb7ithEy=Vy~Qcm>>%J0 z$dXVrx5vDuu!r^Cx+?#J?_*k0)aNlWFyw3vEvsAo=1*B+ literal 0 HcmV?d00001 diff --git a/src/main/resources/settings/layout/revanced_settings_toolbar.xml b/src/main/resources/settings/layout/revanced_settings_toolbar.xml new file mode 100644 index 0000000..86c7177 --- /dev/null +++ b/src/main/resources/settings/layout/revanced_settings_toolbar.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/settings/layout/revanced_settings_with_toolbar.xml b/src/main/resources/settings/layout/revanced_settings_with_toolbar.xml new file mode 100644 index 0000000..21ff597 --- /dev/null +++ b/src/main/resources/settings/layout/revanced_settings_with_toolbar.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml b/src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml new file mode 100644 index 0000000..31b20d9 --- /dev/null +++ b/src/main/resources/settings/layout/revanced_settings_with_toolbar_layout.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/settings/xml/revanced_prefs.xml b/src/main/resources/settings/xml/revanced_prefs.xml new file mode 100644 index 0000000..9562c00 --- /dev/null +++ b/src/main/resources/settings/xml/revanced_prefs.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/main/resources/sponsorblock/drawable-xxxhdpi/quantum_ic_skip_next_white_24.png b/src/main/resources/sponsorblock/drawable-xxxhdpi/quantum_ic_skip_next_white_24.png new file mode 100644 index 0000000000000000000000000000000000000000..19c4929cca13b462dee093c2156ca904e52c4fad GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CW>Ug?1hEy=Vy=AD!n#kb# zu+wGXCC#l@zL+JjhaKR$!6+?Zy`gDi!$yX$*_x%ZGSw!o-qYFD`Ap^cAMOeI5BL5) zFL4G2>VoGL&3Gt({(Rg1xw`frY{hdNK22?ZGnvuz)2v`Kg~%dj)-BIvHZq=c*IU`} zZ3eTM!p*01cs;J&N$uy%(6>xuo^(FOk#TJ{C)bOpCZ=7}HZ-hL)mf0u$@3!W5Yw({ z2T-{nS%U`t87KEUW#^r&KeBtyM0+>)PvVj_UOrC@w?COxyUPe>XM%@5(&1@5$>+j7+;qCN${Dsx0ts=W_VFoA&{SfC57u r3x@!MJrfIufPzB<10&Oc=h8Oks&u}*+`+@Zz`)??>gTe~DWM4fK021Z literal 0 HcmV?d00001 diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml b/src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml new file mode 100644 index 0000000..76a4b8b --- /dev/null +++ b/src/main/resources/sponsorblock/drawable/ic_sb_adjust.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_compare.xml b/src/main/resources/sponsorblock/drawable/ic_sb_compare.xml new file mode 100644 index 0000000..04cc65e --- /dev/null +++ b/src/main/resources/sponsorblock/drawable/ic_sb_compare.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_edit.xml b/src/main/resources/sponsorblock/drawable/ic_sb_edit.xml new file mode 100644 index 0000000..e93574b --- /dev/null +++ b/src/main/resources/sponsorblock/drawable/ic_sb_edit.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_logo.xml b/src/main/resources/sponsorblock/drawable/ic_sb_logo.xml new file mode 100644 index 0000000..c39b9e0 --- /dev/null +++ b/src/main/resources/sponsorblock/drawable/ic_sb_logo.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_publish.xml b/src/main/resources/sponsorblock/drawable/ic_sb_publish.xml new file mode 100644 index 0000000..de4e58d --- /dev/null +++ b/src/main/resources/sponsorblock/drawable/ic_sb_publish.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/resources/sponsorblock/drawable/ic_sb_voting.xml b/src/main/resources/sponsorblock/drawable/ic_sb_voting.xml new file mode 100644 index 0000000..97db9c9 --- /dev/null +++ b/src/main/resources/sponsorblock/drawable/ic_sb_voting.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml b/src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml new file mode 100644 index 0000000..9779626 --- /dev/null +++ b/src/main/resources/sponsorblock/host/layout/youtube_controls_layout.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/sponsorblock/host/values/strings.xml b/src/main/resources/sponsorblock/host/values/strings.xml new file mode 100644 index 0000000..2f48383 --- /dev/null +++ b/src/main/resources/sponsorblock/host/values/strings.xml @@ -0,0 +1,142 @@ + + + Enable SponsorBlock + SponsorBlock is a crowd-sourced system for skipping annoying parts in YouTube videos + Enable new segment adding + Switch this on to enable experimental segment adding (has button visibility issues) + What to do with different segments + General + Show a toast when skipping segment automatically + Click to see an example toast + Skip count tracking + This lets SponsorBlock leaderboard system know how much time people have saved. The extension sends a message to the server each time you skip a segment + Adjusting new segment step + This is the number of milliseconds you can move when you use the time adjustment buttons while adding new segment + Minimum segment duration + Segments shorter than the set value (in seconds) will not be skipped or shown in the player + Your private user id + This should be kept private. This is like a password and should not be shared with anyone. If someone has this, they can impersonate you + Import/Export settings + This is your entire configuration that is applicable in the desktop extension in JSON. This includes your Private userID, so be sure to share this wisely. + Change API URL + The address SponsorBlock uses to make calls to the server. <b>Don\'t change this unless you know what you\'re doing.</b> + Settings were successfully imported + Failed to import settings + Failed to export settings + Cache segments locally + Frequently watched videos (eg. music videos) may store segments in app cache to make skipping segments faster + Clear SponsorBlock segments cache + Sponsor + Paid promotion, paid referrals and direct advertisements + Intermission/Intro Animation + An interval without actual content. Could be a pause, static frame, repeating animation + Endcards/Credits + Credits or when the YouTube endcards appear. Not for spoken conclusions + Interaction Reminder (Subscribe) + When there is a short reminder to like, subscribe, follow or interact with them on any free or paid platform + Unpaid/Self Promotion + When there is unpaid or self promotion. This includes specific sections about merchandise, donations, or information about who they collaborated with + Music: Non-Music Section + Only for use in music videos. Skips parts of the video not in official mixes + Filler Tangent/Jokes + Tangential scenes added only for filler or humor that are not required to understand the main content of the video. This should not include context or background details + Skipped a sponsor segment + Skipped sponsor + Skipped intro + Skipped outro + Skipped annoying reminder + Skipped self promotion + Skipped a non-music section + Skipped preview + Skipped filler + Skipped unsubmitted segment + Skip automatically + Skip automatically once + Show a skip button + Don\'t do anything + Skip segment + About + This app uses the API from SponsorBlock + Tap to learn more, and see downloads for other platforms at: sponsor.ajay.app + Integration made by JakubWeg, recoded by oSumAtrIX + Tap to skip + + Can\'t submit the segment: %s + Unable to submit segments: Status: %d %s + Can\'t submit the segment.\nRate Limited (Too many from the same user or IP) + Can\'t submit the segment.\n\n%s + Can\'t submit the segment.\nAlready exists + Segment submitted successfully + Submitting segment… + + Unable to vote for segment: Status: %d %s + Can\'t vote for segment.\nRate Limited (Too many from the same user or IP) + Can\'t vote for segment.\n\n%s + Voted successfully + Voting for segment… + Upvote + Downvote + Change category + There are no segments to vote for + Enable voting + Switch this on to enable voting. + + Choose the segment category + You\'ve disabled this category in the settings, enable it to be able to submit + New SponsorBlock segment + Set %02d:%02d:%04d as the start or end of a new segment? + start + end + now + Time the segment begins at + Time the segment ends at + Beginning of segment set + End of segment set + Are the times correct? + The segment lasts from %02d:%02d to %02d:%02d (%d minutes %02d seconds)\nIs it ready to submit? + Mark two locations on the time bar first + Edit timing of segment manually + Do you want to edit the timing for the start or end of the segment? + Done + Invalid time given + + View guidelines + Guidelines contain tips and rules about submitting segments + There are guidelines + It\'s recommended to read the SponsorBlock guidelines before submitting any segment + Already read + Show me + + Show time without segments + This time appears in brackets next to the current time. This shows the total video duration minus any segments. + Preview/Recap + Recap of previous episodes, or a preview of what\'s coming up later in the current video or future videos in the same series. Clips should not provide additional information. + Stats + Loading.. + SponsorBlock is disabled + Your username: <b>%s</b> + Click to change your username + Unable to change username: Status: %d %s + Username successfully changed + Submissions: <b>%s</b> + You\'ve saved people from <b>%s</b> segments. + That\'s <b>%s</b> of their lives. Click to see the leaderboard + You\'ve skipped <b>%s</b> segments. + That\'s <b>%s</b>. + minutes + Are you looking for changing colors? + You can now change a category\'s color by clicking on it above. + Choose the category + Color changed + Color reset + Invalid hex code + Change + Reset + + Segments + SB Browser + + API URL changed + API URL reset + Provided API URL is invalid + diff --git a/src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml b/src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml new file mode 100644 index 0000000..6bc670f --- /dev/null +++ b/src/main/resources/sponsorblock/layout/inline_sponsor_overlay.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/sponsorblock/layout/new_segment.xml b/src/main/resources/sponsorblock/layout/new_segment.xml new file mode 100644 index 0000000..155907c --- /dev/null +++ b/src/main/resources/sponsorblock/layout/new_segment.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/sponsorblock/layout/skip_sponsor_button.xml b/src/main/resources/sponsorblock/layout/skip_sponsor_button.xml new file mode 100644 index 0000000..3ceb8c3 --- /dev/null +++ b/src/main/resources/sponsorblock/layout/skip_sponsor_button.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml new file mode 100644 index 0000000..469b335 --- /dev/null +++ b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_auto.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml new file mode 100644 index 0000000..2f6c707 --- /dev/null +++ b/src/main/resources/swipecontrols/drawable/ic_sc_brightness_manual.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml b/src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml new file mode 100644 index 0000000..73dc595 --- /dev/null +++ b/src/main/resources/swipecontrols/drawable/ic_sc_volume_mute.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml b/src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml new file mode 100644 index 0000000..30dff4b --- /dev/null +++ b/src/main/resources/swipecontrols/drawable/ic_sc_volume_normal.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/main/resources/theme/values-night-v31/styles.xml b/src/main/resources/theme/values-night-v31/styles.xml new file mode 100644 index 0000000..53da895 --- /dev/null +++ b/src/main/resources/theme/values-night-v31/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/src/main/resources/twitch/settings/xml/revanced_prefs.xml b/src/main/resources/twitch/settings/xml/revanced_prefs.xml new file mode 100644 index 0000000..14e1c85 --- /dev/null +++ b/src/main/resources/twitch/settings/xml/revanced_prefs.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file