Skip to content

Commit

Permalink
Fix VXMusic not launching on SteamVR boot in cases where Steam and VX…
Browse files Browse the repository at this point in the history
…Music 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.
  • Loading branch information
Soapwood committed Aug 6, 2024
1 parent 346cd75 commit e1d66da
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 73 deletions.
12 changes: 4 additions & 8 deletions VXMusicDesktop/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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?
Expand All @@ -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()
};

Expand Down Expand Up @@ -145,6 +140,7 @@ public static void ConfigureServices()
*/
services.AddSingleton<App>();

services.AddSingleton<RegistryInterface>();
services.AddSingleton<VXMusicSettingsSyncHandler>();

// Handle creating Recognition Clients based off of BYOAPI Configuration
Expand Down
157 changes: 157 additions & 0 deletions VXMusicDesktop/Core/RegistryInterface.cs
Original file line number Diff line number Diff line change
@@ -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<RegistryInterface> _logger;
private static List<string> _deferredLogMessages = new List<string>();

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<RegistryInterface>))
as ILogger<RegistryInterface> ?? 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<Dictionary<string, object>>(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();
}

}
75 changes: 30 additions & 45 deletions VXMusicDesktop/Core/SteamVROverlayAppsInterface.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -12,18 +10,27 @@ public class SteamVROverlayAppsInterface
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<SteamVROverlayAppsInterface> _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)
{
Expand All @@ -32,17 +39,12 @@ public SteamVROverlayAppsInterface(IServiceProvider serviceProvider)
as ILogger<SteamVROverlayAppsInterface> ?? 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()
Expand All @@ -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)
{
Expand All @@ -124,15 +109,15 @@ private bool AddManifestEntryToAppConfig(string vxMusicManifestFile)

private (JArray, JObject) ReadContentsOfAppConfig()
{
string jsonContent = File.ReadAllText(SteamAppConfigPath);
string jsonContent = File.ReadAllText(RegistryInterface.SteamVrAppConfigPath);

var jsonObj = JsonConvert.DeserializeObject<JObject>(jsonContent);

JArray manifestPaths = (JArray) jsonObj["manifest_paths"];

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);
Expand All @@ -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;
Expand Down
5 changes: 1 addition & 4 deletions VXMusicDesktop/VXMusicDesktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageIcon>VXLogo.png</PackageIcon>
<ApplicationIcon>Images\VXLogoIcon.ico</ApplicationIcon>
<RunPostBuildEvent>Always</RunPostBuildEvent>
<Version>0.6.5.3</Version>
<Version>0.6.5.4</Version>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<Title>VXMusicDesktop</Title>
<Authors>VirtualXtensions</Authors>
Expand Down Expand Up @@ -59,9 +59,6 @@
<None Update="Overlay\Images\LastFmIconBase64.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="manifest.vrmanifest">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions VXMusicDesktop/VXMusicSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,6 +85,7 @@ public void Initialise()

VxMusicUpdate = App.ServiceProvider.GetRequiredService<VXMusicUpdateHandler>();
VXMusicSettingsSync = App.ServiceProvider.GetRequiredService<VXMusicSettingsSyncHandler>();
RegistryInterface = App.ServiceProvider.GetRequiredService<RegistryInterface>();

ColourSchemeManager.SetTheme(VXUserSettings.Desktop.GetCurrentDesktopTheme());

Expand Down
Loading

0 comments on commit e1d66da

Please sign in to comment.