diff --git a/app/src/main/java/app/gamenative/PrefManager.kt b/app/src/main/java/app/gamenative/PrefManager.kt index ab8ac7d486..83b8311d56 100644 --- a/app/src/main/java/app/gamenative/PrefManager.kt +++ b/app/src/main/java/app/gamenative/PrefManager.kt @@ -482,6 +482,18 @@ object PrefManager { setPref(LOCAL_SAVES_ONLY, value) } + // app IDs whose cloud cache was cleared because local-saves-only was toggled off + // (not an upgrade) — checked in SteamAutoCloud to avoid showing upgrade conflict text + private val PENDING_CLOUD_RESYNC = stringPreferencesKey("pending_cloud_resync") + var pendingCloudResync: Set + get() { + val raw = getPref(PENDING_CLOUD_RESYNC, "") + return if (raw.isEmpty()) emptySet() else raw.split(',').mapNotNull { it.toIntOrNull() }.toSet() + } + set(value) { + setPref(PENDING_CLOUD_RESYNC, value.joinToString(",")) + } + private val STEAM_OFFLINE_MODE = booleanPreferencesKey("steam_offline_mode") var steamOfflineMode: Boolean get() = getPref(STEAM_OFFLINE_MODE, false) diff --git a/app/src/main/java/app/gamenative/service/SteamAutoCloud.kt b/app/src/main/java/app/gamenative/service/SteamAutoCloud.kt index 3e1c90ddc0..4a681eb28d 100644 --- a/app/src/main/java/app/gamenative/service/SteamAutoCloud.kt +++ b/app/src/main/java/app/gamenative/service/SteamAutoCloud.kt @@ -847,13 +847,10 @@ object SteamAutoCloud { // check if local state is byte-identical to remote — this is // the "cache-wiped by destructive migration, nothing actually // changed" case and should be silent. key by absolute filesystem - // path: cloud stores files as (pathPrefixIndex, basename) while - // local scan stores filename as subdir-relative path with a - // single pattern prefix, so basename-only keys won't match for - // nested files. - // windows paths are case-insensitive; steam cloud and wine may - // disagree on case. lowercase the keys so content-identical - // files compare equal regardless. + // path (cloud stores (pathPrefixIndex, basename), local scan + // stores subpath-relative filename; basename-only won't match + // for nested files). lowercase since windows paths are + // case-insensitive and wine/cloud may disagree on case. val localByPath = allLocalUserFiles.associate { it.getAbsPath(prefixToPath).toString().lowercase() to it.sha } @@ -878,9 +875,12 @@ object SteamAutoCloud { rehydratedSilently = true } else { hasLocalChanges = true - conflictUfsVersion = CURRENT_UFS_PARSE_VERSION remoteTimestamp = appFileListChange.files.map { it.timestamp.time }.maxOrNull() ?: 0L localTimestamp = allLocalUserFiles.map { it.timestamp }.maxOrNull() ?: 0L + // show upgrade-specific text unless this was a local-saves toggle-off + if (appInfo.id !in PrefManager.pendingCloudResync) { + conflictUfsVersion = CURRENT_UFS_PARSE_VERSION + } } } diff --git a/app/src/main/java/app/gamenative/service/SteamService.kt b/app/src/main/java/app/gamenative/service/SteamService.kt index e4d1b402d8..eea86d63b0 100644 --- a/app/src/main/java/app/gamenative/service/SteamService.kt +++ b/app/src/main/java/app/gamenative/service/SteamService.kt @@ -61,7 +61,11 @@ import app.gamenative.utils.generateSteamApp import app.gamenative.workshop.WorkshopManager import com.winlator.container.Container import com.winlator.xenvironment.ImageFs +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent import `in`.dragonbra.javasteam.depotdownloader.DepotDownloader import `in`.dragonbra.javasteam.depotdownloader.IDownloadListener import `in`.dragonbra.javasteam.depotdownloader.data.AppItem @@ -2809,6 +2813,25 @@ class SteamService : Service(), IChallengeUrlChanged { else -> false } + @EntryPoint + @InstallIn(SingletonComponent::class) + interface CloudSyncCacheEntryPoint { + fun fileChangeListsDao(): FileChangeListsDao + } + + // runBlocking is intentional: must complete before pendingCloudResync write that follows + fun clearCloudSyncCache(context: Context, appId: Int) { + val dao = instance?.fileChangeListsDao + ?: EntryPointAccessors.fromApplication( + context.applicationContext, + CloudSyncCacheEntryPoint::class.java, + ).fileChangeListsDao() + runBlocking { + dao.deleteByAppId(appId) + } + Timber.i("Cleared cloud sync cache for appId=$appId") + } + fun clearDatabase(clearCloudSyncState: Boolean = false) { with(instance!!) { scope.launch { diff --git a/app/src/main/java/app/gamenative/ui/PluviaMain.kt b/app/src/main/java/app/gamenative/ui/PluviaMain.kt index c520519f54..ddbbfbd364 100644 --- a/app/src/main/java/app/gamenative/ui/PluviaMain.kt +++ b/app/src/main/java/app/gamenative/ui/PluviaMain.kt @@ -2042,6 +2042,9 @@ fun preLaunchApp( when (postSyncInfo.syncResult) { SyncResult.Conflict -> { + // clear pending resync flag if present — it already prevented + // SteamAutoCloud from setting conflictUfsVersion + PrefManager.pendingCloudResync = PrefManager.pendingCloudResync - gameId val localDate = Date(postSyncInfo.localTimestamp).toString() val remoteDate = Date(postSyncInfo.remoteTimestamp).toString() val (conflictTitle, conflictMessage) = postSyncInfo.conflictUfsVersion diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt b/app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt index 219d7532c6..659e3561a0 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt @@ -330,13 +330,13 @@ fun GeneralTabContent( state = config.forceDlc, onCheckedChange = { state.config.value = config.copy(forceDlc = it) }, ) -// SettingsSwitch( -// colors = settingsTileColorsAlt(), -// title = { Text(text = stringResource(R.string.local_saves_only)) }, -// subtitle = { Text(text = stringResource(R.string.local_saves_only_description)) }, -// state = config.localSavesOnly, -// onCheckedChange = { state.config.value = config.copy(localSavesOnly = it) }, -// ) + SettingsSwitch( + colors = settingsTileColorsAlt(), + title = { Text(text = stringResource(R.string.local_saves_only)) }, + subtitle = { Text(text = stringResource(R.string.local_saves_only_description)) }, + state = config.localSavesOnly, + onCheckedChange = { state.config.value = config.copy(localSavesOnly = it) }, + ) SettingsSwitch( colors = settingsTileColorsAlt(), title = { Text(text = stringResource(R.string.use_legacy_drm)) }, diff --git a/app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt b/app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt index 55cc5f9d47..6f4c4d8225 100644 --- a/app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt @@ -34,13 +34,13 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.core.content.ContextCompat -import app.gamenative.PrefManager import app.gamenative.PluviaApp - +import app.gamenative.PrefManager import app.gamenative.R import app.gamenative.data.LibraryItem import app.gamenative.enums.Marker import app.gamenative.enums.PathType +import app.gamenative.enums.SaveLocation import app.gamenative.enums.SyncResult import app.gamenative.events.AndroidEvent import app.gamenative.service.DownloadService @@ -53,6 +53,7 @@ import app.gamenative.ui.data.AppMenuOption import app.gamenative.ui.data.GameDisplayInfo import app.gamenative.ui.enums.AppOptionMenuType import app.gamenative.ui.enums.DialogType +import java.util.Date import app.gamenative.utils.ContainerUtils import app.gamenative.utils.MarkerUtils import app.gamenative.utils.SteamUtils @@ -218,6 +219,78 @@ class SteamAppScreen : BaseAppScreen() { fun getPendingUpdateVerifyOperation(gameId: Int): AppOptionMenuType? { return pendingUpdateVerifyOperations[gameId] } + + fun performForceCloudSync( + context: Context, + appId: String, + gameId: Int, + preferredSave: SaveLocation = SaveLocation.None, + container: Container? = null, + ) { + CoroutineScope(Dispatchers.IO).launch { + val steamId = SteamService.userSteamId + if (steamId == null) { + SnackbarManager.show(context.getString(R.string.steam_not_logged_in)) + return@launch + } + + val resolvedContainer = container ?: ContainerUtils.getOrCreateContainer(context, appId) + val containerManager = ContainerManager(context) + containerManager.activateContainer(resolvedContainer) + + SnackbarManager.show(context.getString(R.string.library_cloud_sync_starting)) + + val prefixToPath: (String) -> String = { prefix -> + PathType.from(prefix).toAbsPath(resolvedContainer, gameId, steamId.accountID) + } + val syncResult = SteamService.forceSyncUserFiles( + appId = gameId, + prefixToPath = prefixToPath, + preferredSave = preferredSave, + ).await() + + when (syncResult.syncResult) { + SyncResult.Success -> { + SnackbarManager.show(context.getString(R.string.library_cloud_sync_success)) + } + + SyncResult.UpToDate -> { + SnackbarManager.show(context.getString(R.string.library_cloud_sync_up_to_date)) + } + + SyncResult.InProgress -> { + SnackbarManager.show(context.getString(R.string.library_cloud_sync_in_progress)) + } + + SyncResult.Conflict -> { + val localDate = Date(syncResult.localTimestamp).toString() + val remoteDate = Date(syncResult.remoteTimestamp).toString() + withContext(Dispatchers.Main) { + showInstallDialog( + gameId, + MessageDialogState( + visible = true, + type = DialogType.SYNC_CONFLICT, + title = context.getString(R.string.main_save_conflict_title), + message = context.getString(R.string.main_save_conflict_message, localDate, remoteDate), + confirmBtnText = context.getString(R.string.main_keep_remote), + dismissBtnText = context.getString(R.string.main_keep_local), + ), + ) + } + } + + else -> { + SnackbarManager.show( + context.getString( + R.string.library_cloud_sync_error, + syncResult.syncResult, + ), + ) + } + } + } + } } @Composable @@ -747,15 +820,20 @@ class SteamAppScreen : BaseAppScreen() { AppMenuOption( AppOptionMenuType.VerifyFiles, onClick = { - // Show confirmation dialog before verifying setPendingUpdateVerifyOperation(gameId, AppOptionMenuType.VerifyFiles) + val container = ContainerUtils.getOrCreateContainer(context, appId) + val verifyMessage = if (container.isLocalSavesOnly) { + context.getString(R.string.steam_verify_files_message_local_saves) + } else { + context.getString(R.string.steam_verify_files_message) + } showInstallDialog( gameId, MessageDialogState( visible = true, type = DialogType.UPDATE_VERIFY_CONFIRM, title = context.getString(R.string.steam_verify_files_title), - message = context.getString(R.string.steam_verify_files_message), + message = verifyMessage, confirmBtnText = context.getString(R.string.steam_continue), dismissBtnText = context.getString(R.string.cancel), ), @@ -795,43 +873,21 @@ class SteamAppScreen : BaseAppScreen() { properties = mapOf("game_name" to appInfo.name), ) } - CoroutineScope(Dispatchers.IO).launch { - SnackbarManager.show(context.getString(R.string.library_cloud_sync_starting)) - - val steamId = SteamService.userSteamId - if (steamId == null) { - SnackbarManager.show(context.getString(R.string.steam_not_logged_in)) - return@launch - } - - val container = ContainerUtils.getOrCreateContainer(context, appId) - - val prefixToPath: (String) -> String = { prefix -> - PathType.from(prefix).toAbsPath(container, gameId, steamId.accountID) - } - val syncResult = SteamService.forceSyncUserFiles( - appId = gameId, - prefixToPath = prefixToPath, - ).await() - - when (syncResult.syncResult) { - SyncResult.Success -> { - SnackbarManager.show(context.getString(R.string.library_cloud_sync_success)) - } - - SyncResult.UpToDate -> { - SnackbarManager.show(context.getString(R.string.library_cloud_sync_up_to_date)) - } - - else -> { - SnackbarManager.show( - context.getString( - R.string.library_cloud_sync_error, - syncResult.syncResult, - ), - ) - } - } + val container = ContainerUtils.getOrCreateContainer(context, appId) + if (container.isLocalSavesOnly) { + showInstallDialog( + gameId, + MessageDialogState( + visible = true, + type = DialogType.SYNC_CONFLICT, + title = context.getString(R.string.cloud_sync_local_saves_only_title), + message = context.getString(R.string.cloud_sync_local_saves_only_message), + confirmBtnText = context.getString(R.string.main_keep_remote), + dismissBtnText = context.getString(R.string.main_keep_local), + ), + ) + } else { + performForceCloudSync(context, appId, gameId, container = container) } }, ), @@ -1101,18 +1157,21 @@ class SteamAppScreen : BaseAppScreen() { if (operation == AppOptionMenuType.VerifyFiles) { MarkerUtils.clearInstalledPrerequisiteMarkers(getAppDirPath(gameId)) - val steamId = SteamService.userSteamId - if (steamId != null) { - val prefixToPath: (String) -> String = { prefix -> - PathType.from(prefix).toAbsPath(container, gameId, steamId.accountID) + // skip cloud sync if local saves only — verify is about game files, not saves + if (!container.isLocalSavesOnly) { + val steamId = SteamService.userSteamId + if (steamId != null) { + val prefixToPath: (String) -> String = { prefix -> + PathType.from(prefix).toAbsPath(container, gameId, steamId.accountID) + } + SteamService.forceSyncUserFiles( + appId = gameId, + prefixToPath = prefixToPath, + overrideLocalChangeNumber = -1, + ).await() + } else { + SnackbarManager.show(context.getString(R.string.steam_not_logged_in)) } - SteamService.forceSyncUserFiles( - appId = gameId, - prefixToPath = prefixToPath, - overrideLocalChangeNumber = -1, - ).await() - } else { - SnackbarManager.show(context.getString(R.string.steam_not_logged_in)) } } @@ -1163,14 +1222,32 @@ class SteamAppScreen : BaseAppScreen() { } } + DialogType.SYNC_CONFLICT -> { + { + hideInstallDialog(gameId) + PrefManager.pendingCloudResync = PrefManager.pendingCloudResync - gameId + performForceCloudSync(context, libraryItem.appId, gameId, SaveLocation.Remote) + } + } + else -> null } + val effectiveDismissClick: (() -> Unit)? = when (installDialogState.type) { + DialogType.SYNC_CONFLICT -> { + { + hideInstallDialog(gameId) + PrefManager.pendingCloudResync = PrefManager.pendingCloudResync - gameId + performForceCloudSync(context, libraryItem.appId, gameId, SaveLocation.Local) + } + } + else -> onDismissClick + } MessageDialog( visible = installDialogState.visible, onDismissRequest = onDismissRequest, onConfirmClick = onConfirmClick, - onDismissClick = onDismissClick, + onDismissClick = effectiveDismissClick, confirmBtnText = installDialogState.confirmBtnText, dismissBtnText = installDialogState.dismissBtnText, title = installDialogState.title, diff --git a/app/src/main/java/app/gamenative/utils/ContainerUtils.kt b/app/src/main/java/app/gamenative/utils/ContainerUtils.kt index ca3549a0ac..ddaca6db57 100644 --- a/app/src/main/java/app/gamenative/utils/ContainerUtils.kt +++ b/app/src/main/java/app/gamenative/utils/ContainerUtils.kt @@ -474,6 +474,15 @@ object ContainerUtils { container.setExternalDisplayMode(containerData.externalDisplayMode) container.setExternalDisplaySwap(containerData.externalDisplaySwap) container.setForceDlc(containerData.forceDlc) + // clear stale cloud cache so next sync detects divergence and shows conflict dialog + if (saveToDisk && container.isLocalSavesOnly && !containerData.localSavesOnly) { + val gameId = extractGameIdFromContainerId(container.id) + SteamService.clearCloudSyncCache(context, gameId) + PrefManager.pendingCloudResync = PrefManager.pendingCloudResync + gameId + } else if (saveToDisk && !container.isLocalSavesOnly && containerData.localSavesOnly) { + val gameId = extractGameIdFromContainerId(container.id) + PrefManager.pendingCloudResync = PrefManager.pendingCloudResync - gameId + } container.setLocalSavesOnly(containerData.localSavesOnly) container.setSteamOfflineMode(containerData.steamOfflineMode) container.setUseLegacyDRM(containerData.useLegacyDRM) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 1e503fb15f..8f05db2718 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -486,6 +486,8 @@ Gennemtving DLC Kun aktiver hvis DLC\'er ikke opdages, eller gemfiler med DLC ikke virker + Kun lokale gemte data + Deaktiver cloud-synkronisering af gemte data for dette spil og behold kun gemte data på enheden Start Steam-klient (Beta) Steam Offline-tilstand Start Steam-spil i offline-tilstand @@ -835,6 +837,7 @@ Gemfiler er allerede opdaterede Sky-synkronisering fejlede Fejl ved synkronisering af cloud-gemmer: %1$s + Synkronisering af cloud-gemmer er allerede i gang Internet påkrævet for installation @@ -1118,6 +1121,7 @@ Dette vil nulstille din container til standardkonfigurationen. Verificér filer Sørg venligst for, at dine gemfiler er uploadet til skyen eller sikkerhedskopieret før verificering, da de ellers kan blive overskrevet. + Cloud-synkronisering springes over under verificering, fordi kun lokale gemte data er aktiveret. Opdatering Sørg venligst for, at dine gemfiler er uploadet til skyen eller sikkerhedskopieret før opdatering, da de ellers kan blive overskrevet. Lagertilladelse påkrævet @@ -1310,4 +1314,6 @@ Dette downloader Lossless Scaling fra Steam for at aktivere billedgenerering. Downloader Lossless Scaling… Reducerer kvalitet for højere gennemstrømning + Kun lokale gemte data + "Kun lokale gemte data" er aktiveret for dette spil. Cloud-gemte data kan afvige fra lokale gemte data. Hvilke vil du beholde? diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a72a9f0726..a1c1b59f84 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -27,6 +27,7 @@ Dateien überprüfen Stelle bitte sicher, dass deine Spielstände vorher in die Cloud hochgeladen oder gesichert wurden, da sie sonst überschrieben werden können. Speicherstände werden heruntergeladen %1$d/%2$d + Cloud-Synchronisierung wird bei der Überprüfung übersprungen, da nur lokale Speicherstände aktiviert sind. Aktualisieren Stelle sicher, dass deine Speicherstände vor dem Aktualisieren in der Cloud gesichert oder gebackupt sind, um ein Überschreiben zu verhindern. Du musst bei Steam angemeldet sein, um diese Funktion zu nutzen @@ -624,6 +625,8 @@ Nur verwenden, wenn \'Application Load Error 3:0000065432\' auftritt. DLC erzwingen Nur aktivieren, falls DLCs nicht erkannt werden oder Spielstände mit DLC nicht funktionieren + Nur lokale Speicherstände + Cloud-Synchronisierung der Speicherstände für dieses Spiel deaktivieren und nur auf dem Gerät speichern Steam-Client starten (Beta) Steam Offline-Modus Steam-Spiele im Offlinemodus starten @@ -966,6 +969,7 @@ Spielstände sind aktuell Cloud-Synchronisierung fehlgeschlagen Cloud-Synchronisierungsfehler: %1$s + Cloud-Synchronisierung läuft bereits Internetverbindung erforderlich Installieren nur über WLAN/LAN aktiviert @@ -1380,4 +1384,6 @@ Dies lädt Lossless Scaling von Steam herunter, um Bildgenerierung zu aktivieren. Lossless Scaling wird heruntergeladen… Reduziert Qualität für höheren Durchsatz + Nur lokale Speicherstände + "Nur lokale Speicherstände" ist für dieses Spiel aktiviert. Cloud-Speicherstände können von lokalen Speicherständen abweichen. Welche möchtest du behalten? diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 27d90a70b8..dde3b3724d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -28,6 +28,7 @@ Verificar archivos Asegúrate de que tus archivos de guardado están en la nube o respaldados antes de verificar, ya que podrían sobrescribirse. Descargando archivos de guardado %1$d/%2$d + La sincronización en la nube se omitirá durante la verificación porque \"Solo guardados locales\" está activado. Actualizar Asegúrate de que tus archivos de guardado están en la nube o respaldados antes de actualizar, ya que podrían sobrescribirse. Debes iniciar sesión en Steam para usar esta función. @@ -696,6 +697,8 @@ Úsalo solo si obtienes el error \'Application Load Error 3:0000065432\'. Forzar DLC Actívalo solo si los DLC no se detectan o los archivos de guardados con DLC no funcionan. + Solo guardados locales + Desactiva la sincronización de guardados en la nube para este juego y mantén los guardados solo en el dispositivo Iniciar cliente de Steam (Beta) Reduce el rendimiento y ralentiza el inicio.\nPermite el juego en línea y soluciona problemas de DRM y de mando.\nNo todos los juegos funcionan. Modo sin conexión de Steam @@ -1023,6 +1026,7 @@ Los archivos de guardado ya están actualizados. Fallo en la sincronización con la nube. Error de sincronización en la nube: %1$s. + La sincronización en la nube ya está en curso Necesitas conexión a internet para instalar. @@ -1446,4 +1450,6 @@ Esto descargará Lossless Scaling de Steam para habilitar la generación de fotogramas. Descargando Lossless Scaling… Reduce calidad para mayor rendimiento + Solo guardados locales + "Solo guardados locales" está activado para este juego. Los guardados en la nube pueden diferir de los guardados locales. ¿Cuáles deseas conservar? diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 97f5922dec..94265e3206 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -28,6 +28,7 @@ Vérifier les fichiers Veuillez vous assurer que vos sauvegardes sont téléchargées dans le cloud ou sauvegardées avant de vérifier, car elles peuvent être écrasées. Téléchargement des fichiers de sauvegarde %1$d/%2$d + La synchronisation cloud sera ignorée pendant la vérification car seules les sauvegardes locales sont activées. Mettre à jour Veuillez vous assurer que vos sauvegardes sont téléchargées dans le cloud ou sauvegardées avant de mettre à jour, car elles peuvent être écrasées. Vous devez être connecté à Steam pour utiliser cette fonctionnalité @@ -647,6 +648,8 @@ Utiliser uniquement si vous obtenez \'Application Load Error 3:0000065432\'. Forcer les DLC N\'activer que si les DLC ne sont pas détectés ou si les sauvegardes avec DLC ne fonctionnent pas + Sauvegardes locales uniquement + Désactiver la synchronisation cloud des sauvegardes pour ce jeu et conserver les sauvegardes uniquement sur l\'appareil Lancer le client Steam (Bêta) Mode Hors Ligne Steam Lancer les jeux Steam en mode hors ligne @@ -1009,6 +1012,7 @@ Les fichiers de sauvegarde sont déjà à jour Échec de la synchronisation cloud Erreur de synchronisation des sauvegardes cloud: %1$s + La synchronisation cloud est déjà en cours Connexion internet nécessaire pour installer @@ -1440,4 +1444,6 @@ Cela téléchargera Lossless Scaling depuis Steam pour activer la génération d\'images. Téléchargement de Lossless Scaling… Réduit la qualité pour un débit plus élevé + Sauvegardes locales uniquement + "Sauvegardes locales uniquement" est activé pour ce jeu. Les sauvegardes cloud peuvent différer des sauvegardes locales. Lesquelles souhaitez-vous conserver ? diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index cc44bff4bb..19db50ad3f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -28,6 +28,7 @@ Verifica File Assicurati che i tuoi salvataggi siano caricati sul cloud o sottoposti a backup prima della verifica, altrimenti potrebbero essere sovrascritti. Download dei file di salvataggio %1$d/%2$d + La sincronizzazione cloud verrà saltata durante la verifica perché sono attivi solo i salvataggi locali. Aggiorna Assicurati che i tuoi salvataggi siano caricati sul cloud o sottoposti a backup prima dell\'aggiornamento, altrimenti potrebbero essere sovrascritti. Devi aver effettuato l\'accesso a Steam per utilizzare questa funzione @@ -651,6 +652,8 @@ Usa solo se ottieni \'Application Load Error 3:0000065432\'. Forza DLC Abilita solo se i DLC non vengono rilevati o i salvataggi con DLC non funzionano + Solo salvataggi locali + Disattiva la sincronizzazione cloud dei salvataggi per questo gioco e conserva i salvataggi solo sul dispositivo Avvia Client Steam (Beta) Modalità Offline Steam Avvia i giochi Steam in modalità offline @@ -1005,6 +1008,7 @@ I file di salvataggio sono già aggiornati Sincronizzazione cloud fallita Errore sincronizzazione salvataggi cloud: %1$s + Sincronizzazione cloud già in corso Serve internet per installare @@ -1436,4 +1440,6 @@ Scaricherà Lossless Scaling da Steam per abilitare la generazione di fotogrammi. Download di Lossless Scaling in corso… Riduce la qualità per una maggiore velocità + Solo salvataggi locali + \"Solo salvataggi locali\" è attivata per questo gioco. I salvataggi cloud potrebbero differire da quelli locali. Quali desideri conservare? diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 5c463e9eec..1e78d11e77 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -28,6 +28,7 @@ 파일 검증 검증하기 전에 저장 파일이 클라우드에 업로드되었거나 백업되었는지 확인하세요. 그렇지 않으면 덮어쓰기될 수 있습니다. 저장 파일 다운로드 중 %1$d/%2$d + 로컬 저장 데이터만 사용이 활성화되어 있어 검증 중 클라우드 동기화를 건너뜁니다. 업데이트 업데이트하기 전에 저장 파일이 클라우드에 업로드되었거나 백업되었는지 확인하세요. 그렇지 않으면 덮어쓰기될 수 있습니다. 이 기능을 사용하려면 Steam에 로그인해야 합니다 @@ -661,6 +662,8 @@ \'Application Load Error 3:0000065432\'가 발생하는 경우에만 사용하세요. DLC 강제 DLC가 감지되지 않거나 DLC가 있는 저장 파일이 작동하지 않는 경우에만 활성화 + 로컬 저장 데이터만 사용 + 이 게임의 클라우드 저장 동기화를 비활성화하고 기기에만 저장 Steam 클라이언트 실행(베타) Steam 오프라인 모드 Steam 게임을 오프라인 모드로 실행 @@ -1023,6 +1026,7 @@ 저장 파일이 이미 최신 상태입니다 클라우드 동기화 실패 클라우드 저장 파일 동기화 오류: %1$s + 클라우드 동기화가 이미 진행 중입니다 설치하려면 인터넷이 필요합니다 @@ -1444,4 +1448,6 @@ 프레임 생성을 활성화하기 위해 Steam에서 Lossless Scaling을 다운로드합니다. Lossless Scaling 다운로드 중… 처리량 향상을 위해 품질 감소 + 로컬 저장 데이터만 사용 + 이 게임에 "로컬 저장 데이터만 사용"이 활성화되어 있습니다. 클라우드 저장 데이터가 로컬 저장 데이터와 다를 수 있습니다. 어떤 데이터를 유지하시겠습니까? diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a5b681fadc..c13fbb7031 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -28,6 +28,7 @@ Zweryfikuj pliki Upewnij się, że Twoje zapisy są przesłane do chmury lub utworzono ich kopię zapasową przed weryfikacją, w przeciwnym razie mogą zostać nadpisane. Pobieranie plików zapisów %1$d/%2$d + Synchronizacja z chmurą zostanie pominięta podczas weryfikacji, ponieważ włączone są tylko lokalne zapisy. Aktualizuj Upewnij się, że Twoje zapisy są przesłane do chmury lub utworzono ich kopię zapasową przed aktualizacją, w przeciwnym razie mogą zostać nadpisane. Musisz być zalogowany do Steam, aby użyć tej funkcji @@ -660,6 +661,8 @@ Używaj tylko w przypadku błędu \'Application Load Error\'. Może zmniejszyć FPS. Wymuś DLC Włącz tylko jeśli DLC nie są wykrywane lub zapisy z DLC nie działają + Tylko lokalne zapisy + Wyłącz synchronizację zapisów z chmurą dla tej gry i przechowuj zapisy tylko na urządzeniu Uruchom Klienta Steam (Beta) Tryb Offline Steam Uruchamiaj gry Steam w trybie offline @@ -1022,6 +1025,7 @@ Pliki zapisu są już aktualne Synchronizacja z chmurą nie powiodła się Błąd synchronizacji zapisów w chmurze: %1$s + Synchronizacja z chmurą już trwa Wymagany internet do instalacji @@ -1443,4 +1447,6 @@ Spowoduje to pobranie Lossless Scaling ze Steam, aby umożliwić generowanie klatek. Pobieranie Lossless Scaling… Zmniejsza jakość dla wyższej przepustowości + Tylko lokalne zapisy + "Tylko lokalne zapisy" jest włączone dla tej gry. Zapisy w chmurze mogą różnić się od lokalnych zapisów. Które chcesz zachować? diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4090a95f42..332d303b9a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -486,6 +486,8 @@ Forçar DLC Ative somente se os DLCs não forem detectados ou os saves com DLC não estiverem funcionando + Apenas saves locais + Desativar sincronização de saves na nuvem para este jogo e manter saves apenas no dispositivo Iniciar o cliente Steam (Beta) Modo Offline do Steam Iniciar jogos Steam no modo offline @@ -835,6 +837,7 @@ Os arquivos de save já estão atualizados Sincronização na nuvem falhou Erro na sincronização de salvamentos na nuvem: %1$s + A sincronização na nuvem já está em andamento Internet necessária para instalar @@ -1118,6 +1121,7 @@ Isso redefinirá seu contêiner para a configuração padrão. Verificar arquivos Certifique-se de que seus saves foram enviados para a nuvem ou foram feitos backups antes de verificar, caso contrário, eles podem ser sobrescritos. + A sincronização na nuvem será ignorada durante a verificação porque apenas os saves locais estão ativados. Atualização Certifique-se de que seus saves foram enviados para a nuvem ou foram feitos backups antes de atualizar, caso contrário, eles podem ser sobrescritos. Permissão de armazenamento necessária @@ -1310,4 +1314,6 @@ Isso baixará o Lossless Scaling do Steam para ativar a geração de quadros. Baixando Lossless Scaling… Reduz qualidade para maior taxa de processamento + Apenas saves locais + "Apenas saves locais" está ativado para este jogo. Os saves na nuvem podem ser diferentes dos saves locais. Quais você deseja manter? diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ac772c39bb..670fd8b7de 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -28,6 +28,7 @@ Verificare fișiere Asigură-te că salvările sunt încărcate în cloud sau copiate în altă parte înainte de verificare, deoarece pot fi suprascrise. Se descarcă fișierele de salvare %1$d/%2$d + Sincronizarea cloud va fi omisă în timpul verificării deoarece opțiunea „Doar salvări locale” este activată. Actualizare Asigură-te că salvările sunt încărcate în cloud sau copiate în altă parte înainte de actualizare, deoarece pot fi suprascrise. Trebuie să fii autentificat în Steam pentru a folosi această funcție @@ -652,6 +653,8 @@ Folosește doar dacă primești \'Application Load Error 3:0000065432\'. Forțează DLC Activează doar dacă DLC‑urile nu sunt detectate sau salvările cu DLC nu funcționează + Doar salvări locale + Dezactivează sincronizarea salvărilor în cloud pentru acest joc și păstrează salvările doar pe dispozitiv Pornește Steam Client (Beta) Mod Offline Steam Pornește jocurile Steam în mod offline @@ -1013,6 +1016,7 @@ Fișierele de salvare sunt deja la zi Sincronizare cloud eșuată Eroare la sincronizarea salvărilor din cloud: %1$s + Sincronizarea cloud este deja în curs Este necesar internet pentru instalare @@ -1445,4 +1449,6 @@ Aceasta va descărca Lossless Scaling din Steam pentru a activa generarea de cadre. Se descarcă Lossless Scaling… Reduce calitatea pentru un debit mai mare + Doar salvări locale + Opțiunea „Doar salvări locale” este activată pentru acest joc. Salvările din cloud pot fi diferite de cele locale. Pe care vrei să le păstrezi? diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3106a5af4d..ab9afbcbb0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -422,6 +422,8 @@ Версия FEXCore Принудительно включить DLC Включайте только если DLC не обнаружены или сохранения с DLC не работают + Только локальные сохранения + Отключить облачную синхронизацию сохранений для этой игры и хранить сохранения только на устройстве Синхронизация кадров Исполняемый файл не смог быть автоматически выбран. Пожалуйста, установите путь исполняемого файла в настройках контейнера. Невозможно запустить @@ -571,6 +573,7 @@ Ubuntu RootFs - releases.ubuntu.com/focal Файлы сохранения уже актуальны Ошибка синхронизации облака Ошибка синхронизации облачного сохранения: %1$s + Синхронизация облачных сохранений уже выполняется Неизвестно Совместимо Конфигурация %s @@ -1131,6 +1134,7 @@ https://gamenative.app Пожалуйста, убедитесь, что ваши сохранения загружены в облако или скопированы, поскольку они могут быть перезаписаны. Обновление Пожалуйста, убедитесь, что ваши сохранения загружены в облако или скопированы, поскольку они могут быть перезаписаны. + Облачная синхронизация будет пропущена при проверке, так как включены только локальные сохранения. Проверить файлы Информация о хранилище %1$s, %2$s @@ -1373,4 +1377,6 @@ https://gamenative.app Это загрузит Lossless Scaling из Steam для включения генерации кадров. Загрузка Lossless Scaling… Снижает качество для повышения пропускной способности + Только локальные сохранения + "Только локальные сохранения" включено для этой игры. Облачные сохранения могут отличаться от локальных. Какие вы хотите оставить? diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 863e48f29d..504eacafd9 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -28,6 +28,7 @@ Перевірити файли Будь ласка, переконайтеся, що ваші збереження завантажено у хмару або створено їхню резервну копію перед перевіркою, оскільки інакше вони можуть бути перезаписані. Завантаження файлів збережень %1$d/%2$d + Хмарна синхронізація буде пропущена під час перевірки, оскільки увімкнено лише локальні збереження. Оновити Будь ласка, переконайтеся, що ваші збереження завантажено у хмару або створено їхню резервну копію перед оновленням, оскільки інакше вони можуть бути перезаписані. Ви повинні увійти в Steam, щоб використовувати цю функцію @@ -646,6 +647,8 @@ Використовувати лише якщо з\'являється \'Application Load Error 3:0000065432\'. Примусити DLC Увімкнути лише тоді, якщо не виявлено DLC або не працюють збереження з DLC + Лише локальні збереження + Вимкнути хмарну синхронізацію збережень для цієї гри та зберігати лише на пристрої Запустити клієнт Steam (Бета) Режим Офлайн Steam Запускати ігри Steam в офлайн режимі @@ -1008,6 +1011,7 @@ Файли збереження вже актуальні Помилка синхронізації з хмарою Помилка синхронізації з хмарою: %1$s + Синхронізація з хмарою вже виконується Потрібне інтернет-з\'єднання для інсталяції @@ -1439,4 +1443,6 @@ Це завантажить Lossless Scaling зі Steam для увімкнення генерації кадрів. Завантаження Lossless Scaling… Знижує якість для вищої пропускної здатності + Лише локальні збереження + "Лише локальні збереження" увімкнено для цієї гри. Хмарні збереження можуть відрізнятися від локальних. Які ви хочете залишити? diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index dd00a899ea..4ecbf13942 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -28,6 +28,7 @@ 验证文件 验证前请确保存档已上传至云端或已备份,否则存档可能会被覆盖 正在下载存档文件 %1$d/%2$d + 由于已启用仅本地存档,验证期间将跳过云同步。 更新 更新前请确保存档已上传至云端或已备份,否则存档可能会被覆盖 需要存储权限 @@ -642,6 +643,8 @@ 仅在出现\'应用程序加载错误\'时使用。可能会降低帧率。 强制开启DLC 仅在未检测到 DLC 或包含 DLC 的存档无法运作时启用 + 仅本地存档 + 禁用此游戏的云存档同步,仅在设备上保存 启动 Steam 客户端(Beta) Steam 离线模式 以离线模式启动 Steam 游戏 @@ -1004,6 +1007,7 @@ 云存档同步已是最新状态 云存档同步失败 云存档同步错误:%1$s + 云存档同步正在进行中 需联网安装 @@ -1446,10 +1450,6 @@ 未知 不兼容 - - 仅本地存档 - 禁用此游戏的云存档同步,仅将存档保存在本地设备上 - 正在下载 %s @@ -1513,4 +1513,6 @@ 将从 Steam 下载 Lossless Scaling 以启用帧生成。 正在下载 Lossless Scaling… 降低画质以提升吞吐量 - \ No newline at end of file + 仅本地存档 + 此游戏已启用\u201C仅本地存档\u201D。云端存档可能与本地存档不同。您想保留哪个? + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 2fb11d37bd..9aed27d50d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -28,6 +28,7 @@ 驗證檔案 請確保您的存檔已上傳至雲端或已備份, 然後再進行驗證, 否則存檔可能會被覆蓋 正在下載存檔檔案 %1$d/%2$d + 由於已啟用僅本地存檔,驗證期間將跳過雲端同步。 更新 請確保您的存檔已上傳至雲端或已備份, 然後再進行驗證, 否則存檔可能會被覆蓋 需要存儲權限 @@ -644,6 +645,8 @@ 僅在出現\'應用程式載入錯誤\'時使用。可能會降低幀率。 強制開啟DLC 僅在未偵測到 DLC 或包含 DLC 的存檔無法運作時啟用 + 僅本地存檔 + 停用此遊戲的雲端存檔同步,僅在裝置上保存 啟動 Steam 用戶端 (Beta) Steam 離線模式 以離線模式啟動 Steam 遊戲 @@ -1007,6 +1010,7 @@ 儲存檔案已更新至最新版本 雲端同步失敗 雲端存檔同步錯誤:%1$s + 雲端存檔同步正在進行中 需連網安裝 @@ -1445,9 +1449,6 @@ 未知 不相容 - 僅限本地存檔 - 停用此遊戲的雲端存檔同步,僅將存檔儲存在本地裝置上 - 正在下載 %s 開啟商店頁面 @@ -1505,4 +1506,6 @@ 將從 Steam 下載 Lossless Scaling 以啟用幀生成。 正在下載 Lossless Scaling… 降低畫質以提升吞吐量 - \ No newline at end of file + 僅本地存檔 + 此遊戲已啟用「僅本地存檔」。雲端存檔可能與本地存檔不同。您想保留哪一個? + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e96128cb7..073bdcfd28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ Verify Files Please ensure your saves are uploaded to the cloud or backed up before verifying, as they may be overwritten otherwise. Downloading save files %1$d/%2$d + Cloud sync will be skipped during verification because local saves only is enabled. Update Please ensure your saves are uploaded to the cloud or backed up before updating, as they may be overwritten otherwise. You must be logged into Steam to use this feature @@ -1045,6 +1046,7 @@ Cloud sync completed successfully Save files are already up to date Cloud sync failed + Cloud sync is already in progress Cloud sync error: %1$s @@ -1104,6 +1106,8 @@ Keep remote Choose Save Version We fixed cloud save path handling for this game and found different local and cloud saves. Pick one version to keep.\n\nLocal:\n\t%1$s\nCloud:\n\t%2$s + Local Saves Only + "Local saves only" is enabled for this game. Cloud saves may differ from local saves. Which would you like to keep? Sync operation is taking too long. Please try launching the game again in a moment. diff --git a/app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt b/app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt index 054d238840..344f8dfcc1 100644 --- a/app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt +++ b/app/src/test/java/app/gamenative/service/SteamAutoCloudTest.kt @@ -3,6 +3,7 @@ package app.gamenative.service import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider +import app.gamenative.PrefManager import app.gamenative.data.ConfigInfo import app.gamenative.data.FileChangeLists import app.gamenative.data.PostSyncInfo @@ -26,6 +27,7 @@ import `in`.dragonbra.javasteam.steam.handlers.steamcloud.SteamCloud import `in`.dragonbra.javasteam.steam.handlers.steamcloud.FileDownloadInfo import `in`.dragonbra.javasteam.steam.steamclient.configuration.SteamConfiguration import app.gamenative.enums.SyncResult +import app.gamenative.utils.CURRENT_UFS_PARSE_VERSION import `in`.dragonbra.javasteam.util.crypto.CryptoHelper import kotlinx.coroutines.runBlocking import okhttp3.Call @@ -80,6 +82,10 @@ class SteamAutoCloudTest { every { Net.httpForParallelDownloads(any()) } returns mockParallelHttpClient context = ApplicationProvider.getApplicationContext() + + // PrefManager uses a real Robolectric-backed DataStore so pendingCloudResync persists + PrefManager.init(context) + tempDir = File.createTempFile("steam_autocloud_test_", null) tempDir.delete() tempDir.mkdirs() @@ -246,6 +252,7 @@ class SteamAutoCloudTest { @After fun tearDown() { unmockkObject(Net) + PrefManager.pendingCloudResync = emptySet() // Clean up ImageFs directory first (files created in wineprefix) // This is critical because ImageFs uses context.getFilesDir() which is inside Robolectric's temp directory @@ -2396,46 +2403,17 @@ class SteamAutoCloudTest { ) } - // ── Scenario 16: DB cache wiped by destructive migration, local == remote ── - // Repro of the "every steam game reports conflict post-update" bug. When - // fallbackToDestructiveMigration wipes file_change_lists but local save files - // are byte-identical to the cloud manifest, we must rehydrate the cache - // silently and NOT show a conflict dialog. + // ── Scenario 14: pendingCloudResync flag suppresses upgrade conflict text ── @Test - fun dbCleared_localMatchesRemote_rehydratesSilently_noConflict() = runBlocking { + fun cacheCleared_pendingResync_conflictWithoutUpgradeText() = runBlocking { db.appChangeNumbersDao().deleteByAppId(steamAppId) db.appFileChangeListsDao().deleteByAppId(steamAppId) - - // the 5 files created in setUp() — cloud manifest must match exactly. - // local scan basePath = %WinMyDocuments%/My Games/TestGame/Steam/{id}, - // files live under SaveGames/ → filename (relativized) includes that prefix. - val localFiles = mapOf( - "SaveGames/AutoSaveData.sav" to "autosave content".toByteArray(), - "SaveGames/SaveData_0.sav" to "savedata0 content".toByteArray(), - "SaveGames/ContinueSaveData.sav" to "continue content".toByteArray(), - "SaveGames/SaveData_1.sav" to "savedata1 content".toByteArray(), - "SaveGames/SystemData_0.sav" to "systemdata content".toByteArray(), - ) - assertTrue("Precondition: local save files exist", saveFilesDir.listFiles()!!.isNotEmpty()) - val cloudFiles = localFiles.map { (name, content) -> - val m = mock() - whenever(m.filename).thenReturn(name) - whenever(m.shaFile).thenReturn(sha1(content)) - whenever(m.pathPrefixIndex).thenReturn(0) - whenever(m.timestamp).thenReturn(Date()) - whenever(m.rawFileSize).thenReturn(content.size) - m - } + PrefManager.pendingCloudResync = setOf(steamAppId) - val cloudFileChangeList = makeCloudFileChangeList( - cloudChangeNumber = 5, - files = cloudFiles, - pathPrefixes = listOf("%WinMyDocuments%/My Games/TestGame/Steam/76561198025127569"), - ) every { mockSteamCloud.getAppFileListChange(any(), any(), any()) } returns - CompletableFuture.completedFuture(cloudFileChangeList) + CompletableFuture.completedFuture(makeCloudFileChangeList(cloudChangeNumber = 5)) val testApp = db.steamAppDao().findApp(steamAppId)!! val result = SteamAutoCloud.syncUserFiles( @@ -2448,31 +2426,41 @@ class SteamAutoCloudTest { ).await() assertNotNull(result) - assertEquals( - "local matches remote exactly — should be UpToDate, not Conflict", - SyncResult.UpToDate, - result!!.syncResult, - ) + assertEquals("Should still be Conflict", SyncResult.Conflict, result!!.syncResult) assertNull( - "no conflict dialog — conflictUfsVersion must be null", + "conflictUfsVersion should be null — generic text, not upgrade", result.conflictUfsVersion, ) - assertEquals("no downloads", 0, result.filesDownloaded) - assertEquals("no uploads", 0, result.filesUploaded) + } - // cache must be rehydrated so next launch doesn't trip the same path - val rehydrated = db.appFileChangeListsDao().getByAppId(steamAppId) - assertNotNull("cache should be rehydrated", rehydrated) - assertEquals( - "rehydrated cache should contain all 5 local files", - 5, - rehydrated!!.userFileInfo.size, - ) - val rehydratedCn = db.appChangeNumbersDao().getByAppId(steamAppId) + // ── Scenario 15: Without pendingCloudResync, conflict sets conflictUfsVersion ── + @Test + fun cacheCleared_noPendingResync_conflictWithUpgradeText() = runBlocking { + db.appChangeNumbersDao().deleteByAppId(steamAppId) + db.appFileChangeListsDao().deleteByAppId(steamAppId) + assertTrue("Precondition: local save files exist", saveFilesDir.listFiles()!!.isNotEmpty()) + + PrefManager.pendingCloudResync = emptySet() + + every { mockSteamCloud.getAppFileListChange(any(), any(), any()) } returns + CompletableFuture.completedFuture(makeCloudFileChangeList(cloudChangeNumber = 5)) + + val testApp = db.steamAppDao().findApp(steamAppId)!! + val result = SteamAutoCloud.syncUserFiles( + appInfo = testApp, + clientId = clientId, + steamInstance = mockSteamService, + steamCloud = mockSteamCloud, + preferredSave = SaveLocation.None, + prefixToPath = makePrefixToPath(), + ).await() + + assertNotNull(result) + assertEquals("Should be Conflict", SyncResult.Conflict, result!!.syncResult) assertEquals( - "rehydrated change number should match cloud", - 5L, - rehydratedCn!!.changeNumber, + "conflictUfsVersion should be set for upgrade text", + CURRENT_UFS_PARSE_VERSION, + result.conflictUfsVersion, ) } }