Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ output.osu

.idea
.vscode
.dotnet-home/
243 changes: 203 additions & 40 deletions MapWizard.Desktop/Services/SongLibraryService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
Expand Down Expand Up @@ -290,56 +289,233 @@ private static IEnumerable<string> EnumerateWindowsInstallPaths()
{
var paths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

void AddPath(string? value)
foreach (var candidate in EnumerateWindowsInstallPathCandidates())
{
if (string.IsNullOrWhiteSpace(value))
AddNormalizedDirectory(paths, candidate);
}

var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!string.IsNullOrWhiteSpace(localAppData))
{
AddNormalizedDirectory(paths, Path.Combine(localAppData, "osu!"));
}

return paths;
}

[SupportedOSPlatform("windows")]
private static IEnumerable<string?> EnumerateWindowsInstallPathCandidates()
{
foreach (var value in EnumerateRegistryValues(RegistryHive.CurrentUser, @"Software\osu!", "path"))
{
yield return value;
}

foreach (var value in EnumerateRegistryValues(RegistryHive.LocalMachine, @"Software\osu!", "path"))
{
yield return value;
}

foreach (var value in EnumerateRegistryValues(RegistryHive.CurrentUser, @"Software\Microsoft\Windows\CurrentVersion\App Paths\osu!.exe", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.LocalMachine, @"Software\Microsoft\Windows\CurrentVersion\App Paths\osu!.exe", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.CurrentUser, @"Software\Microsoft\Windows\CurrentVersion\App Paths\osu!.exe", "Path"))
{
yield return value;
}

foreach (var value in EnumerateRegistryValues(RegistryHive.LocalMachine, @"Software\Microsoft\Windows\CurrentVersion\App Paths\osu!.exe", "Path"))
{
yield return value;
}

foreach (var value in EnumerateRegistryValues(RegistryHive.ClassesRoot, @"osu\shell\open\command", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.ClassesRoot, @"osu\DefaultIcon", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.LocalMachine, @"Software\Classes\osu\shell\open\command", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.LocalMachine, @"Software\Classes\osu\DefaultIcon", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.CurrentUser, @"Software\Classes\osu\shell\open\command", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateRegistryValues(RegistryHive.CurrentUser, @"Software\Classes\osu\DefaultIcon", null))
{
yield return ExtractExecutableDirectory(value);
}

foreach (var value in EnumerateUninstallInstallPathCandidates())
{
yield return value;
}
}

[SupportedOSPlatform("windows")]
private static IEnumerable<string?> EnumerateUninstallInstallPathCandidates()
{
const string uninstallKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Uninstall";
var candidates = new List<string?>();

foreach (var hive in new[] { RegistryHive.CurrentUser, RegistryHive.LocalMachine })
{
foreach (var view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
{
return;
RegistryKey? uninstallRoot = null;
try
{
uninstallRoot = RegistryKey.OpenBaseKey(hive, view).OpenSubKey(uninstallKeyPath);
if (uninstallRoot is null)
{
continue;
}

foreach (var subKeyName in uninstallRoot.GetSubKeyNames())
{
RegistryKey? appKey = null;
try
{
appKey = uninstallRoot.OpenSubKey(subKeyName);
if (appKey is null)
{
continue;
}

var displayName = appKey.GetValue("DisplayName")?.ToString();
if (!IsOsuDisplayName(displayName) &&
!subKeyName.Contains("osu", StringComparison.OrdinalIgnoreCase))
{
continue;
}

candidates.Add(appKey.GetValue("InstallLocation")?.ToString());
candidates.Add(ExtractExecutableDirectory(appKey.GetValue("DisplayIcon")?.ToString()));
candidates.Add(ExtractExecutableDirectory(appKey.GetValue("UninstallString")?.ToString()));
}
catch
{
// ignored
}
finally
{
appKey?.Dispose();
}
}
}
catch
{
// ignored
}
finally
{
uninstallRoot?.Dispose();
}
}
}

var trimmed = value.Trim().Trim('"');
if (File.Exists(trimmed))
return candidates;
}

[SupportedOSPlatform("windows")]
private static IEnumerable<string?> EnumerateRegistryValues(RegistryHive hive, string subKeyPath, string? valueName)
{
if (hive == RegistryHive.ClassesRoot)
{
string? classRootValue = null;
try
{
trimmed = Path.GetDirectoryName(trimmed) ?? trimmed;
classRootValue = Registry.ClassesRoot.OpenSubKey(subKeyPath)?.GetValue(valueName)?.ToString();
}

if (Directory.Exists(trimmed))
catch
{
paths.Add(trimmed);
// ignored
}
}

AddPath(ReadRegistryValue(@"HKEY_CURRENT_USER\Software\osu!", "path"));
AddPath(ReadRegistryValue(@"HKEY_LOCAL_MACHINE\Software\osu!", "path"));

AddPath(ExtractExecutableDirectory(ReadRegistryValue(@"HKEY_CLASSES_ROOT\osu\shell\open\command", null)));
AddPath(ExtractExecutableDirectory(ReadRegistryValue(@"HKEY_CLASSES_ROOT\osu\DefaultIcon", null)));
AddPath(ExtractExecutableDirectory(ReadRegistryValue(@"HKEY_LOCAL_MACHINE\Software\Classes\osu\shell\open\command", null)));
AddPath(ExtractExecutableDirectory(ReadRegistryValue(@"HKEY_LOCAL_MACHINE\Software\Classes\osu\DefaultIcon", null)));
if (!string.IsNullOrWhiteSpace(classRootValue))
{
yield return classRootValue;
}

var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!string.IsNullOrWhiteSpace(localAppData))
{
AddPath(Path.Combine(localAppData, "osu!"));
yield break;
}

foreach (var process in Process.GetProcessesByName("osu!"))
foreach (var view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
{
RegistryKey? subKey = null;
try
{
AddPath(process.MainModule?.FileName);
subKey = RegistryKey.OpenBaseKey(hive, view).OpenSubKey(subKeyPath);
}
catch
{
// ignored
}
finally

var value = subKey?.GetValue(valueName)?.ToString();
if (!string.IsNullOrWhiteSpace(value))
{
process.Dispose();
yield return value;
}

subKey?.Dispose();
}
}

return paths;
private static bool IsOsuDisplayName(string? displayName)
{
return !string.IsNullOrWhiteSpace(displayName) &&
displayName.Contains("osu", StringComparison.OrdinalIgnoreCase);
}

private static void AddNormalizedDirectory(HashSet<string> paths, string? pathCandidate)
{
if (string.IsNullOrWhiteSpace(pathCandidate))
{
return;
}

var normalized = NormalizeDirectoryPath(pathCandidate);
if (normalized is not null)
{
paths.Add(normalized);
}
}

private static string? NormalizeDirectoryPath(string pathCandidate)
{
var trimmed = pathCandidate.Trim().Trim('"');
if (trimmed.Length == 0)
{
return null;
}

if (File.Exists(trimmed))
{
trimmed = Path.GetDirectoryName(trimmed) ?? trimmed;
}

return Directory.Exists(trimmed) ? Path.GetFullPath(trimmed) : null;
}

private static string? TryDetectSongsPathLinux()
Expand Down Expand Up @@ -694,19 +870,6 @@ private static IEnumerable<string> EnumerateOsuConfigFiles(string osuInstallPath
return Directory.Exists(executablePath) ? executablePath : null;
}

[SupportedOSPlatform("windows")]
private static string? ReadRegistryValue(string keyPath, string? valueName)
{
try
{
return Registry.GetValue(keyPath, valueName, null)?.ToString();
}
catch
{
return null;
}
}

private static string? TryReadFirstLine(string filePath)
{
if (!File.Exists(filePath))
Expand Down
8 changes: 4 additions & 4 deletions MapWizard.sln.DotSettings.user
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATransition_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbc95df96ca14631d97b1f6f841b5961ab73036863a592884251993bbc78e2ba8_003FTransition_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AX11PlatformThreading_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F84e0349151eed01791468fad4c99b11e4072e28ab2ac95202e6fa612fd5181_003FX11PlatformThreading_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AX11Platform_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8e6d8c2560ab57e1c157c43b83a8f84c4ca7496a2a59781158e6b4183b_003FX11Platform_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=2816af0a_002D6d51_002D440f_002Daca0_002D98b3b589d7b6/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="All tests from &amp;lt;MapWizard.Tests&amp;gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=2816af0a_002D6d51_002D440f_002Daca0_002D98b3b589d7b6/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="All tests from &amp;lt;MapWizard.Tests&amp;gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Solution /&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=7ce84f90_002D6ae2_002D4e9e_002D860e_002Deb90f45871f3/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Junie Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=7ce84f90_002D6ae2_002D4e9e_002D860e_002Deb90f45871f3/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Junie Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Solution /&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>