v0.23.1 — Fix crash OpenVR quand SteamVR non lancé
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) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
#define MyAppName "PROSERVE Launcher"
|
#define MyAppName "PROSERVE Launcher"
|
||||||
#define MyAppShortName "PS_Launcher"
|
#define MyAppShortName "PS_Launcher"
|
||||||
#define MyAppVersion "0.23.0"
|
#define MyAppVersion "0.23.1"
|
||||||
#define MyAppPublisher "ASTERION VR"
|
#define MyAppPublisher "ASTERION VR"
|
||||||
#define MyAppURL "https://asterionvr.com"
|
#define MyAppURL "https://asterionvr.com"
|
||||||
#define MyAppExeName "PS_Launcher.exe"
|
#define MyAppExeName "PS_Launcher.exe"
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<Product>PROSERVE Launcher</Product>
|
<Product>PROSERVE Launcher</Product>
|
||||||
<Copyright>© 2026 ASTERION VR — All rights reserved</Copyright>
|
<Copyright>© 2026 ASTERION VR — All rights reserved</Copyright>
|
||||||
<RootNamespace>PSLauncher.App</RootNamespace>
|
<RootNamespace>PSLauncher.App</RootNamespace>
|
||||||
<Version>0.23.0</Version>
|
<Version>0.23.1</Version>
|
||||||
<AssemblyVersion>0.23.0.0</AssemblyVersion>
|
<AssemblyVersion>0.23.1.0</AssemblyVersion>
|
||||||
<FileVersion>0.23.0.0</FileVersion>
|
<FileVersion>0.23.1.0</FileVersion>
|
||||||
|
|
||||||
<!-- Single-file self-contained publish profile (used by `dotnet publish`) -->
|
<!-- Single-file self-contained publish profile (used by `dotnet publish`) -->
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
|||||||
@@ -93,10 +93,16 @@ public static class OpenVRNative
|
|||||||
Idle_Timeout = 4,
|
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)]
|
[DllImport(DllName, EntryPoint = "VR_IsRuntimeInstalled", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
[return: MarshalAs(UnmanagedType.I1)]
|
||||||
public static extern bool VR_IsRuntimeInstalled();
|
public static extern bool VR_IsRuntimeInstalled();
|
||||||
|
|
||||||
[DllImport(DllName, EntryPoint = "VR_IsHmdPresent", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport(DllName, EntryPoint = "VR_IsHmdPresent", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
[return: MarshalAs(UnmanagedType.I1)]
|
||||||
public static extern bool VR_IsHmdPresent();
|
public static extern bool VR_IsHmdPresent();
|
||||||
|
|
||||||
[DllImport(DllName, EntryPoint = "VR_InitInternal2", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
[DllImport(DllName, EntryPoint = "VR_InitInternal2", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Logging;
|
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;
|
namespace PSLauncher.Core.Health.OpenVRInterop;
|
||||||
|
|
||||||
@@ -142,6 +144,17 @@ public sealed class OpenVrService : IOpenVrService, IDisposable
|
|||||||
return VrRuntimeState.HmdAbsent;
|
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.
|
// 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
|
// 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é.
|
// car le health loop tourne sur thread dédié.
|
||||||
@@ -304,6 +317,39 @@ public sealed class OpenVrService : IOpenVrService, IDisposable
|
|||||||
catch { return null; }
|
catch { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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()
|
private void MarkSessionBroken()
|
||||||
{
|
{
|
||||||
ShutdownInternal();
|
ShutdownInternal();
|
||||||
|
|||||||
Reference in New Issue
Block a user