7 Commits
Author SHA1 Message Date
amd64fox 0572e09a3e fix: support variable-length hashes in spotify versions 2026-10-07 20:55:31 +03:00
amd64fox 0447e6eb03 fix: remove microsoft store spotify before extraction 2026-10-07 20:50:27 +03:00
amd64fox 3f3fd30a95 fix: restore block_subfeeds selectors for spotify 1.3.x 2026-10-02 14:36:40 +03:00
amd64fox 20668e69ef version bump to 1.3.3
- update css selectors
2026-10-02 06:08:24 +03:00
amd64fox 8d31a4e0ed fix: adapt native crossfade patches for spotify 1.3.3
- support updated x64 and arm64 crossfade layouts
- handle arm64 list player boolean reads and preserve older versions
2026-10-02 06:08:24 +03:00
amd64fox ddd9257d73 fix: adapt emptyblock and banner_home for spotify 1.3.3
- handle adsenabled assignments in emptyblock
- handle payload assignments in banner_home and preserve older patterns
2026-10-02 06:08:24 +03:00
amd64fox 893a1cca43 fix(js-helper): encode spotify app versions correctly 2026-10-02 03:13:33 +03:00
5 changed files with 98 additions and 42 deletions
+2 -2
View File
@@ -34,7 +34,7 @@ jobs:
}
$spotifyVersion = [string]$latestEntry.Value.fullversion
if ([string]::IsNullOrWhiteSpace($spotifyVersion) -or $spotifyVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$') {
if ([string]::IsNullOrWhiteSpace($spotifyVersion) -or $spotifyVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]+$') {
throw "Invalid Spotify fullversion for $($latestEntry.Name)"
}
}
@@ -93,7 +93,7 @@ jobs:
if (-not $installer.url -or $installer.size -le 0) {
throw "No ARM64 installer found for Spotify $checkVersion"
}
if ($entry.Value.fullversion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$') {
if ($entry.Value.fullversion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]+$') {
throw "Invalid Spotify fullversion for $checkVersion"
}
+17 -7
View File
@@ -17,6 +17,7 @@
[style*="--lyrics-color-active"] p[class*="e-"][class*="-text"].encore-text-body-small,
.ReC7DlF3I_k9g6Vv,
.WlotvRiQOV9Ps_vFbm14,
.main-view-container .FGdK_c0adS4IdZFuZd8Y,
.Zfb6QZfRV28Kz1RQ,
.b8IhQswrT49QTc5CuFif {
color: var(--musixmatch) !important;
@@ -43,7 +44,8 @@ body .kGR_hu4tdj9PnUlSPaRL,
body .iq4cgi0YEKr6DGaTtzUj,
body .KDhLFoEqoClhH12bsfrS,
.NUBq_wlyuwoDUsSg,
.upDNlpL8xEkxmKWWw9IF {
.upDNlpL8xEkxmKWWw9IF,
.main-view-container .LeoJGcLe14Es7fth7HG7 {
color: var(--musixmatch) !important;
}
@@ -53,7 +55,8 @@ body .KDhLFoEqoClhH12bsfrS,
.e7eFLioNSG5PAi1qVFT4,
.l2060qoyWU4J9ihSxHLE,
.hfTlyhd7WCIk9xmP,
.bDlqrHmbHstExWu6T7Ms {
.bDlqrHmbHstExWu6T7Ms,
.main-view-container .lzSRPoTbAKaWG_T2CDlK {
color: var(--musixmatch) !important;
}
@@ -63,7 +66,8 @@ body .KDhLFoEqoClhH12bsfrS,
.hW9km7ku6_iggdWDR_Lg,
.lofIAg8Ixko3mfBrbfej,
.bbJIIopLxggQmv5x,
.bqldaBkacR41KxR2Z0jY {
.bqldaBkacR41KxR2Z0jY,
.main-view-container .Pb9OM7BnosJTdj3KAIWE {
--lyrics-color-active: var(--current) !important;
--lyrics-color-inactive: var(--next) !important;
--lyrics-color-passed: var(--past) !important;
@@ -74,7 +78,8 @@ body .KDhLFoEqoClhH12bsfrS,
.H2J92dVdr0ykdOX5azL1,
.KnFq2ijXFdOtyl4Iebjv,
.XiH9KR6bhDwEFykV,
.loNizikBbaCKyI9Gv8xg {
.loNizikBbaCKyI9Gv8xg,
.main-view-container .zYNpVrsABjnrvr7ZSJTd {
color: var(--past) !important;
opacity: 1 !important;
}
@@ -90,7 +95,9 @@ body .KDhLFoEqoClhH12bsfrS,
.Mnf9PkrVHsX90BNf:hover,
.XiH9KR6bhDwEFykV:hover,
.MZZCOz_ImVH1skMtX3aI:hover,
.loNizikBbaCKyI9Gv8xg:hover {
.loNizikBbaCKyI9Gv8xg:hover,
.main-view-container .qrRT38O7gAArS1n8grcl:hover,
.main-view-container .zYNpVrsABjnrvr7ZSJTd:hover {
color: var(--hover) !important;
/* remove underline */
text-decoration: none !important;
@@ -105,7 +112,8 @@ body .KDhLFoEqoClhH12bsfrS,
.gaHIufRWhoWbiT8S6zuM.Qo3OkrSis5IWlP9Tchbr,
.AEfhRyqGa3vzQrgfdwWE .Re403AJffPPuZmX7LRJj,
.AQFBg9wNhDoKJHvS,
.aO2StTvteoZW3ixq2Fz4 {
.aO2StTvteoZW3ixq2Fz4,
.main-view-container .jdk2I0q0Ac3yps7rqpMQ {
color: var(--next) !important;
}
@@ -152,7 +160,9 @@ body .KDhLFoEqoClhH12bsfrS,
.fhgdX2gD6kkUSiiX,
.ss3nDnn4UPsMVtFr,
.pvWgiS0R_7grz0VjKsSZ,
.qb9cvoyw3y6W2evemPij {
.qb9cvoyw3y6W2evemPij,
.MRV6jUuQgCXqrlnFuDBA,
.XzkBD72pfm2R6YsB2_6W {
--transcript-color-background: var(--background) !important;
--transcript-color-text: var(--next) !important;
--transcript-color-highlightText: var(--current) !important;
+9 -5
View File
@@ -3,7 +3,7 @@
window.oneTime = true;
const WORKER_BASE_URL = "https://spotify-ingest-admin.amd64fox1.workers.dev";
const SCRIPT_VERSION = "1.2.1";
const SCRIPT_VERSION = "1.2.2";
const SOURCE_LABELS = {
REMOTE: "latest.json",
@@ -113,14 +113,18 @@
return "";
}
const parts = shortVersion.split(".");
if (parts.length !== 4) {
if (typeof shortVersion !== "string" || !/^\d+\.\d+\.\d+\.\d+$/.test(shortVersion)) {
console.warn(`Invalid Spotify version format (${sourceLabel}):`, shortVersion);
return "";
}
const [major, minor, patch, build] = parts;
return major + minor + patch + "0".repeat(Math.max(0, 7 - patch.length - build.length)) + build;
const [major, minor, patch, build] = shortVersion.split(".").map(Number);
const appVersion = major * 100000000 + minor * 10000000 + patch * 100000 + build;
if (!Number.isSafeInteger(appVersion) || minor > 9 || patch > 99 || build > 99999) {
console.warn(`Unsupported Spotify version (${sourceLabel}):`, shortVersion);
return "";
}
return String(appVersion);
}
async function fetchJsonWithTimeout(url, timeoutMs) {
+18 -9
View File
@@ -21,8 +21,8 @@
"fr": "1.1.59",
"to": ""
},
"match": "adsEnabled:!0",
"replace": "adsEnabled:!1"
"match": "\\b(adsEnabled[:=])!0",
"replace": "$1!1"
},
"playlistsponsor": {
"version": {
@@ -874,7 +874,7 @@
"description": "Use ads-owned NPV and cinema surface state for ad orchestration",
"version": {
"fr": "1.2.96",
"to": ""
"to": "1.3.1"
}
},
"HptoHarmonyVideoPlayer": {
@@ -916,6 +916,15 @@
"fr": "1.2.99",
"to": ""
}
},
"FocusStateAwareHptoFetch": {
"name": "enableFocusStateAwareHptoFetch",
"description": "Disable background HPTO fetch diagnostics",
"native_description": "Enable focus state guard when fetching HPTO ad",
"version": {
"fr": "1.3.3",
"to": ""
}
}
},
"EnableExp": {
@@ -2094,7 +2103,7 @@
"native_description": "Enables the Canvas context menu toggle",
"version": {
"fr": "1.2.96",
"to": ""
"to": "1.3.1"
}
},
"MusicVideos": {
@@ -2714,28 +2723,28 @@
"fr": "1.2.36",
"to": ""
},
"add": " .LVMjmN2CaPruPAo62RAY, .Wn4fEKCJ6jYPfG2Y0ABn, .Y3Kd8WKnE3ZW9lzgA8nN, .C_T1Uoz7Fws_AFDl { display: none !important }"
"add": " .LVMjmN2CaPruPAo62RAY, .Wn4fEKCJ6jYPfG2Y0ABn, .Y3Kd8WKnE3ZW9lzgA8nN, .C_T1Uoz7Fws_AFDl, .WvvVZFf5X3INPzFLFyXQ, .NTX5QnnAPQ3vbP3jyNlk { display: none !important }"
},
"downloadquality": {
"version": {
"fr": "1.2.30",
"to": ""
},
"add": " :is(.weV_qxFz4gF5sPotO10y, .BMtRRwqaJD_95vJFMFD0, .eguwzH_QWTBXry7hiNj3, .qV_CxbowaNkMarye, .Z6yfNoSJ6OjiEBGI5Osr):has([for=\"desktop.settings.downloadQuality\"]) {display: none}"
"add": " :is(.weV_qxFz4gF5sPotO10y, .BMtRRwqaJD_95vJFMFD0, .eguwzH_QWTBXry7hiNj3, .qV_CxbowaNkMarye, .Z6yfNoSJ6OjiEBGI5Osr, .H_Y7u72_U2ERiU8oYJeL):has([for=\"desktop.settings.downloadQuality\"]) {display: none}"
},
"downloadicon": {
"version": {
"fr": "1.1.74",
"to": ""
},
"add": " .BKsbV2Xl786X9a09XROH, .GWCBhKJqeZal3n5tCQwl, .pX3IkLhEry0wVfiU, .IOXdOZpz_OhQUj2M3A0e {display:none}"
"add": " .BKsbV2Xl786X9a09XROH, .GWCBhKJqeZal3n5tCQwl, .pX3IkLhEry0wVfiU, .IOXdOZpz_OhQUj2M3A0e, .F9vJGD2_t3IUyzuoDzOb {display:none}"
},
"submenudownload": {
"version": {
"fr": "1.1.74",
"to": ""
},
"add": " :is(.pzkhLqffqF_4hucrVVQA, .egE6UQjF_UUoCzvMxREj, .Y98_oiegQgSpY_o7hoKG, .tT_JypfxNakuY1jHgyBN, .zVA1h9TUy8QQBogj, .l7XabbfpobRNm8SpemYx)[role=menuitemcheckbox] {display:none}"
"add": " :is(.pzkhLqffqF_4hucrVVQA, .egE6UQjF_UUoCzvMxREj, .Y98_oiegQgSpY_o7hoKG, .tT_JypfxNakuY1jHgyBN, .zVA1h9TUy8QQBogj, .l7XabbfpobRNm8SpemYx, .OS2FSGrMk4JcABLlGUmc, .OAz6EUvqC2CpvN61OoeA)[role=menuitemcheckbox] {display:none}"
},
"veryhighstream": {
"version": {
@@ -2808,7 +2817,7 @@
"fr": "1.1.70",
"to": ""
},
"match": "(ADS_PREMIUM,isPremium:).(.{20,300}?isHptoHidden:).",
"match": "(ADS_PREMIUM,isPremium:|\\.isPremium=)[\\w$]+(?:\\.payload)?(.{20,300}?isHptoHidden[:=])[\\w$]+(?:\\.payload)?",
"replace": "$1true$2true"
},
"sentry": {
+52 -19
View File
@@ -2,7 +2,7 @@
param
(
[Parameter(HelpMessage = 'Latest recommended Spotify version for Windows 10+.')]
[string]$latest_full = "1.3.1",
[string]$latest_full = "1.3.3",
[Parameter(HelpMessage = 'Latest supported Spotify version for Windows 7-8.1')]
[string]$last_win7_full = "1.2.5.1006.g22820f93",
@@ -489,7 +489,7 @@ function Get-SpotifyVersionNumber {
[string]$SpotifyVersion
)
return [Version]($SpotifyVersion -replace '\.g[0-9a-f]{8}$', '')
return [Version]($SpotifyVersion -replace '\.g[0-9a-f]+$', '')
}
function Get-SpotifyInstallerArchitecture {
@@ -579,7 +579,7 @@ function Resolve-SpotifyInstallerVersionFromManifest {
$entry = $selectedVersion.Value
$fullVersion = [string]$entry.fullversion
if ($fullVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$') {
if ($fullVersion -notmatch '^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]+$') {
throw "Spotify version $($selectedVersion.Name) has invalid fullversion in versions manifest"
}
@@ -621,7 +621,7 @@ $spotifyTemporaryDownloadVersion = "1.2.86.502.g8cd7fb22"
$spotifyVersionsManifestUrl = Get-Link -e "/table/versions.json" -Owner "LoaderSpot" -Repository "table"
$systemArchitecture = Get-SystemArchitecture
$match_v = "^(?<version>\d+\.\d+\.\d+(?:\.\d+(?:\.g[0-9a-f]{8})?)?)(?:-\d+)?$"
$match_v = "^(?<version>\d+\.\d+\.\d+(?:\.\d+(?:\.g[0-9a-f]+)?)?)(?:-\d+)?$"
$versionIsSupported = $false
if ($version) {
if ($version -match $match_v) {
@@ -1777,7 +1777,7 @@ function Kill-Spotify {
}
# Defer removal until the replacement is ready
# Defer Store removal until the replacement installer is downloaded
if ($win10 -or $win11 -or $win8_1 -or $win8 -or $win12) {
if (Get-AppxPackage -Name SpotifyAB.SpotifyMusic) {
@@ -4632,17 +4632,25 @@ function Find-X64CrossfadeEnabledBinaryPatchLocation {
$gateContextPattern = Convert-HexStringToBytes '48 8B 0B 00 00 00 00 00 88 45 00 48 8D 4D 00 E8'
$gateContextMask = Convert-HexStringToBytes 'FF FF FF 00 00 00 00 00 FF FF 00 FF FF FF 00 FF'
$extendedGateContextPattern = Convert-HexStringToBytes '48 8B 0B 00 00 00 00 00 88 45 00 48 8B 0B E8 00 00 00 00 88 45 00 48 8D 4D 00 E8'
$extendedGateContextMask = Convert-HexStringToBytes 'FF FF FF 00 00 00 00 00 FF FF 00 FF FF FF FF 00 00 00 00 FF FF 00 FF FF FF 00 FF'
$hasGateContext = {
param([int]$Candidate)
if (-not [BinaryScannerV3]::MatchMaskedBytes($Bytes, $Candidate - 3, $gateContextPattern, $gateContextMask)) {
$contextLength = if ([BinaryScannerV3]::MatchMaskedBytes($Bytes, $Candidate - 3, $gateContextPattern, $gateContextMask)) {
$gateContextPattern.Length
}
elseif ([BinaryScannerV3]::MatchMaskedBytes($Bytes, $Candidate - 3, $extendedGateContextPattern, $extendedGateContextMask)) {
$extendedGateContextPattern.Length
}
else {
return $false
}
$candidateRva = Get-PERvaFromOffset -Sections $PeInfo.Sections -Offset $Candidate
if ($null -eq $candidateRva) { return $false }
$callerRange = Get-BinaryPatchFunctionRange -Bytes $Bytes -PeInfo $PeInfo -Context $context -Rva $candidateRva
return $callerRange.Length -eq 2 -and ([int64]$candidateRva - 3) -ge [int64]$callerRange[0] -and
([int64]$candidateRva + 13) -le [int64]$callerRange[1]
([int64]$candidateRva - 3 + $contextLength) -le [int64]$callerRange[1]
}
$originalCandidates = @([BinaryScannerV3]::FindCrossfadeGateCallsToRva(
@@ -4748,13 +4756,30 @@ function Find-Arm64CrossfadeEnabledBinaryPatchLocation {
if (($addAnchor -band [uint32]0xFFC0001FL) -ne [uint32]0x91000003L) { continue }
$movKey = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 4)
$movOwner = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 8)
$movDefault = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 12)
$movResolver = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 16)
$addOtherKey = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 20)
$patchOffset = $addOffset + 24
if (($movKey -band [uint32]0x9F000000L) -eq [uint32]0x90000000L) {
$ownerPage = $movKey
$addOtherKey = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 8)
$movKey = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 12)
$movDefault = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 16)
$movResolver = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 20)
$movOwner = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 24)
$patchOffset = $addOffset + 28
$ownerReload = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 32)
$resultOffset = $addOffset + 36
if ((($addOtherKey -shr 5) -band 31) -ne ($ownerPage -band 31) -or
($ownerReload -band [uint32]0xFFC0001FL) -ne
([uint32]0xF9400000L -bor (($movOwner -shr 16) -band 31))) { continue }
}
else {
$movOwner = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 8)
$movDefault = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 12)
$movResolver = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 16)
$addOtherKey = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 20)
$patchOffset = $addOffset + 24
$resultOffset = $addOffset + 28
}
$patchInstruction = Read-Arm64Instruction -Bytes $Bytes -Offset $patchOffset
$storeResult = Read-Arm64Instruction -Bytes $Bytes -Offset ($addOffset + 28)
$storeResult = Read-Arm64Instruction -Bytes $Bytes -Offset $resultOffset
if ($movKey -ne [uint32]0xAA0003E2L -or
($movOwner -band [uint32]0xFFE0FFFFL) -ne [uint32]0xAA0003E0L -or
@@ -4766,7 +4791,8 @@ function Find-Arm64CrossfadeEnabledBinaryPatchLocation {
}
$patchRva = Get-PERvaFromOffset -Sections $PeInfo.Sections -Offset $patchOffset
if ($null -eq $patchRva -or $patchRva -lt $function.StartRva -or $patchRva -ge $function.EndRva) { continue }
if ($null -eq $patchRva -or $patchRva -lt $function.StartRva -or
($patchRva + $resultOffset - $patchOffset + 4) -gt $function.EndRva) { continue }
if (($patchInstruction -band [uint32]0xFC000000L) -eq [uint32]0x94000000L) {
$targetRva = Get-Arm64BranchTargetRva -Instruction $patchInstruction -InstructionRva $patchRva -Kind BL
$textRvaEnd = [int64]$context.Text.VirtualAddress + [Math]::Max([int64]$context.Text.VirtualSize, [int64]$context.Text.RawSize)
@@ -4981,6 +5007,7 @@ function Find-Arm64ListPlayerBinaryPatchLocations {
$next = Read-Arm64Instruction -Bytes $Bytes -Offset ($patchOffset + 4)
$afterNext = Read-Arm64Instruction -Bytes $Bytes -Offset ($patchOffset + 8)
$usesBool = ($next -band [uint32]0xFFFFFFE0L) -eq [uint32]0x2A0003E0L -or
($next -band [uint32]0xFFFFFFE0L) -eq [uint32]0x53001C00L -or
($next -band [uint32]0xFFF8001FL) -eq [uint32]0x36000000L -or
(($next -band [uint32]0xFFC0001FL) -eq [uint32]0xF9400008L -and
($afterNext -band [uint32]0xFFFFFFE0L) -eq [uint32]0x53001C00L)
@@ -5306,7 +5333,6 @@ function Test-SpotifyInstall {
function Expand-SpotifyInstaller {
param([string]$DownloadFolder, [version]$ExpectedVersion, [string]$ExpectedArchitecture)
downloadSp -DownloadFolder $DownloadFolder
$setupExe = Join-Path $DownloadFolder 'SpotifySetup.exe'
$destination = Join-Path $DownloadFolder 'client'
$process = $null
@@ -5361,13 +5387,12 @@ try {
Write-Host ($lang).DownSpoti2`n
$architecture = Get-SpotifyInstallerArchitecture -SystemArchitecture $systemArchitecture `
-SpotifyVersion ([version]$online) -LastX86SupportedVersion $last_x86
$preparedClient = Expand-SpotifyInstaller -DownloadFolder $tempDirectory `
-ExpectedVersion ([version]$online) -ExpectedArchitecture $architecture
Write-Host
downloadSp -DownloadFolder $tempDirectory
}
Kill-Spotify
# Store detection runs before /extract in the Spotify installer
if ($uninstallStoreSpotify) {
Kill-Spotify
$previousProgressPreference = $ProgressPreference
try {
$ProgressPreference = 'SilentlyContinue'
@@ -5378,6 +5403,14 @@ try {
finally { $ProgressPreference = $previousProgressPreference }
}
if ($installSpotify) {
$preparedClient = Expand-SpotifyInstaller -DownloadFolder $tempDirectory `
-ExpectedVersion ([version]$online) -ExpectedArchitecture $architecture
Write-Host
}
if (!$uninstallStoreSpotify) { Kill-Spotify }
if ($installSpotify) {
$null = Unlock-Folder
if ($uninstallSpotify) {