From e1d66dac260715dfd6b42391673719a13c81ae37 Mon Sep 17 00:00:00 2001 From: Soapwood Date: Tue, 6 Aug 2024 23:25:04 +0100 Subject: [PATCH] Fix VXMusic not launching on SteamVR boot in cases where Steam and VXMusic are installed on different drives. Create Registry Interface. Grab installed software paths from registry at boot. Use to compare and check existence of needed files. Remove manifest.vrmanifest. Inject VXMusicDesktop install directory into template and create manifest in AppData instead. Link SteamVR appconfig to this manifest in AppData. Should be backwards compatible. --- VXMusicDesktop/App.xaml.cs | 12 +- VXMusicDesktop/Core/RegistryInterface.cs | 157 ++++++++++++++++++ .../Core/SteamVROverlayAppsInterface.cs | 75 ++++----- VXMusicDesktop/VXMusicDesktop.csproj | 5 +- VXMusicDesktop/VXMusicSession.cs | 2 + VXMusicDesktop/manifest.vrmanifest | 16 -- 6 files changed, 194 insertions(+), 73 deletions(-) create mode 100644 VXMusicDesktop/Core/RegistryInterface.cs delete mode 100644 VXMusicDesktop/manifest.vrmanifest diff --git a/VXMusicDesktop/App.xaml.cs b/VXMusicDesktop/App.xaml.cs index 39ecfe1..0c002bf 100644 --- a/VXMusicDesktop/App.xaml.cs +++ b/VXMusicDesktop/App.xaml.cs @@ -19,13 +19,10 @@ using VXMusic; using System.Diagnostics; using System.Reflection; -using System.Threading; using VXMusic.VRChat; using VXMusicDesktop.Core; -using VXMusicDesktop.Toast; using VXMusicDesktop.Update; using Application = System.Windows.Application; -using MessageBox = System.Windows.MessageBox; namespace VXMusicDesktop { @@ -38,10 +35,7 @@ public partial class App : Application public static ServiceProvider ServiceProvider; public static VXMusicSession VXMusicSession; public static Version ApplicationVersion; - - public static readonly string VxMusicAppDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "VirtualXtensions", "VXMusic"); - + public static Process? VXMOverlayProcess; // TODO Have you bumped the version of VXMusicDesktop? @@ -63,11 +57,12 @@ public App() : base() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build(); + RegistryInterface.RunRegistryCheckOnStartup(); VXMusicSettingsSyncHandler.RunSettingsMigrationOnStartup(); var overlaySettings = new OverlaySettings() { - RuntimePath = Path.Combine(VxMusicAppDataDir, configuration["Overlay:RuntimePath"]), + RuntimePath = Path.Combine(RegistryInterface.VxMusicAppDataDir, configuration["Overlay:RuntimePath"]), OverlayAnchor = VXUserSettings.Overlay.GetOverlayAnchor() }; @@ -145,6 +140,7 @@ public static void ConfigureServices() */ services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); // Handle creating Recognition Clients based off of BYOAPI Configuration diff --git a/VXMusicDesktop/Core/RegistryInterface.cs b/VXMusicDesktop/Core/RegistryInterface.cs new file mode 100644 index 0000000..fdfe404 --- /dev/null +++ b/VXMusicDesktop/Core/RegistryInterface.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.Logging; +using Microsoft.Win32; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace VXMusicDesktop.Core; + +public class RegistryInterface +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private static List _deferredLogMessages = new List(); + + private static readonly string VxMusicInstallPathRootKey = "{{VXMUSIC_INSTALL_PATH_ROOT}}"; + + private static readonly string SteamRegistryValue = "SteamPath"; + private static readonly string SteamRegistryKey = @"HKEY_CURRENT_USER\Software\Valve\Steam"; + + private const string VX_MUSIC_REGISTRY_VALUE = "InstallPath"; + private const string VX_MUSIC_DESKTOP_EXECUTABLE_REGISTRY_VALUE = "Executable"; + private const string VX_MUSIC_REGISTRY_KEY = @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\VirtualXtensions\VXMusic"; + + public static string SteamVrAppConfigPath; + public static string SteamInstallPath; + + public static string? VxMusicVrManifestConfigPath; + public static string? VxMusicInstallPath; + public static string? VxMusicDesktopExecutablePath; + + public static string? VxMusicAppDataDir; + public static string? VxMusicVrManiFestAppDataDestinationPath; + + public RegistryInterface(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _logger = serviceProvider.GetService(typeof(ILogger)) + as ILogger ?? throw new ApplicationException("A logger must be created in service provider."); + + _logger.LogTrace("Creating SteamVROverlayAppsInterface."); + + FlushDeferredLogMessages(); + } + + public static void RunRegistryCheckOnStartup() + { + SteamInstallPath = GetRegistryEntry(SteamRegistryKey, SteamRegistryValue); + _deferredLogMessages.Add($"Found SteamVR appconfig.json path: {SteamInstallPath}"); + + VxMusicInstallPath = GetRegistryEntry(VX_MUSIC_REGISTRY_KEY, VX_MUSIC_REGISTRY_VALUE); + _deferredLogMessages.Add($"Found VXMusic Install path: {VxMusicInstallPath}"); + + VxMusicDesktopExecutablePath = GetRegistryEntry(VX_MUSIC_REGISTRY_KEY, VX_MUSIC_DESKTOP_EXECUTABLE_REGISTRY_VALUE); + _deferredLogMessages.Add($"Found VXMusicDesktop executable path: {VxMusicDesktopExecutablePath}"); + + VxMusicAppDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "VirtualXtensions", "VXMusic"); + + if (!string.IsNullOrEmpty(SteamInstallPath) && Directory.Exists(SteamInstallPath)) + { + SteamVrAppConfigPath = Path.GetFullPath(Path.Combine(SteamInstallPath, "config", "appconfig.json")); + _deferredLogMessages.Add($"Found SteamVR appconfig.json path: {SteamVrAppConfigPath}"); + } + else + { + _deferredLogMessages.Add($"DID NOT FIND SteamVR appconfig.json path: {SteamVrAppConfigPath}"); + } + + if (!string.IsNullOrEmpty(VxMusicInstallPath) && Directory.Exists(VxMusicInstallPath)) + { + VxMusicVrManifestConfigPath = Path.GetFullPath(Path.Combine(VxMusicAppDataDir, "manifest.vrmanifest")); + _deferredLogMessages.Add($"Found VXMusic VR Manifest path: {VxMusicInstallPath}\\manifest.vrmanifest"); + } + else + { + _deferredLogMessages.Add($"DID NOT FIND VXMusic VR Manifest path: {VxMusicInstallPath}"); + } + + VxMusicVrManiFestAppDataDestinationPath = Path.Combine(Path.Combine(RegistryInterface.VxMusicAppDataDir), "manifest.vrmanifest"); + + // Injecting installation path into vrmanifest as soon as possible + if (!File.Exists(VxMusicVrManiFestAppDataDestinationPath)) + { + var formattedVxMusicDesktopPath = RegistryInterface.VxMusicDesktopExecutablePath.Replace(@"\", @"\\"); + var vrManifestJson = SteamVROverlayAppsInterface.VrManifestFileTemplate.Replace("{{VXMUSIC_INSTALL_PATH_ROOT}}", formattedVxMusicDesktopPath); + + _deferredLogMessages.Add($"Creating VR Manifest in AppData -> {VxMusicVrManiFestAppDataDestinationPath}"); + _deferredLogMessages.Add($"{vrManifestJson}"); + + File.WriteAllText(VxMusicVrManiFestAppDataDestinationPath, vrManifestJson); + } + } + + private static void InjectVxMusicDesktopInstallPathIntoVrManifest(string manifestPath, string vxMusicDesktopInstallPath) + { + _deferredLogMessages.Add("Injecting VXMusicDesktop installation directory into VR Manifest"); + _deferredLogMessages.Add($"{vxMusicDesktopInstallPath} -> {manifestPath}"); + + // Read the JSON file + string jsonString = File.ReadAllText(manifestPath); + + // Parse the JSON string + var jsonObject = JsonConvert.DeserializeObject>(jsonString); + + // Update the desired value + var applications = jsonObject["applications"] as JArray; + if (applications != null) + { + foreach (var app in applications) + { + if (app["app_key"] != null && app["app_key"].ToString() == "virtualxtensions.VXMusic") + { + app["binary_path_windows"] = vxMusicDesktopInstallPath; + } + } + } + else + { + throw new Exception("Applications array not found in the JSON data."); + } + + // Serialize the updated object + string updatedJsonString = JsonConvert.SerializeObject(jsonObject, Formatting.Indented); + + // Write the JSON string back to the file + File.WriteAllText(manifestPath, updatedJsonString); + } + + private static string GetRegistryEntry(string registryKey, string registryValue) + { + try + { + // Try to read the Steam installation path from the registry + object value = Registry.GetValue(registryKey, registryValue, null); + if (value != null) return value.ToString(); + } + catch (Exception ex) + { + _deferredLogMessages.Add($"Error accessing registry entry {registryKey}: {registryValue}."); + _deferredLogMessages.Add(ex.Message); + } + + return ""; + } + + private void FlushDeferredLogMessages() + { + foreach (var message in _deferredLogMessages) + { + _logger.LogDebug(message); + } + _deferredLogMessages.Clear(); + } + +} \ No newline at end of file diff --git a/VXMusicDesktop/Core/SteamVROverlayAppsInterface.cs b/VXMusicDesktop/Core/SteamVROverlayAppsInterface.cs index b8efc58..1dfdc92 100644 --- a/VXMusicDesktop/Core/SteamVROverlayAppsInterface.cs +++ b/VXMusicDesktop/Core/SteamVROverlayAppsInterface.cs @@ -1,8 +1,6 @@ using System; using System.IO; using Microsoft.Extensions.Logging; -using Microsoft.VisualBasic.FileIO; -using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -12,18 +10,27 @@ public class SteamVROverlayAppsInterface { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; - - private static readonly string DefaultVxMusicManifestPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "VXMusic\\manifest.vrmanifest"); - private static readonly string DefaultSteamAppConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Steam\\config\\appconfig.json"); - - private string SteamAppConfigPath; - private string SteamInstallPath; - public bool SteamAppConfigPathExists => Directory.Exists(SteamAppConfigPath); - public bool SteamInstallPathExists => Directory.Exists(SteamInstallPath); + public bool SteamAppConfigPathExists => Directory.Exists(RegistryInterface.SteamVrAppConfigPath); + public bool SteamInstallPathExists => Directory.Exists(RegistryInterface.SteamInstallPath); - private static readonly string SteamRegistryValue = "SteamPath"; - private static readonly string SteamRegistryKey = @"HKEY_CURRENT_USER\Software\Valve\Steam"; + public static readonly string VrManifestFileTemplate = @" +{ + ""source"" : ""builtin"", + ""applications"": [{ + ""app_key"": ""virtualxtensions.VXMusic"", + ""launch_type"": ""binary"", + ""binary_path_windows"": ""{{VXMUSIC_INSTALL_PATH_ROOT}}"", + ""is_dashboard_overlay"": true, + + ""strings"": { + ""en_us"": { + ""name"": ""VXMusic"", + ""description"": ""VXMusic"" + } + } + }] +}"; public SteamVROverlayAppsInterface(IServiceProvider serviceProvider) { @@ -32,17 +39,12 @@ public SteamVROverlayAppsInterface(IServiceProvider serviceProvider) as ILogger ?? throw new ApplicationException("A logger must be created in service provider."); _logger.LogTrace("Creating SteamVROverlayAppsInterface."); - - SteamInstallPath = GetSteamInstallationPath(); - - if(!string.IsNullOrEmpty(SteamInstallPath)) - SteamAppConfigPath = Path.GetFullPath(Path.Combine(SteamInstallPath, "config", "appconfig.json")); } public bool InstallVxMusicAsSteamVrOverlay() { _logger.LogInformation("Installing VXMusic as a SteamVR Overlay."); - return AddManifestEntryToAppConfig(DefaultVxMusicManifestPath); + return AddManifestEntryToAppConfig(); } public bool IsManifestEntryInAppConfig() @@ -53,54 +55,37 @@ public bool IsManifestEntryInAppConfig() return false; var (manifestPaths, _) = ReadContentsOfAppConfig(); - return SteamVrManifestPathExistsInAppConfig(manifestPaths, SteamAppConfigPath); - } - - private string GetSteamInstallationPath() - { - try - { - // Try to read the Steam installation path from the registry - object value = Registry.GetValue(SteamRegistryKey, SteamRegistryValue, null); - if (value != null) return value.ToString(); - } - catch (Exception ex) - { - _logger.LogError($"Error accessing registry entry {SteamRegistryKey}."); - _logger.LogError(ex.Message); - } - - return ""; + return SteamVrManifestPathExistsInAppConfig(manifestPaths, RegistryInterface.SteamVrAppConfigPath); } - private bool AddManifestEntryToAppConfig(string vxMusicManifestFile) + private bool AddManifestEntryToAppConfig() { try { var (manifestPaths, jsonObj) = ReadContentsOfAppConfig(); // Check if the entry already exists - if (SteamVrManifestPathExistsInAppConfig(manifestPaths, vxMusicManifestFile)) + if (SteamVrManifestPathExistsInAppConfig(manifestPaths, RegistryInterface.SteamVrAppConfigPath)) { _logger.LogDebug("Manifest path already exists in config file. Exiting."); return true; } // Add a new string to the manifest_paths array - manifestPaths.Add(vxMusicManifestFile); + manifestPaths.Add(RegistryInterface.VxMusicVrManifestConfigPath); // Convert the updated JSON object back to a string string updatedJsonContent = JsonConvert.SerializeObject(jsonObj, Formatting.Indented); // Write the updated JSON content back to the file - File.WriteAllText(SteamAppConfigPath, updatedJsonContent); - _logger.LogTrace($"New path {SteamAppConfigPath} added successfully."); + File.WriteAllText(RegistryInterface.SteamVrAppConfigPath, updatedJsonContent); + _logger.LogTrace($"New path {RegistryInterface.SteamVrAppConfigPath} added successfully."); return true; } catch (FileNotFoundException) { - _logger.LogError($"Error: The file '{SteamAppConfigPath}' was not found."); + _logger.LogError($"Error: The file '{RegistryInterface.SteamVrAppConfigPath}' was not found."); } catch (JsonException jsonEx) { @@ -124,7 +109,7 @@ private bool AddManifestEntryToAppConfig(string vxMusicManifestFile) private (JArray, JObject) ReadContentsOfAppConfig() { - string jsonContent = File.ReadAllText(SteamAppConfigPath); + string jsonContent = File.ReadAllText(RegistryInterface.SteamVrAppConfigPath); var jsonObj = JsonConvert.DeserializeObject(jsonContent); @@ -132,7 +117,7 @@ private bool AddManifestEntryToAppConfig(string vxMusicManifestFile) if (manifestPaths == null) { - throw new Exception($"manifest_paths array not found in {SteamAppConfigPath}."); + throw new Exception($"manifest_paths array not found in {RegistryInterface.SteamVrAppConfigPath}."); } return (manifestPaths, jsonObj); @@ -142,7 +127,7 @@ private bool SteamVrManifestPathExistsInAppConfig(JArray manifestPaths, string p { foreach (var path in manifestPaths) { - if (path.ToString().Equals(DefaultVxMusicManifestPath, StringComparison.OrdinalIgnoreCase)) + if (path.ToString().Equals(RegistryInterface.VxMusicVrManifestConfigPath, StringComparison.OrdinalIgnoreCase)) { _logger.LogInformation("VXMusic is already added as a SteamVR Overlay."); return true; diff --git a/VXMusicDesktop/VXMusicDesktop.csproj b/VXMusicDesktop/VXMusicDesktop.csproj index ad0b2fb..e0afa78 100644 --- a/VXMusicDesktop/VXMusicDesktop.csproj +++ b/VXMusicDesktop/VXMusicDesktop.csproj @@ -9,7 +9,7 @@ VXLogo.png Images\VXLogoIcon.ico Always - 0.6.5.3 + 0.6.5.4 en VXMusicDesktop VirtualXtensions @@ -59,9 +59,6 @@ Always - - Always - diff --git a/VXMusicDesktop/VXMusicSession.cs b/VXMusicDesktop/VXMusicSession.cs index 711e46d..233c1de 100644 --- a/VXMusicDesktop/VXMusicSession.cs +++ b/VXMusicDesktop/VXMusicSession.cs @@ -48,6 +48,7 @@ public class VXMusicSession public SteamVROverlayAppsInterface SteamVrOverlayApps; public VXMusicUpdateHandler VxMusicUpdate; public VXMusicSettingsSyncHandler VXMusicSettingsSync; + public RegistryInterface RegistryInterface; public static event EventHandler LastFmLogin; //public static event EventHandler SpotifyLogin; @@ -84,6 +85,7 @@ public void Initialise() VxMusicUpdate = App.ServiceProvider.GetRequiredService(); VXMusicSettingsSync = App.ServiceProvider.GetRequiredService(); + RegistryInterface = App.ServiceProvider.GetRequiredService(); ColourSchemeManager.SetTheme(VXUserSettings.Desktop.GetCurrentDesktopTheme()); diff --git a/VXMusicDesktop/manifest.vrmanifest b/VXMusicDesktop/manifest.vrmanifest deleted file mode 100644 index e09b574..0000000 --- a/VXMusicDesktop/manifest.vrmanifest +++ /dev/null @@ -1,16 +0,0 @@ -{ - "source" : "builtin", - "applications": [{ - "app_key": "virtualxtensions.VXMusic", - "launch_type": "binary", - "binary_path_windows": "C:\\Program Files\\VXMusic\\VXMusicDesktop.exe", - "is_dashboard_overlay": true, - - "strings": { - "en_us": { - "name": "VXMusic", - "description": "VXMusic" - } - } - }] -} \ No newline at end of file