@@ -144,6 +144,21 @@ public struct ShellHostConfig: Sendable {
144144 return s
145145 }
146146
147+ private enum CloudSaves { case off , own ( URL ) , stale , shell ( URL ) , none }
148+
149+ /// Where the game's cloud saves go: off when the player turned them off for the game, then the game's own folder,
150+ /// then the shell settings' folder.
151+ private func cloudSaves ( _ id : String ) -> CloudSaves {
152+ if config . defaults ( id ) . bool ( forKey : GameDefaults . cloudSavesOffKey ) { return . off }
153+ switch cloudStore ( id ) . resolve ( ) {
154+ case . url ( let url ) : return . own ( url )
155+ case . stale : return . stale
156+ case . none :
157+ if case . url ( let url ) = shellCloud . resolve ( ) { return . shell ( url ) }
158+ return . none
159+ }
160+ }
161+
147162 private func data ( for entry : CatalogueEntry ) -> DataResolution {
148163 guard let manifest = entry . manifest , entry . problem == nil else { return . missing }
149164 return DataLocator . locate ( manifest : manifest , picked : dataStore ( entry . id ) . resolve ( ) , appFolder : appFolder ( for : entry . id ) ,
@@ -172,17 +187,21 @@ public struct ShellHostConfig: Sendable {
172187 var cloud : String ?
173188 var cloudNote = " "
174189 var cloudFromShell = false
175- switch cloudStore ( entry . id ) . resolve ( ) {
176- case . url ( let url ) : cloud = url . lastPathComponent
190+ switch cloudSaves ( entry . id ) {
191+ case . own ( let url ) : cloud = url . lastPathComponent
177192 case . stale : cloudNote = hostText ( " The folder you chose before is no longer readable. Please choose it again. " )
178- case . none :
179- if case . url ( let url ) = shellCloud . resolve ( ) { cloud = url . lastPathComponent ; cloudFromShell = true }
193+ case . shell ( let url ) : cloud = hostText ( " \( url . lastPathComponent ) (from Settings) " ) ; cloudFromShell = true
194+ case . off , . none : break
180195 }
196+ var shellFolder = false
197+ if case . url = shellCloud . resolve ( ) { shellFolder = true }
198+ let fromShell = Set ( profile . settings . filter ( store . isFromShell ) . map ( \. env ) )
181199 return GameDetail (
182- game : tile , sections : SettingsPresenter . sections ( profile : profile , values : store . values ( ) ) ,
200+ game : tile , sections : SettingsPresenter . sections ( profile : profile , values : store . values ( ) , fromShell : fromShell ) ,
183201 dataSource : dataSource ( data ) , dataNote : dataNote ( data , found : data . found ) ,
184202 cloudSaves : cloud , cloudNote : cloudNote , dlc : dlc ( data ) , hasModifiedSettings : store . hasModifiedSettings ,
185- dataFromShell : data . source == . shell , cloudFromShell : cloudFromShell )
203+ dataFromShell : data . source == . shell , cloudFromShell : cloudFromShell ,
204+ cloudCanUseShell : shellFolder && ! cloudFromShell )
186205 }
187206
188207 private func dataSource ( _ data : DataResolution ) -> String {
@@ -224,7 +243,10 @@ public struct ShellHostConfig: Sendable {
224243 onShowLog : { [ weak self ] id in self ? . showLog ( id ) } ,
225244 onAppUpdate : { [ weak self ] in self ? . appUpdate ( ) } ,
226245 onPickShellData : { [ weak self ] in self . map { model in Task { await model . pickShellDataNow ( ) } } } ,
227- onPickShellCloud : { [ weak self ] in self . map { model in Task { await model . pickShellCloudNow ( ) } } } )
246+ onPickShellCloud : { [ weak self ] in self . map { model in Task { await model . pickShellCloudNow ( ) } } } ,
247+ onUseShellCloud : { [ weak self ] id in self ? . useShellCloudSaves ( id ) } ,
248+ onClearShellData : { [ weak self ] in self ? . clearShellData ( ) } ,
249+ onClearShellCloud : { [ weak self ] in self ? . clearShellCloud ( ) } )
228250 }
229251
230252 public func change ( _ id : String , setting : String , value : String ) {
@@ -269,7 +291,8 @@ public struct ShellHostConfig: Sendable {
269291
270292 func pickCloudSavesNow ( _ id : String ) async {
271293 guard let url = await picker . pickFolder ( ) else { return }
272- if ! cloudStore ( id ) . store ( url ) { notice = hostText ( " That folder could not be remembered. Try a different one. " ) }
294+ if cloudStore ( id ) . store ( url ) { config . defaults ( id ) . removeObject ( forKey : GameDefaults . cloudSavesOffKey ) }
295+ else { notice = hostText ( " That folder could not be remembered. Try a different one. " ) }
273296 refresh ( id )
274297 }
275298
@@ -289,9 +312,29 @@ public struct ShellHostConfig: Sendable {
289312
290313 public func disableCloudSaves ( _ id : String ) {
291314 cloudStore ( id ) . clear ( )
315+ config . defaults ( id ) . set ( true , forKey : GameDefaults . cloudSavesOffKey )
316+ refresh ( id )
317+ }
318+
319+ /// The game follows the shell settings' cloud-saves folder again.
320+ public func useShellCloudSaves ( _ id : String ) {
321+ cloudStore ( id ) . clear ( )
322+ config . defaults ( id ) . removeObject ( forKey : GameDefaults . cloudSavesOffKey )
292323 refresh ( id )
293324 }
294325
326+ func clearShellData ( ) {
327+ shellData . clear ( )
328+ shellSettings . clearDataFolderName ( )
329+ refreshAll ( )
330+ }
331+
332+ func clearShellCloud ( ) {
333+ shellCloud . clear ( )
334+ shellSettings . clearCloudSavesFolderName ( )
335+ refreshAll ( )
336+ }
337+
295338 public func showLog ( _ id : String ) {
296339 guard let entry = entry ( id ) else { return }
297340 let app = appFolder ( for : id )
@@ -316,10 +359,9 @@ public struct ShellHostConfig: Sendable {
316359 guard let entry = entry ( id ) , let manifest = entry . manifest , let profile = entry . profile , let framework = entry . frameworkURL ,
317360 details . first ( where : { $0 . id == id } ) ? . game . state == . playable , let dataURL = data ( for : entry ) . url else { return }
318361 var cloud : URL ?
319- switch cloudStore ( id ) . resolve ( ) {
320- case . url ( let url ) : cloud = url
321- case . stale : break
322- case . none : if case . url ( let url ) = shellCloud . resolve ( ) { cloud = url }
362+ switch cloudSaves ( id ) {
363+ case . own ( let url ) , . shell ( let url ) : cloud = url
364+ case . off , . stale , . none : break
323365 }
324366 // Remembered when Play is triggered, even if the launcher then fails (the stub). The order changes at the next load.
325367 LastPlayedStore ( defaults : config . defaults ( id ) ) . record ( config . now ( ) )
0 commit comments