// 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.Runtime.Versioning; using osu.Desktop.LegacyIpc; using osu.Desktop.Windows; using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; using SDL; using Velopack; namespace osu.Desktop { public static class Program { #if DEBUG private const string base_game_name = @"osu-development"; #else private const string base_game_name = @"osu"; #endif private static LegacyTcpIpcProvider? legacyIpc; [STAThread] public static void Main(string[] args) { // IMPORTANT DON'T IGNORE: For general sanity, velopack's setup needs to run before anything else. // This has bitten us in the rear before (bricked updater), and although the underlying issue from // last time has been fixed, let's not tempt fate. setupVelopack(); if (OperatingSystem.IsWindows()) { var windowsVersion = Environment.OSVersion.Version; // While .NET 8 only supports Windows 10 and above, running on Windows 7/8.1 may still work. We are limited by realm currently, as they choose to only support 8.1 and higher. // See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/ if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { unsafe { // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider // disabling it ourselves. // We could also better detect compatibility mode if required: // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!"u8, "This version of osu! requires at least Windows 8.1 to run.\n"u8 + "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8 + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null); return; } } } // NVIDIA profiles are based on the executable name of a process. // Lazer and stable share the same executable name. // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. if (OperatingSystem.IsWindows()) NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; string gameName = base_game_name; bool tournamentClient = false; foreach (string arg in args) { string[] split = arg.Split('='); string key = split[0]; string val = split.Length > 1 ? split[1] : string.Empty; switch (key) { case "--tournament": tournamentClient = true; break; case "--debug-client-id": if (!DebugUtils.IsDebugBuild) throw new InvalidOperationException("Cannot use this argument in a non-debug build."); if (!int.TryParse(val, out int clientID)) throw new ArgumentException("Provided client ID must be an integer."); gameName = $"{base_game_name}-{clientID}"; break; } } var hostOptions = new HostOptions { IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null, FriendlyGameName = OsuGameBase.GAME_NAME, }; using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, hostOptions)) { if (!host.IsPrimaryInstance) { if (trySendIPCMessage(host, cwd, args)) return; // we want to allow multiple instances to be started when in debug. if (!DebugUtils.IsDebugBuild) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); return; } } if (host.IsPrimaryInstance) { try { Logger.Log("Starting legacy IPC provider..."); legacyIpc = new LegacyTcpIpcProvider(); legacyIpc.Bind(); } catch (Exception ex) { Logger.Error(ex, "Failed to start legacy IPC provider"); } } if (tournamentClient) host.Run(new TournamentGame()); else host.Run(new OsuGameDesktop(args)); } } private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) { if (args.Length == 1 && args[0].StartsWith(OsuGameBase.OSU_PROTOCOL, StringComparison.Ordinal)) { var osuSchemeLinkHandler = new OsuSchemeLinkIPCChannel(host); if (!osuSchemeLinkHandler.HandleLinkAsync(args[0]).Wait(3000)) throw new IPCTimeoutException(osuSchemeLinkHandler.GetType()); return true; } if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args { var importer = new ArchiveImportIPCChannel(host); foreach (string file in args) { Console.WriteLine(@"Importing {0}", file); if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) throw new IPCTimeoutException(importer.GetType()); } return true; } return false; } private static void setupVelopack() { var app = VelopackApp.Build(); if (OperatingSystem.IsWindows()) configureWindows(app); app.Run(); } [SupportedOSPlatform("windows")] private static void configureWindows(VelopackApp app) { app.WithFirstRun(_ => WindowsAssociationManager.InstallAssociations()); app.WithAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations()); app.WithBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations()); } } }