// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.IO; using System.Linq; using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.IPC { public partial class FileBasedIPC : MatchIPCInfo { public Storage? IPCStorage { get; private set; } [Resolved] protected IAPIProvider API { get; private set; } = null!; [Resolved] protected IRulesetStore Rulesets { get; private set; } = null!; [Resolved] private GameHost host { get; set; } = null!; [Resolved] private LadderInfo ladder { get; set; } = null!; [Resolved] private StableInfo stableInfo { get; set; } = null!; private int lastBeatmapId; private ScheduledDelegate? scheduled; private GetBeatmapRequest? beatmapLookupRequest; [BackgroundDependencyLoader] private void load() { string? stablePath = stableInfo.StablePath ?? findStablePath(); initialiseIPCStorage(stablePath); } private Storage? initialiseIPCStorage(string? path) { scheduled?.Cancel(); IPCStorage = null; try { if (string.IsNullOrEmpty(path)) return null; IPCStorage = new DesktopStorage(path, host as DesktopGameHost); const string file_ipc_filename = "ipc.txt"; const string file_ipc_state_filename = "ipc-state.txt"; const string file_ipc_scores_filename = "ipc-scores.txt"; const string file_ipc_channel_filename = "ipc-channel.txt"; if (IPCStorage.Exists(file_ipc_filename)) { scheduled = Scheduler.AddDelayed(delegate { try { using (var stream = IPCStorage.GetStream(file_ipc_filename)) using (var sr = new StreamReader(stream)) { int beatmapId = int.Parse(sr.ReadLine().AsNonNull()); int mods = int.Parse(sr.ReadLine().AsNonNull()); if (lastBeatmapId != beatmapId) { beatmapLookupRequest?.Cancel(); lastBeatmapId = beatmapId; var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId); if (existing != null) Beatmap.Value = existing.Beatmap; else { beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId }); beatmapLookupRequest.Success += b => { if (lastBeatmapId == beatmapId) Beatmap.Value = new TournamentBeatmap(b); }; beatmapLookupRequest.Failure += _ => { if (lastBeatmapId == beatmapId) Beatmap.Value = null; }; API.Queue(beatmapLookupRequest); } } Mods.Value = (LegacyMods)mods; } } catch { // file might be in use. } try { using (var stream = IPCStorage.GetStream(file_ipc_channel_filename)) using (var sr = new StreamReader(stream)) { ChatChannel.Value = sr.ReadLine().AsNonNull(); } } catch (Exception) { // file might be in use. } try { using (var stream = IPCStorage.GetStream(file_ipc_state_filename)) using (var sr = new StreamReader(stream)) { State.Value = Enum.Parse(sr.ReadLine().AsNonNull()); } } catch (Exception) { // file might be in use. } try { using (var stream = IPCStorage.GetStream(file_ipc_scores_filename)) using (var sr = new StreamReader(stream)) { Score1.Value = int.Parse(sr.ReadLine().AsNonNull()); Score2.Value = int.Parse(sr.ReadLine().AsNonNull()); } } catch (Exception) { // file might be in use. } }, 250, true); } } catch (Exception e) { Logger.Error(e, "Stable installation could not be found; disabling file based IPC"); } return IPCStorage; } /// /// Manually sets the path to the directory used for inter-process communication with a cutting-edge install. /// /// Path to the IPC directory /// Whether the supplied path was a valid IPC directory. public bool SetIPCLocation(string? path) { if (path == null || !ipcFileExistsInDirectory(path)) return false; var newStorage = initialiseIPCStorage(stableInfo.StablePath = path); if (newStorage == null) return false; stableInfo.SaveChanges(); return true; } /// /// Tries to automatically detect the path to the directory used for inter-process communication /// with a cutting-edge install. /// /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); private static bool ipcFileExistsInDirectory(string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); private string? findStablePath() { string? stableInstallPath = findFromEnvVar() ?? findFromRegistry() ?? findFromLocalAppData() ?? findFromDotFolder(); Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); return stableInstallPath; } private string? findFromEnvVar() { try { Logger.Log("Trying to find stable with environment variables"); string? stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath!; } catch { } return null; } private string? findFromLocalAppData() { Logger.Log("Trying to find stable in %LOCALAPPDATA%"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; return null; } private string? findFromDotFolder() { Logger.Log("Trying to find stable in dotfolders"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; return null; } private string? findFromRegistry() { Logger.Log("Trying to find stable in registry"); try { string? stableInstallPath; #pragma warning disable CA1416 using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu")) stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); #pragma warning restore CA1416 if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; } catch { } return null; } } }