using System.IO; using System.Text.Json; using System.Text.RegularExpressions; using Microsoft.Win32; namespace DeskCore; /// /// 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. /// 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/"; /// Ist das Spiel hinter diesem Launcher-Link noch installiert? (null = nicht feststellbar) 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? Libraries) _steamCache; /// Alle Steam-Bibliotheksordner – oder null, wenn Steam fehlt bzw. eine Bibliothek gerade nicht erreichbar ist. private static List? SteamLibraries() { if (DateTime.UtcNow - _steamCache.When < TimeSpan.FromSeconds(30)) return _steamCache.Libraries; List? 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 { 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/%3A%3A?action=launch // oder com.epicgames.launcher://apps/?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; } } }