diff --git a/installer/PSLauncher.iss b/installer/PSLauncher.iss
index c3bae56..741630d 100644
--- a/installer/PSLauncher.iss
+++ b/installer/PSLauncher.iss
@@ -11,7 +11,7 @@
#define MyAppName "PROSERVE Launcher"
#define MyAppShortName "PS_Launcher"
-#define MyAppVersion "0.23.0"
+#define MyAppVersion "0.23.1"
#define MyAppPublisher "ASTERION VR"
#define MyAppURL "https://asterionvr.com"
#define MyAppExeName "PS_Launcher.exe"
diff --git a/src/PSLauncher.App/PSLauncher.App.csproj b/src/PSLauncher.App/PSLauncher.App.csproj
index da3942f..f9cf722 100644
--- a/src/PSLauncher.App/PSLauncher.App.csproj
+++ b/src/PSLauncher.App/PSLauncher.App.csproj
@@ -15,9 +15,9 @@
PROSERVE Launcher
© 2026 ASTERION VR — All rights reserved
PSLauncher.App
- 0.23.0
- 0.23.0.0
- 0.23.0.0
+ 0.23.1
+ 0.23.1.0
+ 0.23.1.0
true
diff --git a/src/PSLauncher.Core/Health/OpenVR/OpenVRNative.cs b/src/PSLauncher.Core/Health/OpenVR/OpenVRNative.cs
index 1a2e356..c834e8e 100644
--- a/src/PSLauncher.Core/Health/OpenVR/OpenVRNative.cs
+++ b/src/PSLauncher.Core/Health/OpenVR/OpenVRNative.cs
@@ -93,10 +93,16 @@ public static class OpenVRNative
Idle_Timeout = 4,
}
+ // ATTENTION : OpenVR renvoie des bool C++ (1 octet). Sans MarshalAs(I1), le
+ // marshaller .NET lit 4 octets (BOOL Win32) et obtient des valeurs random ;
+ // c'est ce qui faisait passer VR_IsHmdPresent à "true" même casque débranché,
+ // déclenchant ensuite une init qui plante quand SteamVR n'est pas lancé.
[DllImport(DllName, EntryPoint = "VR_IsRuntimeInstalled", CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
public static extern bool VR_IsRuntimeInstalled();
[DllImport(DllName, EntryPoint = "VR_IsHmdPresent", CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
public static extern bool VR_IsHmdPresent();
[DllImport(DllName, EntryPoint = "VR_InitInternal2", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
diff --git a/src/PSLauncher.Core/Health/OpenVR/OpenVrService.cs b/src/PSLauncher.Core/Health/OpenVR/OpenVrService.cs
index 821058b..2872132 100644
--- a/src/PSLauncher.Core/Health/OpenVR/OpenVrService.cs
+++ b/src/PSLauncher.Core/Health/OpenVR/OpenVrService.cs
@@ -1,6 +1,8 @@
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Logging;
+// Alias pour éviter la collision avec le namespace PSLauncher.Core.Process
+using SysProcess = System.Diagnostics.Process;
namespace PSLauncher.Core.Health.OpenVRInterop;
@@ -142,6 +144,17 @@ public sealed class OpenVrService : IOpenVrService, IDisposable
return VrRuntimeState.HmdAbsent;
}
+ // Garde supplémentaire avant l'init : vrserver.exe DOIT être actif. Sans
+ // lui, VR_InitInternal2 en mode Background peut soit hang, soit charger
+ // des drivers (lighthouse, oculus_link…) qui plantent en SEH si SteamVR
+ // n'a jamais été démarré sur cette session. Cette garde évite ce scénario
+ // au prix de ne plus rien lire jusqu'à ce que l'utilisateur lance SteamVR
+ // — ce qui est le comportement attendu de toute façon.
+ if (!IsVrServerRunning())
+ {
+ return VrRuntimeState.HmdAbsent;
+ }
+
// Init en mode Background : on lit les states sans devenir une scene app.
// L'init peut prendre 100-500 ms la première fois ; bloquer le caller est OK
// car le health loop tourne sur thread dédié.
@@ -304,6 +317,39 @@ public sealed class OpenVrService : IOpenVrService, IDisposable
catch { return null; }
}
+ ///
+ /// vrserver.exe est le process central de SteamVR ; sa présence garantit qu'on
+ /// peut init OpenVR sans déclencher de chargement de drivers risqué. Cache de
+ /// 2 s pour ne pas faire un GetProcessesByName à chaque tick.
+ ///
+ private DateTime _vrServerCacheUntil = DateTime.MinValue;
+ private bool _vrServerCacheValue;
+
+ private bool IsVrServerRunning()
+ {
+ var now = DateTime.UtcNow;
+ if (now < _vrServerCacheUntil) return _vrServerCacheValue;
+ try
+ {
+ var procs = SysProcess.GetProcessesByName("vrserver");
+ try
+ {
+ _vrServerCacheValue = procs.Length > 0;
+ }
+ finally
+ {
+ foreach (var p in procs) p.Dispose();
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogDebug(ex, "vrserver process probe failed, assuming absent");
+ _vrServerCacheValue = false;
+ }
+ _vrServerCacheUntil = now.AddSeconds(2);
+ return _vrServerCacheValue;
+ }
+
private void MarkSessionBroken()
{
ShutdownInternal();