feat(storage): reset a game's Wine prefix from Game data

This commit is contained in:
Kurt Himebauch committed 2026-10-28 11:50:54 -04:00
1 parent 27bdc68867
commit 7dc447e65c
4 files changed
+26 -4

No files matched your search

@@ -19,7 +19,7 @@ enum class Bucket { GAMES, GAME_DATA, TOOLS, COMPONENTS, STORES, DESKTOP, SYSTEM
class GameUsage(
val appId: Int, val name: String, val art: File?, val library: String, val internal: Boolean, val lastPlayed: Long,
val game: Long, val prefix: Long, val shaders: Long, val workshop: Long, val shaderDir: File?,
val x64: Boolean = false,
val x64: Boolean = false, val prefixDir: File? = null,
) {
val data: Long get() = prefix + shaders + workshop
val total: Long get() = game + data
@@ -175,14 +175,15 @@ object StorageScan {
}
if (!m.installed) continue
val gameBytes = dir?.let { File(lib.root, "steamapps/common/$it") }?.takeIf { it.isDirectory }?.let { walker.measure(it) } ?: 0
val prefix = File(lib.data, "steamapps/compatdata/${m.appId}").takeIf { it.isDirectory }?.let { walker.measure(it) } ?: 0
val prefixDir = File(lib.data, "steamapps/compatdata/${m.appId}").takeIf { it.isDirectory }
val prefix = prefixDir?.let { walker.measure(it) } ?: 0
val shaderDir = File(lib.data, "steamapps/shadercache/${m.appId}").takeIf { it.isDirectory }
val shaders = shaderDir?.let { walker.measure(it) } ?: 0
val workshop = File(lib.root, "steamapps/workshop/content/${m.appId}").takeIf { it.isDirectory }?.let { walker.measure(it) } ?: 0
val known = art[lib.x64]?.get(m.appId)
games.add(GameUsage(
m.appId, known?.name ?: m.name, known?.art, lib.label, lib.internal, m.lastPlayed,
gameBytes, prefix, shaders, workshop, shaderDir, lib.x64,
gameBytes, prefix, shaders, workshop, shaderDir, lib.x64, prefixDir,
))
}
}
@@ -140,6 +140,11 @@ object StorageState {
FileOps.delete(dir)
})
fun resetPrefix(context: Context, appId: Int, x64: Boolean = false) = act(context, "prefix-$appId", { _, fresh ->
val dir = fresh.games.firstOrNull { it.appId == appId && it.x64 == x64 }?.prefixDir ?: return@act false
FileOps.delete(dir)
})
fun removeLeftover(context: Context, l: Leftover) = act(context, "leftover-${l.appId}", { _, fresh ->
val leftover = fresh.leftovers.firstOrNull { it.appId == l.appId && it.prefix?.path == l.prefix?.path && it.shaders?.path == l.shaders?.path }
?: return@act false
@@ -846,6 +846,7 @@ private fun GameDataView(report: StorageReport, sessionRunning: Boolean, onBack:
val ctx = LocalContext.current
val busy = sessionRunning || StorageState.cleaning
var confirm by remember { mutableStateOf<com.droiddeck.launcher.storage.Leftover?>(null) }
var confirmPrefix by remember { mutableStateOf<com.droiddeck.launcher.storage.GameUsage?>(null) }
SubHeader(stringResource(R.string.storage_game_data), stringResource(R.string.storage_game_data_hint, gb(report.prefixes), gb(report.workshop), gb(report.shaders)), onBack)
ActionStatus()
if (sessionRunning) { Note(stringResource(R.string.storage_session_running)); Spacer(Modifier.height(4.dp)) }
@@ -856,7 +857,12 @@ private fun GameDataView(report: StorageReport, sessionRunning: Boolean, onBack:
games.forEachIndexed { i, g ->
ItemRow(
gameName(g), stringResource(R.string.storage_data_parts, gb(g.prefix), gb(g.shaders), gb(g.workshop)), g.data, i == games.lastIndex,
action = { SecondaryButton(stringResource(R.string.storage_clear_shaders), compact = true, enabled = !busy && g.shaders >= ONE_MB) { StorageState.clearShaders(ctx, g.appId, g.x64) } },
action = {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
SecondaryButton(stringResource(R.string.storage_clear_shaders), compact = true, enabled = !busy && g.shaders >= ONE_MB) { StorageState.clearShaders(ctx, g.appId, g.x64) }
SecondaryButton(stringResource(R.string.storage_reset_prefix), compact = true, enabled = !busy && g.prefix >= ONE_MB) { confirmPrefix = g }
}
},
)
}
}
@@ -888,6 +894,13 @@ private fun GameDataView(report: StorageReport, sessionRunning: Boolean, onBack:
SectionTitle(stringResource(R.string.storage_data_other), null)
ListCard { ItemRow(stringResource(R.string.storage_data_other_title), stringResource(R.string.storage_data_other_detail), report.otherPrefixes, true) }
}
confirmPrefix?.let { g ->
ConfirmDialog(
stringResource(R.string.storage_reset_prefix_title, gameName(g)),
stringResource(R.string.storage_reset_prefix_confirm, gb(g.prefix)), stringResource(R.string.storage_reset_prefix),
onConfirm = { StorageState.resetPrefix(ctx, g.appId, g.x64) }, onDismiss = { confirmPrefix = null },
)
}
confirm?.let { l ->
ConfirmDialog(
stringResource(R.string.storage_remove_title, stringResource(R.string.storage_steam_app, l.appId)),
+3
View File
@@ -2959,6 +2959,9 @@
<string name="storage_data_installed">Installed games</string>
<string name="storage_data_parts">Prefix %1$s · shaders %2$s · workshop %3$s</string>
<string name="storage_clear_shaders">Clear shaders</string>
<string name="storage_reset_prefix">Reset prefix</string>
<string name="storage_reset_prefix_title">Reset prefix for %1$s?</string>
<string name="storage_reset_prefix_confirm">Deletes its Wine prefix (%1$s) and recreates it on the next launch. Saves, settings and installed redistributables inside it are lost; Steam Cloud saves stay in your account.</string>
<string name="storage_data_leftovers">Uninstalled games</string>
<string name="storage_steam_app">Steam app %1$d</string>
<string name="storage_leftover_unsure_detail">Its prefix still holds files, which can include local saves</string>