102 lines
4.4 KiB
C#
102 lines
4.4 KiB
C#
using System.IO;
|
||
using System.Text.Json;
|
||
using System.Text.RegularExpressions;
|
||
using Microsoft.Win32;
|
||
|
||
namespace DeskCore;
|
||
|
||
/// <summary>
|
||
/// Erkennt, ob ein Spiel aus einem Steam- oder Epic-Link (.url) noch installiert ist.
|
||
/// Ergebnis null = "weiß ich nicht" → DeskCore lässt den Link dann sicherheitshalber stehen.
|
||
/// </summary>
|
||
public static class GameLibraries
|
||
{
|
||
private static readonly Regex SteamRun = new(@"^steam://(?:rungameid|run|launch)/(\d+)", RegexOptions.IgnoreCase);
|
||
private const string EpicPrefix = "com.epicgames.launcher://apps/";
|
||
|
||
/// <summary>Ist das Spiel hinter diesem Launcher-Link noch installiert? (null = nicht feststellbar)</summary>
|
||
public static bool? IsInstalled(string url)
|
||
{
|
||
url = url.Trim();
|
||
var m = SteamRun.Match(url);
|
||
if (m.Success) return SteamAppInstalled(m.Groups[1].Value);
|
||
if (url.StartsWith(EpicPrefix, StringComparison.OrdinalIgnoreCase)) return EpicAppInstalled(url);
|
||
return null;
|
||
}
|
||
|
||
// ---------- Steam ----------
|
||
|
||
private static (DateTime When, List<string>? Libraries) _steamCache;
|
||
|
||
/// <summary>Alle Steam-Bibliotheksordner – oder null, wenn Steam fehlt bzw. eine Bibliothek gerade nicht erreichbar ist.</summary>
|
||
private static List<string>? SteamLibraries()
|
||
{
|
||
if (DateTime.UtcNow - _steamCache.When < TimeSpan.FromSeconds(30)) return _steamCache.Libraries;
|
||
List<string>? result = null;
|
||
try
|
||
{
|
||
using var k = Registry.CurrentUser.OpenSubKey(@"Software\Valve\Steam");
|
||
if (k?.GetValue("SteamPath") is string sp && sp.Length > 0)
|
||
{
|
||
var steamPath = Path.GetFullPath(sp.Replace('/', '\\'));
|
||
var libs = new List<string> { steamPath };
|
||
var vdf = Path.Combine(steamPath, "steamapps", "libraryfolders.vdf");
|
||
if (File.Exists(vdf))
|
||
{
|
||
foreach (Match lm in Regex.Matches(File.ReadAllText(vdf), "\"path\"\\s+\"([^\"]+)\""))
|
||
libs.Add(Path.GetFullPath(lm.Groups[1].Value.Replace(@"\\", @"\")));
|
||
}
|
||
libs = libs.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||
// Liegt eine Bibliothek auf einem abgesteckten Laufwerk, könnte das Spiel dort sein → nichts entscheiden
|
||
result = libs.All(l => Directory.Exists(Path.Combine(l, "steamapps"))) ? libs : null;
|
||
}
|
||
}
|
||
catch { result = null; }
|
||
_steamCache = (DateTime.UtcNow, result);
|
||
return result;
|
||
}
|
||
|
||
private static bool? SteamAppInstalled(string appId)
|
||
{
|
||
var libs = SteamLibraries();
|
||
if (libs is null) return null;
|
||
return libs.Any(l => File.Exists(Path.Combine(l, "steamapps", $"appmanifest_{appId}.acf")));
|
||
}
|
||
|
||
// ---------- Epic Games ----------
|
||
|
||
private static bool? EpicAppInstalled(string url)
|
||
{
|
||
// Format: com.epicgames.launcher://apps/<namespace>%3A<katalog>%3A<AppName>?action=launch
|
||
// oder com.epicgames.launcher://apps/<AppName>?action=launch
|
||
var rest = url[EpicPrefix.Length..];
|
||
var q = rest.IndexOf('?');
|
||
if (q >= 0) rest = rest[..q];
|
||
var appName = Uri.UnescapeDataString(rest).Split(':').Last().Trim();
|
||
if (appName.Length == 0) return null;
|
||
|
||
var manifests = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||
"Epic", "EpicGamesLauncher", "Data", "Manifests");
|
||
if (!Directory.Exists(manifests)) return null; // Epic-Launcher nicht (mehr) vorhanden → nicht entscheiden
|
||
try
|
||
{
|
||
foreach (var f in Directory.EnumerateFiles(manifests, "*.item"))
|
||
{
|
||
using var doc = JsonDocument.Parse(File.ReadAllText(f));
|
||
var root = doc.RootElement;
|
||
if (!root.TryGetProperty("AppName", out var an) ||
|
||
!string.Equals(an.GetString(), appName, StringComparison.OrdinalIgnoreCase)) continue;
|
||
// Installationsort auf abgestecktem Laufwerk? → nicht entscheiden
|
||
if (root.TryGetProperty("InstallLocation", out var loc) && loc.GetString() is { Length: > 0 } dir)
|
||
{
|
||
var drive = new DriveInfo(Path.GetPathRoot(dir)!);
|
||
if (!drive.IsReady) return null;
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
catch { return null; }
|
||
}
|
||
}
|