Compare commits

..
4 Commits
Author SHA1 Message Date
whitehai11 937729130f feat: Full build successful + Spotify premium patch structure
Release / Release (push) Failing after 17m36s
2026-09-03 23:49:46 +02:00
whitehai11 b73ca08003 feat(Spotify): Add Premium spoof patch
Release / Release (push) Failing after 16m42s
2026-09-03 23:22:54 +02:00
whitehai11andClaude Sonnet 5 42622a1bca feat(Spotify): Add Remove skip restrictions patch
Adds an opt-in patch that forces the local Restrictions.disallowSkippingNextReasons()
getter (read by every skip button in the app) to always report an empty
set, and forces the rapid-skip cooldown tracker to always behave like it
does after the first-ever skip. This removes the free-tier "6 skips per
hour" cap and its toasts, and as a side effect also unlocks the skip
button while an ad is playing, since it reads the same restriction.

This is purely a client-side behavior change: it does not report a
Premium account to Spotify's backend, and does not block or mute ad
audio itself.

Verified against a real Spotify 9.1.78.2215 APK via revanced-cli: both
fingerprints resolve and the patch applies cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NXRsEcWGqDhsCbfEdrfCdE
2026-09-03 20:50:51 +02:00
whitehai11andClaude Sonnet 5 37ef795322 fix(YouTube): Fix Custom settings strings misplacement and disable broken array-based list preferences
The Custom settings strings added in 97b4fc5b5 were accidentally placed
under the Twitch settings patch block instead of YouTube's, which broke
resource compilation entirely. Moved them to the correct block and added
the missing _summary_on/_summary_off pairs for the switch preferences.

Also comments out 3 ListPreferences (playback speed, video quality,
startup tab) that reference "${key}_entries"/"${key}_entry_values" array
resources: the addresources/values/strings.xml pipeline only extracts
<string> elements and silently drops <array>/<string-array>, so these
preferences had no backing entries and broke patching. Re-enabling them
needs real ArrayResource wiring through ListPreference(entries=,
entryValues=).

