From c997859750bc4888fc7ca8f21f09f08f89f01fd5 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Sun, 3 May 2026 19:38:35 +0200 Subject: [PATCH] =?UTF-8?q?v0.23.1=20=E2=80=94=20Fix=20crash=20OpenVR=20qu?= =?UTF-8?q?and=20SteamVR=20non=20lanc=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux bugs corrigés : 1. Marshalling bool incorrect sur VR_IsHmdPresent / VR_IsRuntimeInstalled. OpenVR renvoie un bool C++ (1 octet) ; sans MarshalAs(I1) le marshaller .NET lit 4 octets (Win32 BOOL) et obtient des valeurs random. Conséquence : VR_IsHmdPresent passait parfois à "true" même si aucun casque n'était détecté, et on tentait alors une init OpenVR sans backend → crash. 2. Garde process avant init. Même avec le bool marshalling correct, VR_IsHmdPresent peut renvoyer true si un HMD est branché en USB sans que SteamVR soit lancé. VR_InitInternal2 en mode Background tente alors de charger les drivers (lighthouse, oculus_link…) qui peuvent crash en SEH si vrserver n'a jamais été démarré sur cette session. On ajoute une vérification de la présence de vrserver.exe avant de tenter l'init, avec cache 2 s pour ne pas faire un GetProcessesByName à chaque tick. Résultat : si SteamVR est installé mais pas lancé → pill "SteamVR pas lancé" proprement, sans tenter d'init et donc sans risque de crash. Co-Authored-By: Claude Opus 4.7 (1M context) --- installer/PSLauncher.iss | 2 +- src/PSLauncher.App/PSLauncher.App.csproj | 6 +-- .../Health/OpenVR/OpenVRNative.cs | 6 +++ .../Health/OpenVR/OpenVrService.cs | 46 +++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) 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();