using Daybreak.Shared.Models.Async; using Daybreak.Shared.Services.DirectSong; using Daybreak.Shared.Services.Downloads; using Microsoft.Extensions.Logging; namespace Daybreak.Windows.Services.DirectSong; /// /// Windows implementation that registers DirectSong via the Windows registry. /// Uses HKEY_CURRENT_USER to avoid requiring administrator privileges. /// internal sealed class DirectSongRegistrar( ILogger logger) : IDirectSongRegistrar { // The Revival Pack extracts DirectSong files to a nested DirectSong subfolder private const string DirectSongSubdir = "DirectSong"; // Registry paths for DirectSong // Using HKCU instead of HKLM to avoid requiring admin privileges private const string DirectSongRegistryKey = @"Software\DirectSong"; private const string MusicPathValueName = "MusicPath"; private const string DsGuildWarsDll = "ds_GuildWars.dll"; private readonly ILogger logger = logger; public Task RegisterDirectSongDirectory(string installationDirectory, string registryEditorName, CancellationToken cancellationToken) { // The actual DirectSong files are in the nested DirectSong subfolder var directSongDir = Path.Combine(Path.GetFullPath(installationDirectory), DirectSongSubdir); try { // Try HKEY_CURRENT_USER first (no admin required) using var hkcuKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(DirectSongRegistryKey); if (hkcuKey is not null) { hkcuKey.SetValue(MusicPathValueName, directSongDir); this.logger.LogDebug("Set DirectSong registry in HKCU: {Path}", directSongDir); return Task.FromResult(true); } this.logger.LogWarning("Failed to create registry key in HKCU"); // Fallback to HKEY_LOCAL_MACHINE (requires admin, but try anyway) try { using var hklmKey = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(DirectSongRegistryKey); if (hklmKey is not null) { hklmKey.SetValue(MusicPathValueName, directSongDir); this.logger.LogDebug("Set DirectSong registry in HKLM: {Path}", directSongDir); return Task.FromResult(true); } } catch (UnauthorizedAccessException) { this.logger.LogWarning("Cannot write to HKLM without administrator privileges"); } return Task.FromResult(false); } catch (Exception ex) { this.logger.LogError(ex, "Failed to set DirectSong registry"); return Task.FromResult(false); } } public Task SetupDllOverrides(CancellationToken cancellationToken) { // On Windows, no DLL overrides are needed return Task.FromResult(true); } public bool IsGStreamerAvailable() { // On Windows, gstreamer is not required return true; } public string GetGStreamerInstallInstructions() { return string.Empty; } public bool ArePlatformFilesInstalled(string installationDirectory) { var fullInstallDir = Path.GetFullPath(installationDirectory); var directSongDir = Path.Combine(fullInstallDir, DirectSongSubdir); var dsGuildWarsPath = Path.Combine(directSongDir, DsGuildWarsDll); return File.Exists(dsGuildWarsPath); } public Task SetupPlatformFiles(string installationDirectory, IDownloadService downloadService, IProgress? progress, CancellationToken cancellationToken) { // On Windows, all files come from the DirectSong.7z archive - nothing extra to download return Task.FromResult(true); } public void CopyFilesToGuildWars(string installationDirectory, string gwDirectory) { var fullInstallDir = Path.GetFullPath(installationDirectory); var directSongDir = Path.Combine(fullInstallDir, DirectSongSubdir); var dsGuildWarsSrc = Path.Combine(directSongDir, DsGuildWarsDll); var dsGuildWarsDst = Path.Combine(gwDirectory, DsGuildWarsDll); if (!File.Exists(dsGuildWarsDst) && File.Exists(dsGuildWarsSrc)) { File.Copy(dsGuildWarsSrc, dsGuildWarsDst); this.logger.LogDebug("Copied {Dll} to {Path}", DsGuildWarsDll, dsGuildWarsDst); } } public void RemoveFilesFromGuildWars(string gwDirectory) { var dsGuildWarsPath = Path.Combine(gwDirectory, DsGuildWarsDll); if (File.Exists(dsGuildWarsPath)) { File.Delete(dsGuildWarsPath); this.logger.LogDebug("Removed {Dll} from {Path}", DsGuildWarsDll, gwDirectory); } } }