@@ -2837,7 +2837,13 @@ private fun setupXEnvironment(
28372837 envVars.put(" LC_ALL" , lc_all)
28382838 envVars.put(" MESA_DEBUG" , " silent" )
28392839 envVars.put(" MESA_NO_ERROR" , " 1" )
2840- envVars.put(" WINEPREFIX" , imageFs.wineprefix)
2840+ // Use the per-container wine prefix path directly. imageFs.wineprefix
2841+ // resolves through the global `xuser` symlink which points at whichever
2842+ // container was last activated, so writes from this Wine process can race
2843+ // a concurrent activate-container on another launch and corrupt the wrong
2844+ // container's prefix (registry hives shrink, native d3dx9/d3dcompiler
2845+ // revert to Wine builtins, etc.).
2846+ envVars.put(" WINEPREFIX" , File (container.rootDir, " .wine" ).absolutePath)
28412847 if (container.isSdlControllerAPI){
28422848 if (container.inputType == PreferredInputApi .XINPUT .ordinal || container.inputType == PreferredInputApi .AUTO .ordinal){
28432849 envVars.put(" SDL_XINPUT_ENABLED" , " 1" )
@@ -4027,15 +4033,28 @@ private fun setupWineSystemFiles(
40274033 val imageFs = ImageFs .find(context)
40284034 val appVersion = AppUtils .getVersionCode(context).toString()
40294035 val imgVersion = imageFs.getVersion().toString()
4030- val wineVersion = imageFs.getArch()
4031- val variant = imageFs.getVariant()
4036+ // imageFs.getArch() and imageFs.getVariant() read .winlator/.arch and
4037+ // .winlator/.variant which are GLOBAL files shared across containers
4038+ // (whichever container launched last writes them). When a container with a
4039+ // different Wine version launches, that flips the global file and the next
4040+ // launch of any other container sees a fake "wineVersion changed" mismatch
4041+ // and runs applyGeneralPatches, which deletes and re-extracts every common
4042+ // DLL (including d3dx9_*/d3dcompiler_*) from Wine's bundle — reverting
4043+ // installed-redist DLLs back to Wine builtins and breaking the prefix.
4044+ // Compare against per-container state instead so a foreign launch can't
4045+ // contaminate this container.
4046+ val lastAppliedWine = container.getExtra(" lastAppliedWineVersion" , " " )
4047+ val lastAppliedVariant = container.getExtra(" lastAppliedVariant" , " " )
40324048 var containerDataChanged = false
40334049
40344050 if (! container.getExtra(" appVersion" ).equals(appVersion) || ! container.getExtra(" imgVersion" ).equals(imgVersion) ||
4035- container.containerVariant != variant || (container.containerVariant == variant && container.wineVersion != wineVersion)) {
4051+ container.containerVariant != lastAppliedVariant ||
4052+ (container.containerVariant == lastAppliedVariant && container.wineVersion != lastAppliedWine)) {
40364053 applyGeneralPatches(context, container, imageFs, xServerState.value.wineInfo, containerManager, onExtractFileListener)
40374054 container.putExtra(" appVersion" , appVersion)
40384055 container.putExtra(" imgVersion" , imgVersion)
4056+ container.putExtra(" lastAppliedWineVersion" , container.wineVersion)
4057+ container.putExtra(" lastAppliedVariant" , container.containerVariant)
40394058 containerDataChanged = true
40404059 }
40414060
@@ -4056,7 +4075,7 @@ private fun setupWineSystemFiles(
40564075 )
40574076 }
40584077
4059- val needReextract = ALWAYS_REEXTRACT || xServerState.value.dxwrapper != container.getExtra(" dxwrapper" ) || container.wineVersion != wineVersion
4078+ val needReextract = ALWAYS_REEXTRACT || xServerState.value.dxwrapper != container.getExtra(" dxwrapper" ) || container.wineVersion != lastAppliedWine
40604079
40614080 Timber .i(" needReextract is " + needReextract)
40624081 Timber .i(" xServerState.value.dxwrapper is " + xServerState.value.dxwrapper)
@@ -4093,7 +4112,9 @@ private fun setupWineSystemFiles(
40934112 val openalState = if (needsOpenalDlls) " yes" else " no"
40944113 if (openalState != container.getExtra(" openal_dlls" ) || firstTimeBoot) {
40954114 if (needsOpenalDlls) {
4096- val windowsDir = File (imageFs.rootDir, ImageFs .WINEPREFIX + " /drive_c/windows" )
4115+ // Per-container path; xuser symlink is unsafe for writes (see
4116+ // applySystemTweaks rationale).
4117+ val windowsDir = File (container.rootDir, " .wine/drive_c/windows" )
40974118 TarCompressorUtils .extract(
40984119 TarCompressorUtils .Type .ZSTD , context.assets,
40994120 " wincomponents/openal.tzst" , windowsDir, onExtractFileListener,
@@ -4108,12 +4129,12 @@ private fun setupWineSystemFiles(
41084129 // on launch, so invalidate the extraction when Wine version or variant changes.
41094130 val steamExtractedKey = " ${container.wineVersion} |${container.containerVariant} "
41104131 val steamExtractedPrev = container.getExtra(" steamExtractedForWine" )
4111- val steamExeFile = File (ImageFs .find(context). rootDir.absolutePath, ImageFs . WINEPREFIX + " /drive_c/Program Files (x86)/Steam/steam.exe" )
4132+ val steamExeFile = File (container. rootDir, " .wine /drive_c/Program Files (x86)/Steam/steam.exe" )
41124133 if (steamExtractedPrev != steamExtractedKey && steamExeFile.exists()) {
41134134 // Symlink-safe delete: steamapps/common/<installdir> symlinks point at
41144135 // GameNative's own Steam dir, and a following recursive delete would wipe
41154136 // every installed game's files.
4116- val steamDir = File (ImageFs .find(context). rootDir.absolutePath, ImageFs . WINEPREFIX + " /drive_c/Program Files (x86)/Steam" )
4137+ val steamDir = File (container. rootDir, " .wine /drive_c/Program Files (x86)/Steam" )
41174138 SteamUtils .deleteTreeNoFollowSymlinks(steamDir)
41184139 }
41194140 extractSteamFiles(context, container, onExtractFileListener)
@@ -4126,7 +4147,7 @@ private fun setupWineSystemFiles(
41264147
41274148 val desktopTheme = container.desktopTheme
41284149 if ((desktopTheme + " ," + screenInfo) != container.getExtra(" desktopTheme" )) {
4129- WineThemeManager .apply (context, WineThemeManager .ThemeInfo (desktopTheme), screenInfo)
4150+ WineThemeManager .apply (context, WineThemeManager .ThemeInfo (desktopTheme), screenInfo, container )
41304151 container.putExtra(" desktopTheme" , desktopTheme + " ," + screenInfo)
41314152 containerDataChanged = true
41324153 }
@@ -4181,7 +4202,7 @@ private fun applyGeneralPatches(
41814202 Timber .i(" Attempting to extract _container_pattern.tzst with wine version " + container.wineVersion)
41824203 }
41834204 containerManager.extractContainerPatternFile(container.getWineVersion(), contentsManager, container.rootDir, null )
4184- WineUtils .applySystemTweaks(context, wineInfo)
4205+ WineUtils .applySystemTweaks(context, wineInfo, container )
41854206 container.putExtra(" graphicsDriver" , null )
41864207 container.putExtra(" desktopTheme" , null )
41874208 WinlatorPrefManager .init (context)
@@ -4215,9 +4236,11 @@ private fun extractDXWrapperFiles(
42154236 " ddraw.dll" ,
42164237 )
42174238 val splitDxWrapper = dxwrapper.split(" -" )[0 ]
4218- if (firstTimeBoot && splitDxWrapper != " vkd3d" ) cloneOriginalDllFiles(imageFs, * dlls)
4219- val rootDir = imageFs.getRootDir()
4220- val windowsDir = File (rootDir, ImageFs .WINEPREFIX + " /drive_c/windows" )
4239+ if (firstTimeBoot && splitDxWrapper != " vkd3d" ) cloneOriginalDllFiles(imageFs, container, * dlls)
4240+ // Per-container .wine paths; the xuser symlink is unsafe for writes
4241+ // (see applySystemTweaks for the cascade-corruption rationale).
4242+ val containerRoot = container.rootDir
4243+ val windowsDir = File (containerRoot, " .wine/drive_c/windows" )
42214244
42224245 when (splitDxWrapper) {
42234246 " wined3d" -> {
@@ -4226,9 +4249,9 @@ private fun extractDXWrapperFiles(
42264249 " cnc-ddraw" -> {
42274250 restoreOriginalDllFiles(context, container, containerManager, imageFs, * dlls)
42284251 val assetDir = " dxwrapper/cnc-ddraw-" + DefaultVersion .CNC_DDRAW
4229- val configFile = File (rootDir, ImageFs . WINEPREFIX + " /drive_c/ProgramData/cnc-ddraw/ddraw.ini" )
4252+ val configFile = File (containerRoot, " .wine /drive_c/ProgramData/cnc-ddraw/ddraw.ini" )
42304253 if (! configFile.isFile) FileUtils .copy(context, " $assetDir /ddraw.ini" , configFile)
4231- val shadersDir = File (rootDir, ImageFs . WINEPREFIX + " /drive_c/ProgramData/cnc-ddraw/Shaders" )
4254+ val shadersDir = File (containerRoot, " .wine /drive_c/ProgramData/cnc-ddraw/Shaders" )
42324255 FileUtils .delete(shadersDir)
42334256 FileUtils .copy(context, " $assetDir /Shaders" , shadersDir)
42344257 TarCompressorUtils .extract(
@@ -4287,11 +4310,11 @@ private fun extractDXWrapperFiles(
42874310 }
42884311 }
42894312}
4290- private fun cloneOriginalDllFiles (imageFs : ImageFs , vararg dlls : String ) {
4291- val rootDir = imageFs .rootDir
4292- val cacheDir = File (rootDir, ImageFs . CACHE_PATH + " /original_dlls" )
4313+ private fun cloneOriginalDllFiles (imageFs : ImageFs , container : Container , vararg dlls : String ) {
4314+ val containerRoot = container .rootDir
4315+ val cacheDir = File (containerRoot, " .cache /original_dlls" )
42934316 if (! cacheDir.isDirectory) cacheDir.mkdirs()
4294- val windowsDir = File (rootDir, ImageFs . WINEPREFIX + " /drive_c/windows" )
4317+ val windowsDir = File (containerRoot, " .wine /drive_c/windows" )
42954318 val dirnames = arrayOf(" system32" , " syswow64" )
42964319
42974320 for (dll in dlls) {
@@ -4308,12 +4331,12 @@ private fun restoreOriginalDllFiles(
43084331 imageFs : ImageFs ,
43094332 vararg dlls : String ,
43104333) {
4311- val rootDir = imageFs .rootDir
4334+ val containerRoot = container .rootDir
43124335 if (container.containerVariant.equals(Container .GLIBC )) {
4313- val cacheDir = File (rootDir, ImageFs . CACHE_PATH + " /original_dlls" )
4336+ val cacheDir = File (containerRoot, " .cache /original_dlls" )
43144337 val contentsManager = ContentsManager (context)
43154338 if (cacheDir.isDirectory) {
4316- val windowsDir = File (rootDir, ImageFs . WINEPREFIX + " /drive_c/windows" )
4339+ val windowsDir = File (containerRoot, " .wine /drive_c/windows" )
43174340 val dirnames = cacheDir.list()
43184341 var filesCopied = 0
43194342
@@ -4345,9 +4368,9 @@ private fun restoreOriginalDllFiles(
43454368 },
43464369 )
43474370
4348- cloneOriginalDllFiles(imageFs, * dlls)
4371+ cloneOriginalDllFiles(imageFs, container, * dlls)
43494372 } else {
4350- val windowsDir = File (rootDir, ImageFs . WINEPREFIX + " /drive_c/windows" )
4373+ val windowsDir = File (containerRoot, " .wine /drive_c/windows" )
43514374 var system32dlls: File ? = null
43524375 var syswow64dlls: File ? = null
43534376
@@ -4376,9 +4399,11 @@ private fun extractWinComponentFiles(
43764399 // shortcut: Shortcut?,
43774400 onExtractFileListener : OnExtractFileListener ? ,
43784401) {
4379- val rootDir = imageFs.rootDir
4380- val windowsDir = File (rootDir, ImageFs .WINEPREFIX + " /drive_c/windows" )
4381- val systemRegFile = File (rootDir, ImageFs .WINEPREFIX + " /system.reg" )
4402+ // Resolve through container.rootDir, not the global xuser symlink — see
4403+ // applySystemTweaks for the cascade-corruption rationale.
4404+ val containerRoot = container.rootDir
4405+ val windowsDir = File (containerRoot, " .wine/drive_c/windows" )
4406+ val systemRegFile = File (containerRoot, " .wine/system.reg" )
43824407
43834408 try {
43844409 val wincomponentsJSONObject = JSONObject (FileUtils .readString(context, " wincomponents/wincomponents.json" ))
@@ -4395,7 +4420,7 @@ private fun extractWinComponentFiles(
43954420 }
43964421 }
43974422
4398- cloneOriginalDllFiles(imageFs, * dlls.toTypedArray())
4423+ cloneOriginalDllFiles(imageFs, container, * dlls.toTypedArray())
43994424 dlls.clear()
44004425 }
44014426
@@ -4669,11 +4694,13 @@ private fun extractGraphicsDriverFiles(
46694694 )
46704695 val renderer = GPUInformation .getRenderer(null , null )
46714696 if (container.wineVersion.contains(" arm64ec" ) && renderer?.contains(" Mali" ) != true ) {
4697+ // Per-container path; xuser symlink is unsafe for writes
4698+ // (see applySystemTweaks rationale).
46724699 TarCompressorUtils .extract(
46734700 TarCompressorUtils .Type .ZSTD ,
46744701 context.assets,
46754702 " graphics_driver/zink_dlls" + " .tzst" ,
4676- File (rootDir, ImageFs . WINEPREFIX + " /drive_c/windows" ),
4703+ File (container. rootDir, " .wine /drive_c/windows" ),
46774704 )
46784705 }
46794706 }
@@ -4771,7 +4798,9 @@ private fun extractSteamFiles(
47714798 onExtractFileListener : OnExtractFileListener ? ,
47724799) {
47734800 val imageFs = ImageFs .find(context)
4774- if (File (ImageFs .find(context).rootDir.absolutePath, ImageFs .WINEPREFIX + " /drive_c/Program Files (x86)/Steam/steam.exe" ).exists()) return
4801+ // Per-container path; xuser symlink is unsafe for writes (see
4802+ // applySystemTweaks rationale).
4803+ if (File (container.rootDir, " .wine/drive_c/Program Files (x86)/Steam/steam.exe" ).exists()) return
47754804 val downloaded = File (imageFs.getFilesDir(), " steam.tzst" )
47764805 Timber .i(" Extracting steam.tzst" )
47774806 TarCompressorUtils .extract(
@@ -4802,8 +4831,9 @@ private fun readLibraryNameFromExtractedDir(destinationDir: File): String? {
48024831}
48034832private fun changeWineAudioDriver (audioDriver : String , container : Container , imageFs : ImageFs ) {
48044833 if (audioDriver != container.getExtra(" audioDriver" )) {
4805- val rootDir = imageFs.rootDir
4806- val userRegFile = File (rootDir, ImageFs .WINEPREFIX + " /user.reg" )
4834+ // Per-container path; xuser symlink is unsafe for writes (see
4835+ // applySystemTweaks rationale).
4836+ val userRegFile = File (container.rootDir, " .wine/user.reg" )
48074837 WineRegistryEditor (userRegFile).use { registryEditor ->
48084838 if (audioDriver == " alsa" ) {
48094839 registryEditor.setStringValue(" Software\\ Wine\\ Drivers" , " Audio" , " alsa" )
0 commit comments