1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:07:23 +08:00
osu-lazer/osu.Desktop/Program.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

198 lines
8.4 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2016-08-26 11:28:23 +08:00
using System;
using System.IO;
using System.Runtime.Versioning;
2021-11-28 17:00:06 +08:00
using osu.Desktop.LegacyIpc;
using osu.Desktop.Windows;
2017-09-18 21:32:49 +08:00
using osu.Framework;
using osu.Framework.Development;
2018-08-03 18:25:55 +08:00
using osu.Framework.Logging;
2017-09-18 21:32:49 +08:00
using osu.Framework.Platform;
using osu.Game;
using osu.Game.IPC;
using osu.Game.Tournament;
using SDL2;
using Squirrel;
2018-07-10 19:30:08 +08:00
namespace osu.Desktop
2016-08-26 11:28:23 +08:00
{
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;
2021-11-28 21:24:42 +08:00
2016-08-26 11:28:23 +08:00
[STAThread]
2021-11-28 13:03:21 +08:00
public static void Main(string[] args)
2016-08-26 11:28:23 +08:00
{
Fix broken installer Closes https://github.com/ppy/osu/issues/26510. Time for a rant. Technically, this "broke" with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0, but it is actually an end result of upstream behaviours that I am failing to find a better description for than "utterly broken". Squirrel (the installer we use) has unit tests. Which is great, power to them. However, the method in which that testing is implemented leads to epic levels of WTF breakage. To determine whether Squirrel is being tested right now, it is checking all currently loaded assemblies, and determining that if any loaded assembly contains the magic string of "NUNIT" - among others - it must be being tested right now: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 If one assumes that there is no conceivable way that an NUnit assembly *may* be loaded *without* it being a test context, this *may* seem sane. Foreshadowing. (Now, to avoid being hypocritical, we also do this, *but* we do this by checking if the *entry* assembly is an NUnit: https://github.com/ppy/osu-framework/blob/92db55a52742047b42c0b4b25327ce28bd991b44/osu.Framework/Development/DebugUtils.cs#L16-L34 which seems *much* saner, no?) Now, why did this break with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0 *specifically*, you might wonder? Well the reason is this line: https://github.com/ppy/osu/blob/3d3f58c2523f519504d9156a684538e2aa0c094c/osu.Desktop/NVAPI.cs#L183 Yes you are reading this correctly, it's not NVAPI anything itself that breaks this, it is *a log statement*. To be precise, what the log statement *does* to provoke this, is calling into framework. That causes the framework assembly to load, *which* transitively loads the `nunit.framework` assembly. (If you ever find yourself wanting to find out this sort of cursed knowledge - I hope you never need to - you can run something along the lines of dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 -- .\osu!.exe then open the resulting trace in PerfView, and then search the `Microsoft-Windows-DotNETRuntime/AssemblyLoader/Start` log for the cursed assembly. In this case, the relevant entry said something along the lines of HasStack="True" ThreadID="23,924" ProcessorNumber="0" ClrInstanceID="6" AssemblyName="nunit.framework, Version=3.13.3.0, Culture=neutral, PublicKeyToken=2638cd05610744eb" AssemblyPath="" RequestingAssembly="osu.Framework, Version=2024.113.0.0, Culture=neutral, PublicKeyToken=null" AssemblyLoadContext="Default" RequestingAssemblyLoadContext="Default" ActivityID="/#21032/1/26/" Either that or just comment the log line for kicks. But the above is *much* faster.) Now, what *happens* if Squirrel "detects" that it is being "tested"? Well, it will refuse to close after executing the "hooks" defined via `SquirrelAwareApp`: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SquirrelAwareApp.cs#L85-L88 and it will also refuse to create version shortcuts: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/UpdateManager.Shortcuts.cs#L63-L65 Sounds familiar, don't it? There are days on which I tire of computers. Today is one of them.
2024-01-14 04:39:32 +08:00
/*
* WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
*
* Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
* To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
* namely by checking loaded assemblies:
* https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
*
* If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
* the app will then do completely broken things like:
* - not creating system shortcuts (as the logic is if'd out if "running tests")
* - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
*/
if (OperatingSystem.IsWindows())
{
var windowsVersion = Environment.OSVersion.Version;
// While .NET 6 still supports Windows 7 and above, 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/#supported-platforms
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
{
// 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
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
"Your operating system is too old to run osu!",
"This version of osu! requires at least Windows 8.1 to run.\n"
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero);
return;
}
setupSquirrel();
}
Fix broken installer Closes https://github.com/ppy/osu/issues/26510. Time for a rant. Technically, this "broke" with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0, but it is actually an end result of upstream behaviours that I am failing to find a better description for than "utterly broken". Squirrel (the installer we use) has unit tests. Which is great, power to them. However, the method in which that testing is implemented leads to epic levels of WTF breakage. To determine whether Squirrel is being tested right now, it is checking all currently loaded assemblies, and determining that if any loaded assembly contains the magic string of "NUNIT" - among others - it must be being tested right now: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 If one assumes that there is no conceivable way that an NUnit assembly *may* be loaded *without* it being a test context, this *may* seem sane. Foreshadowing. (Now, to avoid being hypocritical, we also do this, *but* we do this by checking if the *entry* assembly is an NUnit: https://github.com/ppy/osu-framework/blob/92db55a52742047b42c0b4b25327ce28bd991b44/osu.Framework/Development/DebugUtils.cs#L16-L34 which seems *much* saner, no?) Now, why did this break with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0 *specifically*, you might wonder? Well the reason is this line: https://github.com/ppy/osu/blob/3d3f58c2523f519504d9156a684538e2aa0c094c/osu.Desktop/NVAPI.cs#L183 Yes you are reading this correctly, it's not NVAPI anything itself that breaks this, it is *a log statement*. To be precise, what the log statement *does* to provoke this, is calling into framework. That causes the framework assembly to load, *which* transitively loads the `nunit.framework` assembly. (If you ever find yourself wanting to find out this sort of cursed knowledge - I hope you never need to - you can run something along the lines of dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 -- .\osu!.exe then open the resulting trace in PerfView, and then search the `Microsoft-Windows-DotNETRuntime/AssemblyLoader/Start` log for the cursed assembly. In this case, the relevant entry said something along the lines of HasStack="True" ThreadID="23,924" ProcessorNumber="0" ClrInstanceID="6" AssemblyName="nunit.framework, Version=3.13.3.0, Culture=neutral, PublicKeyToken=2638cd05610744eb" AssemblyPath="" RequestingAssembly="osu.Framework, Version=2024.113.0.0, Culture=neutral, PublicKeyToken=null" AssemblyLoadContext="Default" RequestingAssemblyLoadContext="Default" ActivityID="/#21032/1/26/" Either that or just comment the log line for kicks. But the above is *much* faster.) Now, what *happens* if Squirrel "detects" that it is being "tested"? Well, it will refuse to close after executing the "hooks" defined via `SquirrelAwareApp`: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SquirrelAwareApp.cs#L85-L88 and it will also refuse to create version shortcuts: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/UpdateManager.Shortcuts.cs#L63-L65 Sounds familiar, don't it? There are days on which I tire of computers. Today is one of them.
2024-01-14 04:39:32 +08:00
// 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.
NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
2017-01-30 22:35:59 +08:00
// Back up the cwd before DesktopGameHost changes it
string cwd = Environment.CurrentDirectory;
2018-04-13 17:19:50 +08:00
string gameName = base_game_name;
2021-07-21 12:53:24 +08:00
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)
2021-07-21 12:53:24 +08:00
{
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.");
2021-07-21 12:53:24 +08:00
gameName = $"{base_game_name}-{clientID}";
2021-07-21 12:53:24 +08:00
break;
}
}
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null }))
{
if (!host.IsPrimaryInstance)
{
if (trySendIPCMessage(host, cwd, args))
2021-11-28 13:03:21 +08:00
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);
2021-11-28 13:03:21 +08:00
return;
}
}
2021-11-28 20:15:29 +08:00
if (host.IsPrimaryInstance)
{
2021-11-28 21:24:42 +08:00
try
{
Logger.Log("Starting legacy IPC provider...");
legacyIpc = new LegacyTcpIpcProvider();
legacyIpc.Bind();
}
catch (Exception ex)
{
Logger.Error(ex, "Failed to start legacy IPC provider");
}
2021-11-28 20:15:29 +08:00
}
2021-07-21 12:53:24 +08:00
if (tournamentClient)
host.Run(new TournamentGame());
else
host.Run(new OsuGameDesktop(args));
}
2016-08-26 11:28:23 +08:00
}
2018-08-03 18:25:55 +08:00
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;
}
[SupportedOSPlatform("windows")]
private static void setupSquirrel()
{
2022-06-24 20:25:23 +08:00
SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) =>
{
tools.CreateShortcutForThisExe();
tools.CreateUninstallerRegistryEntry();
WindowsAssociationManager.InstallAssociations(null);
2022-06-24 20:25:23 +08:00
}, onAppUpdate: (_, tools) =>
{
tools.CreateUninstallerRegistryEntry();
2022-06-24 20:25:23 +08:00
}, onAppUninstall: (_, tools) =>
{
tools.RemoveShortcutForThisExe();
tools.RemoveUninstallerRegistryEntry();
WindowsAssociationManager.UninstallAssociations();
2022-06-24 20:25:23 +08:00
}, onEveryRun: (_, _, _) =>
{
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
// causes the right-click context menu to function incorrectly.
//
// This may turn out to be non-required after an alternative solution is implemented.
// see https://github.com/clowd/Clowd.Squirrel/issues/24
// tools.SetProcessAppUserModelId();
});
}
2021-11-28 13:03:21 +08:00
}
2016-08-26 11:28:23 +08:00
}