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.