From 7dc447e65c8efa76bdfadeaa094dd51001a7c258 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Wed, 28 Oct 2026 11:50:54 -0400 Subject: [PATCH] feat(storage): reset a game's Wine prefix from Game data --- .../com/droiddeck/launcher/storage/StorageScan.kt | 7 ++++--- .../droiddeck/launcher/storage/StorageState.kt | 5 +++++ .../java/com/droiddeck/launcher/ui/StoragePage.kt | 15 ++++++++++++++- app/src/main/res/values/strings.xml | 3 +++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/droiddeck/launcher/storage/StorageScan.kt b/app/src/main/java/com/droiddeck/launcher/storage/StorageScan.kt index 71eb79198..d3001bccd 100644 --- a/app/src/main/java/com/droiddeck/launcher/storage/StorageScan.kt +++ b/app/src/main/java/com/droiddeck/launcher/storage/StorageScan.kt @@ -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, )) } } diff --git a/app/src/main/java/com/droiddeck/launcher/storage/StorageState.kt b/app/src/main/java/com/droiddeck/launcher/storage/StorageState.kt index 0724dd8dd..6a8b116b1 100644 --- a/app/src/main/java/com/droiddeck/launcher/storage/StorageState.kt +++ b/app/src/main/java/com/droiddeck/launcher/storage/StorageState.kt @@ -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 diff --git a/app/src/main/java/com/droiddeck/launcher/ui/StoragePage.kt b/app/src/main/java/com/droiddeck/launcher/ui/StoragePage.kt index badd3f1f4..e13d4145c 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/StoragePage.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/StoragePage.kt @@ -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(null) } + var confirmPrefix by remember { mutableStateOf(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)), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f90afa5c..606ed1c17 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2959,6 +2959,9 @@ Installed games Prefix %1$s · shaders %2$s · workshop %3$s Clear shaders + Reset prefix + Reset prefix for %1$s? + 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. Uninstalled games Steam app %1$d Its prefix still holds files, which can include local saves