From be3b79afe4ee2490dca51b7348c0ac58228630f3 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 22:20:58 -0400 Subject: [PATCH] Fix swapped red and blue on Mali and stop silent launch hangs - AHB import chose its red/blue view swizzle from a CPU-upload capability probe, which says nothing about a gralloc buffer's channel order, so every imported guest buffer was swizzled wherever BGRA8 is sampled-capable. It now keys off the buffer's own gralloc format. - Wrapper tarballs extract against a usr/lib/.wrapper_state marker keyed to the app version, instead of only on first boot for the stock wrapper and on every single launch for leegao and gamenative. - A throw anywhere in the launch chain was logged and dropped by the background uncaught handler, leaving the preloader up forever. It now closes the preloader and reports, without tearing down a session that may still be alive. - The launch graphics driver config is overlaid on the shipped defaults, so a shortcut config saved by an older build can no longer throw on a missing key or send the literal string "null" to the wrapper. - Shortcut inputType parses tolerantly instead of through Byte.parseByte. - The GPUInformation probes no longer leak a VkInstance and a dlopen handle on their failure paths. --- app/src/main/cpp/winlator/vk/vk_image.c | 3 +- app/src/main/cpp/winlator/vulkan.c | 59 +++--- app/src/main/res/values/strings.xml | 1 + .../display/XServerDisplayActivity.java | 200 ++++++++++-------- 4 files changed, 152 insertions(+), 111 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/vk_image.c b/app/src/main/cpp/winlator/vk/vk_image.c index 83d2d171a..2c281f078 100644 --- a/app/src/main/cpp/winlator/vk/vk_image.c +++ b/app/src/main/cpp/winlator/vk/vk_image.c @@ -1235,9 +1235,8 @@ VkTexture* vkr_texture_import_ahb(VkRenderer* r, AHardwareBuffer* ahb, bool tran if (t->ycbcr != VK_NULL_HANDLE) { vi.components = format_props.samplerYcbcrConversionComponents; } else { - // fixes devices that supports vulkan bgra8 format, but doesn't support bgra8 ahb images bool swizzle_rb = format_props.format == VK_FORMAT_R8G8B8A8_UNORM - && r->caps.upload_format == VK_FORMAT_B8G8R8A8_UNORM; + && desc.format == HAL_PIXEL_FORMAT_BGRA_8888; vi.components.r = swizzle_rb ? VK_COMPONENT_SWIZZLE_B : VK_COMPONENT_SWIZZLE_IDENTITY; vi.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; vi.components.b = swizzle_rb ? VK_COMPONENT_SWIZZLE_R : VK_COMPONENT_SWIZZLE_IDENTITY; diff --git a/app/src/main/cpp/winlator/vulkan.c b/app/src/main/cpp/winlator/vulkan.c index 09035ad4c..3e5b83251 100644 --- a/app/src/main/cpp/winlator/vulkan.c +++ b/app/src/main/cpp/winlator/vulkan.c @@ -23,6 +23,26 @@ PFN_vkDestroyInstance destroyInstance; static void *vulkan_handle = NULL; +static void destroy_probe_instance(void) { + if (instance != VK_NULL_HANDLE) { + PFN_vkDestroyInstance destroy = destroyInstance; + if (destroy == NULL && vulkan_handle != NULL) { + destroy = (PFN_vkDestroyInstance)dlsym(vulkan_handle, "vkDestroyInstance"); + } + if (destroy != NULL) destroy(instance, NULL); + } + instance = VK_NULL_HANDLE; + physicalDevice = VK_NULL_HANDLE; + getPhysicalDeviceProperties = NULL; + enumerateDeviceExtensionProperties = NULL; + enumeratePhysicalDevices = NULL; + destroyInstance = NULL; + if (vulkan_handle) { + dlclose(vulkan_handle); + vulkan_handle = NULL; + } +} + static char *get_native_library_dir(JNIEnv *env, jobject context) { char *native_libdir = NULL; @@ -363,11 +383,13 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getVulkanVersionNative( if (create_instance(driverName, env, context) != VK_SUCCESS) { printf("Failed to create instance"); + destroy_probe_instance(); return (*env)->NewStringUTF(env, "Unknown"); } if (enumerate_physical_devices() != VK_SUCCESS) { printf("Failed to query physical devices"); + destroy_probe_instance(); return (*env)->NewStringUTF(env, "Unknown"); } @@ -381,12 +403,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getVulkanVersionNative( jstring result = (*env)->NewStringUTF(env, driverVersion); free(driverVersion); - destroyInstance(instance, NULL); - - if (vulkan_handle) { - dlclose(vulkan_handle); - vulkan_handle = NULL; - } + destroy_probe_instance(); return result; } @@ -399,23 +416,20 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getVendorIDNative( if (create_instance(driverName, env, context) != VK_SUCCESS) { printf("Failed to create instance"); + destroy_probe_instance(); return 0; } if (enumerate_physical_devices() != VK_SUCCESS) { printf("Failed to query physical devices"); + destroy_probe_instance(); return 0; } getPhysicalDeviceProperties(physicalDevice, &props); vendorID = props.vendorID; - destroyInstance(instance, NULL); - - if (vulkan_handle) { - dlclose(vulkan_handle); - vulkan_handle = NULL; - } + destroy_probe_instance(); return vendorID; } @@ -427,23 +441,20 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getRendererNative( if (create_instance(driverName, env, context) != VK_SUCCESS) { printf("Failed to create instance"); + destroy_probe_instance(); return (*env)->NewStringUTF(env, "Unknown"); } if (enumerate_physical_devices() != VK_SUCCESS) { printf("Failed to query physical devices"); + destroy_probe_instance(); return (*env)->NewStringUTF(env, "Unknown"); } getPhysicalDeviceProperties(physicalDevice, &props); jstring result = (*env)->NewStringUTF(env, props.deviceName); - destroyInstance(instance, NULL); - - if (vulkan_handle) { - dlclose(vulkan_handle); - vulkan_handle = NULL; - } + destroy_probe_instance(); return result; } @@ -458,11 +469,13 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensionsNative( if (create_instance(driverName, env, context) != VK_SUCCESS) { printf("Failed to create instance"); + destroy_probe_instance(); return (*env)->NewObjectArray(env, 0, stringClass, NULL); } if (enumerate_physical_devices() != VK_SUCCESS) { printf("Failed to query physical devices"); + destroy_probe_instance(); return (*env)->NewObjectArray(env, 0, stringClass, NULL); } @@ -471,6 +484,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensionsNative( if (result != VK_SUCCESS || extensionCount < 1) { printf("Failed to query extension count"); + destroy_probe_instance(); return (*env)->NewObjectArray(env, 0, stringClass, NULL); } @@ -478,6 +492,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensionsNative( malloc(sizeof(VkExtensionProperties) * extensionCount); if (!extensionProperties) { printf("Failed to allocate extension properties"); + destroy_probe_instance(); return (*env)->NewObjectArray(env, 0, stringClass, NULL); } @@ -487,6 +502,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensionsNative( if (result != VK_SUCCESS) { printf("Failed to query extensions (result=%d)", result); free(extensionProperties); + destroy_probe_instance(); return (*env)->NewObjectArray(env, 0, stringClass, NULL); } @@ -501,12 +517,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensionsNative( free(extensionProperties); - destroyInstance(instance, NULL); - - if (vulkan_handle) { - dlclose(vulkan_handle); - vulkan_handle = NULL; - } + destroy_probe_instance(); return extensions; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82dc45924..9d1525200 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1760,6 +1760,7 @@ Installed path: Launching Closing %1$s Preparing Steam environment + Launch failed: %1$s Starting Wine Starting Steam Launcher Loading Steam client diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index e848139db..cccf9e715 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -2358,7 +2358,10 @@ public void handleOnBackPressed() { startupSelection + "'"); } - this.graphicsDriverConfig = GraphicsDriverConfigUtils.parseGraphicsDriverConfig(graphicsDriverConfig); + this.graphicsDriverConfig = + GraphicsDriverConfigUtils.parseGraphicsDriverConfig(Container.DEFAULT_GRAPHICSDRIVERCONFIG); + this.graphicsDriverConfig.putAll( + GraphicsDriverConfigUtils.parseGraphicsDriverConfig(graphicsDriverConfig)); this.dxwrapperConfig = DXVKConfigUtils.parseConfig(dxwrapperConfig); Log.i("XServerDisplayActivity", "Launch DX wrapper selected: dxwrapper='" + dxwrapper + "' dxvkVersion='" + this.dxwrapperConfig.get("version") + @@ -2504,88 +2507,92 @@ public void onFailed(Exception e) { simulateConfirmInputControlsDialog(); } Executors.newSingleThreadExecutor().execute(() -> { - boolean sessionToReuse = SessionKeepAliveService.isSessionActive() && - SessionKeepAliveService.getActiveEnvironment() != null && - SessionKeepAliveService.getActiveXServer() != null; + try { + boolean sessionToReuse = SessionKeepAliveService.isSessionActive() && + SessionKeepAliveService.getActiveEnvironment() != null && + SessionKeepAliveService.getActiveXServer() != null; - UpdateService.INSTANCE.cancelPostGameCheck(); + UpdateService.INSTANCE.cancelPostGameCheck(); - if (!sessionToReuse) { - if (isSteamShortcut()) { - try { - setSteamClientVisibility(true, isColdClientEnabledForShortcut()); - } catch (Throwable t) { - Log.w("XServerDisplayActivity", - "Failed to select Steam client store before cloud sync", t); + if (!sessionToReuse) { + if (isSteamShortcut()) { + try { + setSteamClientVisibility(true, isColdClientEnabledForShortcut()); + } catch (Throwable t) { + Log.w("XServerDisplayActivity", + "Failed to select Steam client store before cloud sync", t); + } } - } - // Parallel prep (cloud sync + Steam prefix DLL/asset setup), joined before setupXEnvironment so the launcher sees a complete prefix. - java.util.concurrent.ExecutorService prepExec = - java.util.concurrent.Executors.newFixedThreadPool(2); - java.util.concurrent.Future cloudFuture = prepExec.submit(() -> { - try { - if (steamCloudHandledByAgent()) { - Log.i("XServerDisplayActivity", - "Steam cloud pre-launch sync skipped — the Steam Launcher " - + "agent runs RunAutoCloudOnAppLaunch inside the " - + "prefix"); - } else { - SteamLaunchCloudSync.syncBeforeLaunch( + // Parallel prep (cloud sync + Steam prefix DLL/asset setup), joined before setupXEnvironment so the launcher sees a complete prefix. + java.util.concurrent.ExecutorService prepExec = + java.util.concurrent.Executors.newFixedThreadPool(2); + java.util.concurrent.Future cloudFuture = prepExec.submit(() -> { + try { + if (steamCloudHandledByAgent()) { + Log.i("XServerDisplayActivity", + "Steam cloud pre-launch sync skipped — the Steam Launcher " + + "agent runs RunAutoCloudOnAppLaunch inside the " + + "prefix"); + } else { + SteamLaunchCloudSync.syncBeforeLaunch( + this, shortcut, isCloudSyncEnabledForShortcut(), + this::showLaunchPreloader); + } + EpicLaunchCloudSync.syncBeforeLaunch( + this, shortcut, isCloudSyncEnabledForShortcut(), + this::showLaunchPreloader); + GogLaunchCloudSync.syncBeforeLaunch( this, shortcut, isCloudSyncEnabledForShortcut(), this::showLaunchPreloader); + } catch (Throwable t) { + Log.w("XServerDisplayActivity", + "Pre-launch cloud sync failed", t); } - EpicLaunchCloudSync.syncBeforeLaunch( - this, shortcut, isCloudSyncEnabledForShortcut(), - this::showLaunchPreloader); - GogLaunchCloudSync.syncBeforeLaunch( - this, shortcut, isCloudSyncEnabledForShortcut(), - this::showLaunchPreloader); + }); + java.util.concurrent.Future steamFuture = isSteamShortcut() + ? prepExec.submit(() -> { + try { + setupSteamGameFiles(); + } catch (Throwable t) { + Log.w("XServerDisplayActivity", + "Pre-launch Steam game setup failed", t); + } + }) + : null; + prepExec.shutdown(); + + if (preloaderDialog != null && isSteamShortcut()) { + preloaderDialog.setStepOnUiThread(R.string.preloader_preparing_steam_environment); + } + setupWineSystemFiles(); + extractGraphicsDriverFiles(); + changeWineAudioDriver(); + + try { + if (steamFuture != null) steamFuture.get(); } catch (Throwable t) { Log.w("XServerDisplayActivity", - "Pre-launch cloud sync failed", t); + "Steam game setup wait interrupted", t); } - }); - java.util.concurrent.Future steamFuture = isSteamShortcut() - ? prepExec.submit(() -> { - try { - setupSteamGameFiles(); - } catch (Throwable t) { - Log.w("XServerDisplayActivity", - "Pre-launch Steam game setup failed", t); - } - }) - : null; - prepExec.shutdown(); - - if (preloaderDialog != null && isSteamShortcut()) { - preloaderDialog.setStepOnUiThread(R.string.preloader_preparing_steam_environment); + try { + cloudFuture.get(); + } catch (Throwable t) { + Log.w("XServerDisplayActivity", + "Cloud sync wait interrupted", t); + } + } else { + Log.i("XServerDisplayActivity", "Skipping pre-game setup for active background session"); + applyPreferredRefreshRate(); } - setupWineSystemFiles(); - extractGraphicsDriverFiles(); - changeWineAudioDriver(); try { - if (steamFuture != null) steamFuture.get(); - } catch (Throwable t) { - Log.w("XServerDisplayActivity", - "Steam game setup wait interrupted", t); - } - try { - cloudFuture.get(); - } catch (Throwable t) { - Log.w("XServerDisplayActivity", - "Cloud sync wait interrupted", t); + setupXEnvironment(); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); } - } else { - Log.i("XServerDisplayActivity", "Skipping pre-game setup for active background session"); - applyPreferredRefreshRate(); - } - - try { - setupXEnvironment(); - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); + } catch (Throwable t) { + reportLaunchFailure(t); } }); }; @@ -4494,6 +4501,21 @@ private void showLaunchPreloaderProgress(String text, int percent) { ); } + private void reportLaunchFailure(Throwable t) { + Log.e("XServerDisplayActivity", "Launch failed before the game window appeared", t); + LogManager.log(TAG, "Launch failed: " + t, this); + if (preloaderDialog != null) preloaderDialog.closeOnUiThread(); + stopWnLauncherStatusTailer(); + if (activityDestroyed.get() || isFinishing() || isDestroyed()) return; + String reason = t.getMessage(); + if (reason == null || reason.isEmpty()) reason = t.getClass().getSimpleName(); + final String message = reason; + runOnUiThread(() -> { + if (activityDestroyed.get() || isFinishing() || isDestroyed()) return; + WinToast.show(this, getString(R.string.preloader_launch_failed, message)); + }); + } + private void stopWnLauncherStatusTailer() { wnLauncherDrivesDismiss.set(false); if (wnLauncherStatusTailer == null) return; @@ -7628,7 +7650,7 @@ private void setupWineSystemFiles() { if (shortcut != null) { String shortcutInputType = shortcut.getSettingExtra("inputType", ""); if (!shortcutInputType.isEmpty()) { - inputType = Byte.parseByte(shortcutInputType); + inputType = parseSettingInt(shortcutInputType, inputType); } } boolean dinputEnabled = (inputType & WinHandler.FLAG_INPUT_TYPE_DINPUT) == WinHandler.FLAG_INPUT_TYPE_DINPUT; @@ -9495,9 +9517,7 @@ private void extractGraphicsDriverFiles() { if (firstTimeBoot) { Log.d("XServerDisplayActivity", "First time container boot, re-extracting libs"); - TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, "graphics_driver/wrapper" + ".tzst", rootDir); TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, "layers" + ".tzst", rootDir); - // extra_libs.tzst handled by the version-aware block below (covers first boot too). } // safe to re-extract: the tzst holds only usr/lib/*.so + usr/share/vulkan/*, no home/drive_c @@ -9534,19 +9554,29 @@ private void extractGraphicsDriverFiles() { boolean wantGamenative = "wrapper-gamenative".equals(graphicsDriver); File leegaoMarker = new File(rootDir, "usr/lib/.wrapper_leegao"); File gamenativeMarker = new File(rootDir, "usr/lib/.wrapper_gamenative"); - if (wantLeegao) { - TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, "graphics_driver/wrapper-leegao.tzst", rootDir); - try { leegaoMarker.createNewFile(); } catch (IOException ignored) {} - gamenativeMarker.delete(); - } else if (wantGamenative) { - TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, "graphics_driver/wrapper-gamenative.tzst", rootDir); - try { gamenativeMarker.createNewFile(); } catch (IOException ignored) {} - leegaoMarker.delete(); - } else if (leegaoMarker.exists() || gamenativeMarker.exists()) { - TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, "graphics_driver/wrapper" + ".tzst", rootDir); - TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, "layers" + ".tzst", rootDir); - leegaoMarker.delete(); - gamenativeMarker.delete(); + File wrapperStateMarker = new File(rootDir, "usr/lib/.wrapper_state"); + String wantedWrapperState = + (wantLeegao ? "leegao" : wantGamenative ? "gamenative" : "stock") + + ":" + AppUtils.getVersionCode(this); + String installedWrapperState = ""; + if (wrapperStateMarker.isFile()) { + String raw = FileUtils.readString(wrapperStateMarker); + if (raw != null) installedWrapperState = raw.trim(); + } + if (!wantedWrapperState.equals(installedWrapperState)) { + Log.i("GraphicsDriverExtraction", "Wrapper state " + installedWrapperState + + " -> " + wantedWrapperState + ", extracting"); + String wrapperAsset = wantLeegao ? "graphics_driver/wrapper-leegao.tzst" + : wantGamenative ? "graphics_driver/wrapper-gamenative.tzst" + : "graphics_driver/wrapper.tzst"; + if (TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, this, wrapperAsset, rootDir)) { + FileUtils.writeString(wrapperStateMarker, wantedWrapperState); + leegaoMarker.delete(); + gamenativeMarker.delete(); + } else { + Log.w("GraphicsDriverExtraction", "Extraction of " + wrapperAsset + " failed"); + wrapperStateMarker.delete(); + } } // libgallium_wgl.dll is present only while Windows Zink is installed — use as marker.