@@ -306,7 +306,7 @@ object SteamUtils {
306306 // another concurrent launch and corrupt that game's prefix).
307307 val container = ContainerUtils .getContainer(context, appId)
308308 autoLoginUserChanges(imageFs, container)
309- setupLightweightSteamConfig(imageFs, SteamService .userSteamId?.toString())
309+ setupLightweightSteamConfig(imageFs, SteamService .userSteamId?.toString(), container )
310310
311311 val rootPath = Paths .get(appDirPath)
312312 // Get ticket once for all DLLs
@@ -435,15 +435,18 @@ object SteamUtils {
435435 }
436436
437437 fun backupSteamclientFiles (context : Context , steamAppId : Int ) {
438- val imageFs = ImageFs .find(context)
438+ // Per-container path; imageFs.wineprefix resolves through the global xuser
439+ // symlink which is unsafe for writes (see WineUtils.applySystemTweaks).
440+ val container = ContainerUtils .getContainer(context, " STEAM_$steamAppId " )
441+ val steamDir = File (container.rootDir, " .wine/drive_c/Program Files (x86)/Steam" )
439442
440443 var backupCount = 0
441444
442- val backupDir = File (imageFs.wineprefix , " drive_c/Program Files (x86)/Steam/ steamclient_backup" )
445+ val backupDir = File (steamDir , " steamclient_backup" )
443446 backupDir.mkdirs()
444447
445448 steamClientFiles().forEach { file ->
446- val dll = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam/ $ file" )
449+ val dll = File (steamDir, file)
447450 if (dll.exists()) {
448451 Files .copy(dll.toPath(), File (backupDir, " $file .orig" ).toPath(), StandardCopyOption .REPLACE_EXISTING )
449452 backupCount++
@@ -479,13 +482,14 @@ object SteamUtils {
479482 }
480483
481484 fun restoreSteamclientFiles (context : Context , steamAppId : Int ) {
482- val imageFs = ImageFs .find(context)
485+ // Per-container path; imageFs.wineprefix resolves through the global xuser
486+ // symlink which is unsafe for writes (see WineUtils.applySystemTweaks).
487+ val container = ContainerUtils .getContainer(context, " STEAM_$steamAppId " )
488+ val origDir = File (container.rootDir, " .wine/drive_c/Program Files (x86)/Steam" )
483489
484490 var restoredCount = 0
485491
486- val origDir = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam" )
487-
488- val backupDir = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam/steamclient_backup" )
492+ val backupDir = File (origDir, " steamclient_backup" )
489493 if (backupDir.exists()) {
490494 steamClientFiles().forEach { file ->
491495 val dll = File (backupDir, " $file .orig" )
@@ -496,7 +500,7 @@ object SteamUtils {
496500 }
497501 }
498502
499- val extraDllDir = File (imageFs.wineprefix , " drive_c/Program Files (x86)/Steam/ extra_dlls" )
503+ val extraDllDir = File (origDir , " extra_dlls" )
500504 if (extraDllDir.exists()) {
501505 extraDllDir.deleteRecursively()
502506 }
@@ -599,10 +603,13 @@ object SteamUtils {
599603 * Creates configuration files that make Steam run in lightweight mode
600604 * with reduced resource usage and disabled community features
601605 */
602- private fun setupLightweightSteamConfig (imageFs : ImageFs , steamId64 : String? ) {
606+ private fun setupLightweightSteamConfig (imageFs : ImageFs , steamId64 : String? , container : Container ? = null ) {
603607 Timber .i(" Setting up lightweight steam configs" )
604608 try {
605- val steamPath = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam" )
609+ // Prefer per-container path. Fallback to imageFs.wineprefix (xuser
610+ // symlink) is kept for non-launch callers that have no container.
611+ val steamPath = container?.let { File (it.rootDir, " .wine/drive_c/Program Files (x86)/Steam" ) }
612+ ? : File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam" )
606613
607614 // Create necessary directories
608615 val userDataPath = File (steamPath, " userdata/$steamId64 " )
@@ -681,7 +688,6 @@ object SteamUtils {
681688 */
682689 private fun restoreUnpackedExecutable (context : Context , steamAppId : Int ) {
683690 try {
684- val imageFs = ImageFs .find(context)
685691 val appDirPath = SteamService .getAppDirPath(steamAppId)
686692
687693 // Convert to Wine path format
@@ -697,8 +703,11 @@ object SteamUtils {
697703 }
698704 val executableFile = " $drive :\\ ${executablePath} "
699705
700- val exe = File (imageFs.wineprefix + " /dosdevices/" + executableFile.replace(" A:" , " a:" ).replace(' \\ ' , ' /' ))
701- val unpackedExe = File (imageFs.wineprefix + " /dosdevices/" + executableFile.replace(" A:" , " a:" ).replace(' \\ ' , ' /' ) + " .unpacked.exe" )
706+ // Per-container .wine path; imageFs.wineprefix resolves through the
707+ // global xuser symlink which is unsafe for writes.
708+ val winePrefix = File (container.rootDir, " .wine" ).absolutePath
709+ val exe = File (winePrefix + " /dosdevices/" + executableFile.replace(" A:" , " a:" ).replace(' \\ ' , ' /' ))
710+ val unpackedExe = File (winePrefix + " /dosdevices/" + executableFile.replace(" A:" , " a:" ).replace(' \\ ' , ' /' ) + " .unpacked.exe" )
702711
703712 if (unpackedExe.exists()) {
704713 // Check if files are different (compare size and last modified time for efficiency)
@@ -732,10 +741,10 @@ object SteamUtils {
732741 return
733742 }
734743
735- val imageFs = ImageFs .find(context)
736-
737- // Create the steamapps folder structure
738- val steamappsDir = File (imageFs.wineprefix , " drive_c/Program Files (x86)/Steam/steamapps" )
744+ // Per-container path; imageFs.wineprefix resolves through the global
745+ // xuser symlink which is unsafe for writes.
746+ val container = ContainerUtils .getContainer(context, " STEAM_ $steamAppId " )
747+ val steamappsDir = File (container.rootDir , " .wine/ drive_c/Program Files (x86)/Steam/steamapps" )
739748 if (! steamappsDir.exists()) {
740749 steamappsDir.mkdirs()
741750 }
@@ -1344,8 +1353,13 @@ object SteamUtils {
13441353 }
13451354
13461355 /* * Writes steam.cfg with update-inhibit keys if missing. Both real-Steam and emu paths need it present. */
1347- fun ensureSteamCfg (imageFs : ImageFs ) {
1348- val cfgFile = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam/steam.cfg" )
1356+ fun ensureSteamCfg (imageFs : ImageFs , container : Container ? = null) {
1357+ // Per-container path; imageFs.wineprefix resolves through the global
1358+ // xuser symlink which is unsafe for writes. Fallback retained for
1359+ // legacy callers without a container in scope.
1360+ val cfgFile = container?.let {
1361+ File (it.rootDir, " .wine/drive_c/Program Files (x86)/Steam/steam.cfg" )
1362+ } ? : File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam/steam.cfg" )
13491363 if (cfgFile.exists()) return
13501364 try {
13511365 cfgFile.parentFile?.mkdirs()
@@ -1357,9 +1371,11 @@ object SteamUtils {
13571371 }
13581372
13591373 /* * Logs size + mtime of the Steam binaries whose unexpected change breaks launches. Diagnostic-only. */
1360- fun logSteamBinaryFingerprint (imageFs : ImageFs , tag : String ) {
1374+ fun logSteamBinaryFingerprint (imageFs : ImageFs , tag : String , container : Container ? = null ) {
13611375 try {
1362- val steamDir = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam" )
1376+ // Per-container path with symlink fallback for legacy callers.
1377+ val steamDir = container?.let { File (it.rootDir, " .wine/drive_c/Program Files (x86)/Steam" ) }
1378+ ? : File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam" )
13631379 val fmt = SimpleDateFormat (" yyyy-MM-dd HH:mm:ss" , Locale .US ).apply { timeZone = TimeZone .getDefault() }
13641380 val parts = listOf (" steam.exe" , " steamclient.dll" , " steamclient64.dll" ).map { name ->
13651381 val f = File (steamDir, name)
@@ -1379,8 +1395,8 @@ object SteamUtils {
13791395 val steamAppId = ContainerUtils .extractGameIdFromContainerId(appId)
13801396 val imageFs = ImageFs .find(context)
13811397 val container = ContainerUtils .getOrCreateContainer(context, appId)
1382- ensureSteamCfg(imageFs)
1383- logSteamBinaryFingerprint(imageFs, " restoreSteamApi:emu:$steamAppId " )
1398+ ensureSteamCfg(imageFs, container )
1399+ logSteamBinaryFingerprint(imageFs, " restoreSteamApi:emu:$steamAppId " , container )
13841400
13851401 // Update or modify localconfig.vdf
13861402 updateOrModifyLocalConfig(imageFs, container, steamAppId.toString(), SteamService .userSteamId!! .accountID.toString())
@@ -1396,7 +1412,7 @@ object SteamUtils {
13961412 MarkerUtils .removeMarker(appDirPath, Marker .STEAM_COLDCLIENT_USED )
13971413
13981414 autoLoginUserChanges(imageFs, container)
1399- setupLightweightSteamConfig(imageFs, SteamService .userSteamId!! .accountID.toString())
1415+ setupLightweightSteamConfig(imageFs, SteamService .userSteamId!! .accountID.toString(), container )
14001416
14011417 putBackSteamDlls(appDirPath)
14021418 restoreOriginalExecutable(context, steamAppId)
@@ -1488,8 +1504,10 @@ object SteamUtils {
14881504 Timber .i(" Checking directory: $appDirPath " )
14891505 var restoredCount = 0
14901506
1491- val imageFs = ImageFs .find(context)
1492- val dosDevicesPath = File (imageFs.wineprefix, " dosdevices/a:" )
1507+ // Per-container path; imageFs.wineprefix resolves through the global
1508+ // xuser symlink which is unsafe.
1509+ val container = ContainerUtils .getContainer(context, " STEAM_$steamAppId " )
1510+ val dosDevicesPath = File (container.rootDir, " .wine/dosdevices/a:" )
14931511
14941512 dosDevicesPath.walkTopDown().maxDepth(10 )
14951513 .filter { it.isFile && it.name.endsWith(" .original.exe" , ignoreCase = true ) }
@@ -1523,7 +1541,6 @@ object SteamUtils {
15231541 */
15241542 fun migrateGSESavesToSteamUserdata (context : Context , appId : Int , isLaunchRealSteam : Boolean = true) {
15251543 if (! isLaunchRealSteam) return
1526- val imageFs = ImageFs .find(context)
15271544 val accountId = SteamService .userSteamId?.accountID?.toInt()
15281545 ? : PrefManager .steamUserAccountId.takeIf { it != 0 }
15291546
@@ -1532,15 +1549,13 @@ object SteamUtils {
15321549 return
15331550 }
15341551
1535- val gseDir = File (
1536- imageFs.rootDir,
1537- " ${ ImageFs . WINEPREFIX } /drive_c/users/xuser/AppData/Roaming/GSE Saves/ $ appId"
1538- )
1552+ // Per-container path; imageFs.rootDir + ImageFs.WINEPREFIX resolves through
1553+ // the global xuser symlink which is unsafe for writes.
1554+ val container = ContainerUtils .getContainer(context, " STEAM_ $ appId" )
1555+ val winePrefix = File (container.rootDir, " .wine " )
15391556
1540- val steamUserdataDir = File (
1541- imageFs.rootDir,
1542- " ${ImageFs .WINEPREFIX } /drive_c/Program Files (x86)/Steam/userdata/$accountId /$appId "
1543- )
1557+ val gseDir = File (winePrefix, " drive_c/users/xuser/AppData/Roaming/GSE Saves/$appId " )
1558+ val steamUserdataDir = File (winePrefix, " drive_c/Program Files (x86)/Steam/userdata/$accountId /$appId " )
15441559
15451560 fun isDirectoryEmpty (file : File ): Boolean {
15461561 return file.isDirectory && file.list()?.isEmpty() ? : true
@@ -1613,7 +1628,6 @@ object SteamUtils {
16131628 */
16141629 fun migrateSteamUserdataToGSESaves (context : Context , appId : Int , isLaunchRealSteam : Boolean = false) {
16151630 if (isLaunchRealSteam) return
1616- val imageFs = ImageFs .find(context)
16171631 val accountId = SteamService .userSteamId?.accountID?.toInt()
16181632 ? : PrefManager .steamUserAccountId.takeIf { it != 0 }
16191633
@@ -1622,14 +1636,13 @@ object SteamUtils {
16221636 return
16231637 }
16241638
1625- val steamUserdataDir = File (
1626- imageFs.rootDir,
1627- " ${ImageFs .WINEPREFIX } /drive_c/Program Files (x86)/Steam/userdata/$accountId /$appId "
1628- )
1629- val gseDir = File (
1630- imageFs.rootDir,
1631- " ${ImageFs .WINEPREFIX } /drive_c/users/xuser/AppData/Roaming/GSE Saves/$appId "
1632- )
1639+ // Per-container path; imageFs.rootDir + ImageFs.WINEPREFIX resolves through
1640+ // the global xuser symlink which is unsafe.
1641+ val container = ContainerUtils .getContainer(context, " STEAM_$appId " )
1642+ val winePrefix = File (container.rootDir, " .wine" )
1643+
1644+ val steamUserdataDir = File (winePrefix, " drive_c/Program Files (x86)/Steam/userdata/$accountId /$appId " )
1645+ val gseDir = File (winePrefix, " drive_c/users/xuser/AppData/Roaming/GSE Saves/$appId " )
16331646
16341647 fun isDirectoryEmpty (file : File ): Boolean {
16351648 return file.isDirectory && file.list()?.isEmpty() ? : true
@@ -1716,10 +1729,12 @@ object SteamUtils {
17161729 })
17171730 }
17181731
1719- fun cleanupExtractedSteamFiles (context : Context ) {
1732+ fun cleanupExtractedSteamFiles (context : Context , container : Container ? = null ) {
17201733 try {
1721- val imageFs = ImageFs .find(context)
1722- val steamDir = File (imageFs.rootDir, ImageFs .WINEPREFIX + " /drive_c/Program Files (x86)/Steam" )
1734+ // Per-container path; imageFs.WINEPREFIX resolves through the
1735+ // global xuser symlink which is unsafe for writes.
1736+ val steamDir = container?.let { File (it.rootDir, " .wine/drive_c/Program Files (x86)/Steam" ) }
1737+ ? : File (ImageFs .find(context).rootDir, ImageFs .WINEPREFIX + " /drive_c/Program Files (x86)/Steam" )
17231738 if (! steamDir.exists()) return
17241739 val steamExe = File (steamDir, " steam.exe" )
17251740 if (! steamExe.exists()) return
@@ -1737,9 +1752,11 @@ object SteamUtils {
17371752 * Deleting userdata/<steamId>/<phantomAppId>/ breaks the association so
17381753 * shutdown completes promptly for subsequent real-Steam launches.
17391754 */
1740- fun purgePhantomAppUserdata (imageFs : ImageFs , phantomAppId : String ) {
1755+ fun purgePhantomAppUserdata (imageFs : ImageFs , phantomAppId : String , container : Container ? = null ) {
17411756 try {
1742- val userdataRoot = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam/userdata" )
1757+ // Per-container path with symlink fallback for legacy callers.
1758+ val userdataRoot = container?.let { File (it.rootDir, " .wine/drive_c/Program Files (x86)/Steam/userdata" ) }
1759+ ? : File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam/userdata" )
17431760 if (! userdataRoot.isDirectory) return
17441761 val victims = userdataRoot.listFiles { f -> f.isDirectory }?.mapNotNull { userDir ->
17451762 File (userDir, phantomAppId).takeIf { it.exists() }
@@ -2126,7 +2143,9 @@ object SteamUtils {
21262143 try {
21272144 val exeCommandLine = container.execArgs
21282145
2129- val steamPath = File (imageFs.wineprefix, " drive_c/Program Files (x86)/Steam" )
2146+ // Per-container path; imageFs.wineprefix resolves through the global
2147+ // xuser symlink which is unsafe for writes.
2148+ val steamPath = File (container.rootDir, " .wine/drive_c/Program Files (x86)/Steam" )
21302149
21312150 // Create necessary directories
21322151 val userDataPath = File (steamPath, " userdata/$steamUserId64 " )
0 commit comments