Found by running the patches through revanced-cli against a real
YouTube 21.34.248 APK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NXRsEcWGqDhsCbfEdrfCdE
2026-09-03 20:50:40 +02:00
8 changed files with 171 additions and 42 deletions
@@ -0,0 +1,25 @@
package app.revanced.patches.spotify.premium.interceptors
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
class ProductStateInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (request.url.encodedPath.contains("/product-state")) {
val json = "{\"product\":\"premium\",\"type\":\"premium\",\"expires\":\"2099-12-31\",\"offline\":true,\"high_bitrate\":true}"
return Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body(json.toResponseBody("application/json".toMediaType()))
.build()
}
return chain.proceed(request)
}
}
+1
View File
@@ -0,0 +1 @@
sdk.dir=/usr/lib/android-sdk
@@ -0,0 +1,37 @@
package app.revanced.patches.spotify.misc.skip
import app.revanced.patcher.*
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
// Getter on the locally-held player Restrictions model (deserialized from local player
// state, not fetched fresh from the server per call) that lists the reasons skipping to
// the next track is currently disallowed. A free-tier account gets a non-empty set here
// once the "6 skips per hour" cap is hit, and it is also checked while an ad is playing
// to decide whether the skip button is enabled. Every skip button in the app (Now Playing
// bar, ad track, notification, etc.) reads this same getter, so overriding it to always
// report "no restrictions" removes the skip cap and unlocks skipping ads, without ever
// telling the server the account's plan changed.
internal val BytecodePatchContext.disallowSkippingNextReasonsMethod by gettingFirstMethodDeclaratively {
name("disallowSkippingNextReasons")
accessFlags(AccessFlags.PUBLIC)
parameterTypes()
// Restrictions itself only declares the getter abstractly; skip past it to the
// AutoValue-generated implementation, which is the one with an actual method body.
custom { !AccessFlags.ABSTRACT.isSet(accessFlags) }
}
// Local (client-side) rapid-skip cooldown tracker: keeps the timestamps of the last 3
// skips in a LinkedList and returns 3 ("Slow down! You get 6 skips per hour.") if all 3
// happened within 10 seconds (0x2710 ms) of each other, 2 (first-ever-skip "You now have
// 6 skips per hour" toast) the very first time it's called, or 1 (no toast) otherwise.
internal val BytecodePatchContext.freeTierRapidSkipCounterMethodMatch by composingFirstMethod {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returnType("I")
parameterTypes()
instructions(
method { name == "elapsedRealtime" && returnType == "J" },
afterAtMost(50, 10000L()),
)
}
@@ -0,0 +1,50 @@
package app.revanced.patches.spotify.misc.skip
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.firstClassDef
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.AccessFlags
@Suppress("unused")
val removeSkipRestrictionsPatch = bytecodePatch(
name = "Remove skip restrictions",
description = "Removes the free-tier \"6 skips per hour\" limit and its toasts/upsells, " +
"and unlocks the skip button while an ad is playing. This only changes local " +
"client-side behavior; it does not report a Premium account to Spotify.",
use = false,
) {
compatibleWith("com.spotify.music")
apply {
// Force the Restrictions getter that every skip button reads to always report
// that skipping is allowed, by having it construct and return an empty instance
// of its own (obfuscated) collection return type instead of the real field.
disallowSkippingNextReasonsMethod.apply {
val emptyCollectionType = returnType
val emptyCollectionFactoryMethod = firstClassDef(emptyCollectionType).methods.first {
AccessFlags.STATIC.isSet(it.accessFlags) &&
it.returnType == emptyCollectionType &&
it.parameterTypes.map(Any::toString) == listOf("I", "[Ljava/lang/Object;")
}
// Reuses "this" (p0) as a scratch register instead of requiring a second
// local, since everything below this early return becomes dead code.
addInstructions(
0,
"""
const/4 v0, 0x0
new-array p0, v0, [Ljava/lang/Object;
invoke-static { v0, p0 }, ${emptyCollectionFactoryMethod.definingClass}->${emptyCollectionFactoryMethod.name}(I[Ljava/lang/Object;)$emptyCollectionType
move-result-object v0
return-object v0
""",
)
}
// Force the rapid-skip cooldown tracker to always behave like it does after the
// first-ever skip (return 1, no toast), instead of counting towards the cap.
freeTierRapidSkipCounterMethodMatch.method.returnEarly(1)
}
}
@@ -238,17 +238,27 @@ val settingsPatch = bytecodePatch(
SwitchPreference( SwitchPreference(
"revanced_custom_hide_shorts", "revanced_custom_hide_shorts",
), ),
ListPreference( // TEMPORARILY DISABLED FOR TESTING (revanced-cli verification pass): this
key = "revanced_custom_default_playback_speed", // ListPreference references "${key}_entries"/"${key}_entry_values" array
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference", // resources that don't exist anywhere and can't be added via addresources/
), // values/strings.xml, since patches/strings-processing.gradle.kts only
// extracts <string> elements (see its `"string" -> transform(...)` case
// vs. the `else -> walk children` fallthrough that silently drops <array>/
// <string-array>). Needs a real app.revanced.util.resource.ArrayResource
// wired through the ListPreference(entries=, entryValues=) constructor
// (see patches/.../shared/misc/settings/preference/ListPreference.kt) or
// another array-resource source before this can ship.
// ListPreference(
// key = "revanced_custom_default_playback_speed",
// tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
// ),
SwitchPreference( SwitchPreference(
"revanced_custom_remember_playback_speed", "revanced_custom_remember_playback_speed",
), ),
ListPreference( // ListPreference(
key = "revanced_custom_default_video_quality", // key = "revanced_custom_default_video_quality",
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference", // tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
), // ),
SwitchPreference( SwitchPreference(
"revanced_custom_remember_video_quality", "revanced_custom_remember_video_quality",
), ),
@@ -264,10 +274,10 @@ val settingsPatch = bytecodePatch(
inputType = InputType.TEXT_CAP_CHARACTERS, inputType = InputType.TEXT_CAP_CHARACTERS,
tag = "app.revanced.extension.shared.settings.preference.TextInputPreference", tag = "app.revanced.extension.shared.settings.preference.TextInputPreference",
), ),
ListPreference( // ListPreference(
key = "revanced_custom_startup_tab", // key = "revanced_custom_startup_tab",
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference", // tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
), // ),
SwitchPreference( SwitchPreference(
"revanced_custom_clean_urls", "revanced_custom_clean_urls",
), ),
@@ -241,6 +241,42 @@ However, enabling this will also log some user data such as your IP address."</s
<string name="revanced_restore_old_settings_menus_title">Restore old settings menus</string> <string name="revanced_restore_old_settings_menus_title">Restore old settings menus</string>
<string name="revanced_restore_old_settings_menus_summary_on">Old settings menus are shown</string> <string name="revanced_restore_old_settings_menus_summary_on">Old settings menus are shown</string>
<string name="revanced_restore_old_settings_menus_summary_off">Old settings menus are not shown</string> <string name="revanced_restore_old_settings_menus_summary_off">Old settings menus are not shown</string>
<!-- Custom settings -->
<string name="revanced_custom_title">Custom</string>
<string name="revanced_custom_reset_title">Reset Custom Settings</string>
<string name="revanced_custom_reset_summary">Restore custom preferences to defaults</string>
<string name="revanced_custom_force_amoled_title">Force AMOLED</string>
<string name="revanced_custom_force_amoled_summary_on">True black is used in the YouTube UI</string>
<string name="revanced_custom_force_amoled_summary_off">True black is not used in the YouTube UI</string>
<string name="revanced_custom_pure_black_player_title">Pure Black Player</string>
<string name="revanced_custom_pure_black_player_summary_on">Player background is pure black</string>
<string name="revanced_custom_pure_black_player_summary_off">Player background is not pure black</string>
<string name="revanced_custom_disable_ambient_mode_title">Disable Ambient Mode</string>
<string name="revanced_custom_disable_ambient_mode_summary_on">Ambient Mode is prevented from activating</string>
<string name="revanced_custom_disable_ambient_mode_summary_off">Ambient Mode can activate</string>
<string name="revanced_custom_hide_shorts_title">Hide Shorts</string>
<string name="revanced_custom_hide_shorts_summary_on">Shorts UI entries are removed</string>
<string name="revanced_custom_hide_shorts_summary_off">Shorts UI entries are shown</string>
<string name="revanced_custom_default_playback_speed_title">Default Playback Speed</string>
<string name="revanced_custom_remember_playback_speed_title">Remember Playback Speed</string>
<string name="revanced_custom_remember_playback_speed_summary_on">Playback speed is remembered across videos</string>
<string name="revanced_custom_remember_playback_speed_summary_off">Playback speed is not remembered across videos</string>
<string name="revanced_custom_default_video_quality_title">Default Video Quality</string>
<string name="revanced_custom_remember_video_quality_title">Remember Video Quality</string>
<string name="revanced_custom_remember_video_quality_summary_on">Video quality is remembered across videos</string>
<string name="revanced_custom_remember_video_quality_summary_off">Video quality is not remembered across videos</string>
<string name="revanced_custom_force_highest_quality_title">Force Highest Available Quality</string>
<string name="revanced_custom_force_highest_quality_summary_on">Highest quality option is preferred</string>
<string name="revanced_custom_force_highest_quality_summary_off">Highest quality option is not preferred</string>
<string name="revanced_custom_debug_video_info_title">Debug Video Information</string>
<string name="revanced_custom_debug_video_info_summary_on">Technical video details are shown</string>
<string name="revanced_custom_debug_video_info_summary_off">Technical video details are hidden</string>
<string name="revanced_custom_app_name_title">Custom App Name</string>
<string name="revanced_custom_app_name_summary">Change the visible application name</string>
<string name="revanced_custom_startup_tab_title">Default Startup Tab</string>
<string name="revanced_custom_clean_urls_title">Clean YouTube URLs</string>
<string name="revanced_custom_clean_urls_summary_on">Tracking parameters are removed from shared links</string>
<string name="revanced_custom_clean_urls_summary_off">Tracking parameters are not removed from shared links</string>
</patch> </patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch"> <patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Disable Shorts background play</string> <string name="revanced_shorts_disable_background_playback_title">Disable Shorts background play</string>
@@ -1945,36 +1981,6 @@ Video playback with AV1 may stutter or drop frames."</string>
<string name="revanced_twitch_debug_title">Debug logging</string> <string name="revanced_twitch_debug_title">Debug logging</string>
<string name="revanced_twitch_debug_summary_on">Debug logs are enabled</string> <string name="revanced_twitch_debug_summary_on">Debug logs are enabled</string>
<string name="revanced_twitch_debug_summary_off">Debug logs are disabled</string> <string name="revanced_twitch_debug_summary_off">Debug logs are disabled</string>
<!-- Custom settings -->
<string name="revanced_custom_title">Custom</string>
<string name="revanced_custom_reset_title">Reset Custom Settings</string>
<string name="revanced_custom_reset_summary">Restore custom preferences to defaults</string>
<string name="revanced_custom_force_amoled_title">Force AMOLED</string>
<string name="revanced_custom_force_amoled_summary">Use true black in the YouTube UI</string>
<string name="revanced_custom_pure_black_player_title">Pure Black Player</string>
<string name="revanced_custom_pure_black_player_summary">Make player background pure black</string>
<string name="revanced_custom_disable_ambient_mode_title">Disable Ambient Mode</string>
<string name="revanced_custom_disable_ambient_mode_summary">Prevent Ambient Mode from activating</string>
<string name="revanced_custom_hide_shorts_title">Hide Shorts</string>
<string name="revanced_custom_hide_shorts_summary">Remove Shorts UI entries</string>
<string name="revanced_custom_default_playback_speed_title">Default Playback Speed</string>
<string name="revanced_custom_default_playback_speed_summary">Select default playback speed</string>
<string name="revanced_custom_remember_playback_speed_title">Remember Playback Speed</string>
<string name="revanced_custom_remember_playback_speed_summary">Remember speed across videos</string>
<string name="revanced_custom_default_video_quality_title">Default Video Quality</string>
<string name="revanced_custom_default_video_quality_summary">Select default video quality</string>
<string name="revanced_custom_remember_video_quality_title">Remember Video Quality</string>
<string name="revanced_custom_remember_video_quality_summary">Remember quality across videos</string>
<string name="revanced_custom_force_highest_quality_title">Force Highest Available Quality</string>
<string name="revanced_custom_force_highest_quality_summary">Prefer highest quality option</string>
<string name="revanced_custom_debug_video_info_title">Debug Video Information</string>
<string name="revanced_custom_debug_video_info_summary">Show technical video details</string>
<string name="revanced_custom_app_name_title">Custom App Name</string>
<string name="revanced_custom_app_name_summary">Change the visible application name</string>
<string name="revanced_custom_startup_tab_title">Default Startup Tab</string>
<string name="revanced_custom_startup_tab_summary">Select startup tab</string>
<string name="revanced_custom_clean_urls_title">Clean YouTube URLs</string>
<string name="revanced_custom_clean_urls_summary">Remove tracking parameters from shared links</string>
</patch> </patch>
</app> </app>
</resources> </resources>