@@ -2,6 +2,7 @@ package cmd
22
33import (
44 "bytes"
5+ "fmt"
56 "os"
67 "os/exec"
78 "path/filepath"
@@ -33,18 +34,24 @@ func BlockSpotifyUpdates(disabled bool) {
3334 return
3435 }
3536 updateDir := homeDir + "/Library/Application Support/Spotify/PersistentCache/Update"
37+ exec .Command ("pkill" , "Spotify" ).Run ()
38+ exec .Command ("mkdir" , "-p" , updateDir ).Run ()
3639 if disabled {
37- exec .Command ("pkill" , "Spotify" ).Run ()
38- exec .Command ("mkdir" , "-p" , updateDir ).Run ()
3940 exec .Command ("chflags" , "uchg" , updateDir ).Run ()
4041 msg = "Disabled"
4142 } else {
42- exec .Command ("pkill" , "Spotify" ).Run ()
43- exec .Command ("mkdir" , "-p" , updateDir ).Run ()
4443 exec .Command ("chflags" , "nouchg" , updateDir ).Run ()
4544 msg = "Enabled"
4645 }
4746
47+ // chflags alone is not enough anymore: current clients stage updates
48+ // via a segmented downloader that does not reference that directory.
49+ // Patching the update endpoint makes the updater unreachable
50+ // regardless of how the payload is fetched.
51+ if err := patchDarwinUpdateEndpoint (spotifyExecPath , disabled ); err != nil {
52+ utils .PrintWarning ("Endpoint patch failed (lock still applied): " + err .Error ())
53+ }
54+
4855 utils .PrintSuccess (msg + " Spotify updates!" )
4956 return
5057 }
@@ -75,3 +82,60 @@ func BlockSpotifyUpdates(disabled bool) {
7582 file .WriteAt ([]byte (str ), int64 (i + 15 ))
7683 utils .PrintSuccess (msg + " Spotify updates!" )
7784}
85+
86+ const (
87+ darwinUpdateEndpoint = "desktop-update/v2/update"
88+ darwinUpdateEndpointPatched = "desktop-update/no/thanks"
89+ darwinUpdateEndpointPatchOff = len ("desktop-update/" )
90+ )
91+
92+ // patchDarwinUpdateEndpoint rewrites the desktop-update endpoint inside the
93+ // Spotify binary and re-signs the bundle ad-hoc so it still launches on
94+ // Apple Silicon. Blocking writes "no/thanks" over "v2/update"; unblocking
95+ // restores the original bytes (from the backup taken on first block, or by
96+ // reversing the patch).
97+ func patchDarwinUpdateEndpoint (binaryPath string , block bool ) error {
98+ raw , err := os .ReadFile (binaryPath )
99+ if err != nil {
100+ return err
101+ }
102+
103+ backupPath := filepath .Join (utils .GetSpicetifyFolder (), "spotify-binary-backup" )
104+
105+ if block {
106+ if ! bytes .Contains (raw , []byte (darwinUpdateEndpoint )) {
107+ // Already patched (or the endpoint moved): nothing to do.
108+ return nil
109+ }
110+ if _ , err := os .Stat (backupPath ); os .IsNotExist (err ) {
111+ if err := os .WriteFile (backupPath , raw , 0755 ); err != nil {
112+ return fmt .Errorf ("cannot back up binary: %w" , err )
113+ }
114+ }
115+ idx := bytes .Index (raw , []byte (darwinUpdateEndpoint ))
116+ copy (raw [idx + darwinUpdateEndpointPatchOff :], "no/thanks" )
117+ } else {
118+ if st , err := os .Stat (backupPath ); err == nil && ! st .IsDir () {
119+ raw , err = os .ReadFile (backupPath )
120+ if err != nil {
121+ return err
122+ }
123+ } else {
124+ idx := bytes .Index (raw , []byte (darwinUpdateEndpointPatched ))
125+ if idx < 0 {
126+ return fmt .Errorf ("patched endpoint not found and no backup to restore" )
127+ }
128+ copy (raw [idx + darwinUpdateEndpointPatchOff :], "v2/update" )
129+ }
130+ }
131+
132+ if err := os .WriteFile (binaryPath , raw , 0755 ); err != nil {
133+ return err
134+ }
135+
136+ bundlePath := filepath .Join (binaryPath , ".." , ".." , ".." )
137+ if out , err := exec .Command ("codesign" , "--force" , "--deep" , "--sign" , "-" , bundlePath ).CombinedOutput (); err != nil {
138+ return fmt .Errorf ("codesign failed: %w (%s)" , err , strings .TrimSpace (string (out )))
139+ }
140+ return nil
141+ }
0 commit comments