|
|
- #nullable enable
- using IPA.Config;
- using IPA.Utilities.Async;
- using System;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Threading;
- using UnityEngine;
- #if NET3
- using Path = Net3_Proxy.Path;
- #endif
-
- namespace IPA.Utilities
- {
- /// <summary>
- /// Provides some basic utility methods and properties of Beat Saber
- /// </summary>
- public static class UnityGame
- {
- private static AlmostVersion? _gameVersion;
- /// <summary>
- /// Provides the current game version.
- /// </summary>
- /// <value>the SemVer version of the game</value>
- public static AlmostVersion GameVersion => _gameVersion ??= new AlmostVersion(ApplicationVersionProxy);
-
- internal static void SetEarlyGameVersion(AlmostVersion ver)
- {
- _gameVersion = ver;
- Logging.Logger.Default.Debug($"GameVersion set early to {ver}");
- }
- private static string ApplicationVersionProxy
- {
- [MethodImpl(MethodImplOptions.NoInlining)]
- get
- {
- try
- {
- return Application.version;
- }
- catch(MissingMemberException ex)
- {
- Logging.Logger.Default.Error($"Tried to grab 'Application.version' too early, it's probably broken now.");
- if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
- Logging.Logger.Default.Error(ex);
- }
- catch (Exception ex)
- {
- Logging.Logger.Default.Error($"Error getting Application.version: {ex.Message}");
- if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
- Logging.Logger.Default.Error(ex);
- }
- return string.Empty;
- }
- }
- internal static void EnsureRuntimeGameVersion()
- {
- try
- {
- var rtVer = new AlmostVersion(ApplicationVersionProxy);
- if (!rtVer.Equals(_gameVersion)) // this actually uses stricter equality than == for AlmostVersion
- {
- Logging.Logger.Default.Warn($"Early version {_gameVersion} parsed from game files doesn't match runtime version {rtVer}!");
- _gameVersion = rtVer;
- }
- }
- catch (MissingMethodException e)
- {
- Logging.Logger.Default.Error("Application.version was not found! Cannot check early parsed version");
- if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
- Logging.Logger.Default.Error(e);
-
- var st = new StackTrace();
- Logging.Logger.Default.Notice($"{st}");
- }
- }
-
- internal static bool IsGameVersionBoundary { get; private set; }
- internal static AlmostVersion? OldVersion { get; private set; }
- internal static void CheckGameVersionBoundary()
- {
- var gameVer = GameVersion;
- var lastVerS = SelfConfig.LastGameVersion_;
- OldVersion = lastVerS != null ? new AlmostVersion(lastVerS, gameVer) : null;
-
- IsGameVersionBoundary = OldVersion is not null && gameVer != OldVersion;
-
- SelfConfig.Instance.LastGameVersion = gameVer.ToString();
- }
-
- private static Thread? mainThread;
- /// <summary>
- /// Checks if the currently running code is running on the Unity main thread.
- /// </summary>
- /// <value><see langword="true"/> if the curent thread is the Unity main thread, <see langword="false"/> otherwise</value>
- public static bool OnMainThread => Environment.CurrentManagedThreadId == mainThread?.ManagedThreadId;
-
- /// <summary>
- /// Asynchronously switches the current execution context to the Unity main thread.
- /// </summary>
- /// <returns>An awaitable which causes any following code to execute on the main thread.</returns>
- public static SwitchToUnityMainThreadAwaitable SwitchToMainThreadAsync() => default;
-
- internal static void SetMainThread()
- => mainThread = Thread.CurrentThread;
-
- /// <summary>
- /// The different types of releases of the game.
- /// </summary>
- public enum Release
- {
- /// <summary>
- /// Indicates a Steam release.
- /// </summary>
- Steam,
- /// <summary>
- /// Indicates a non-Steam release.
- /// </summary>
- Other
- }
- private static Release? _releaseCache;
- /// <summary>
- /// Gets the <see cref="Release"/> type of this installation of Beat Saber
- /// </summary>
- /// <remarks>
- /// This only gives a
- /// </remarks>
- /// <value>the type of release this is</value>
- public static Release ReleaseType => _releaseCache ??= CheckIsSteam() ? Release.Steam : Release.Other;
-
- private static string? _installRoot;
- /// <summary>
- /// Gets the path to the game's install directory.
- /// </summary>
- /// <value>the path of the game install directory</value>
- public static string InstallPath
- {
- get
- {
- if (_installRoot == null)
- _installRoot = Path.GetFullPath(
- Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..", ".."));
- return _installRoot;
- }
- }
- /// <summary>
- /// The path to the `Libs` folder. Use only if necessary.
- /// </summary>
- /// <value>the path to the library directory</value>
- public static string LibraryPath => Path.Combine(InstallPath, "Libs");
- /// <summary>
- /// The path to the `Libs\Native` folder. Use only if necessary.
- /// </summary>
- /// <value>the path to the native library directory</value>
- public static string NativeLibraryPath => Path.Combine(LibraryPath, "Native");
- /// <summary>
- /// The directory to load plugins from.
- /// </summary>
- /// <value>the path to the plugin directory</value>
- public static string PluginsPath => Path.Combine(InstallPath, "Plugins");
- /// <summary>
- /// The path to the `UserData` folder.
- /// </summary>
- /// <value>the path to the user data directory</value>
- public static string UserDataPath => Path.Combine(InstallPath, "UserData");
-
- private static bool CheckIsSteam()
- {
- var installDirInfo = new DirectoryInfo(InstallPath);
- return installDirInfo.Parent?.Name == "common"
- && installDirInfo.Parent?.Parent?.Name == "steamapps";
- }
- }
-
- /// <summary>
- /// An awaitable which, when awaited, switches the current context to the Unity main thread.
- /// </summary>
- /// <seealso cref="UnityGame.SwitchToMainThreadAsync"/>
- [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types",
- Justification = "This type should never be compared.")]
- public struct SwitchToUnityMainThreadAwaitable
- {
- /// <summary>
- /// Gets the awaiter for this awaitable.
- /// </summary>
- /// <returns>The awaiter for this awaitable.</returns>
- public SwitchToUnityMainThreadAwaiter GetAwaiter() => default;
- }
-
- /// <summary>
- /// An awaiter which, when awaited, switches the current context to the Unity main thread.
- /// </summary>
- /// <seealso cref="UnityGame.SwitchToMainThreadAsync"/>
- [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types",
- Justification = "This type should never be compared.")]
- public struct SwitchToUnityMainThreadAwaiter : INotifyCompletion, ICriticalNotifyCompletion
- {
- private static readonly ContextCallback InvokeAction = static o => ((Action)o!)();
-
- /// <summary>
- /// Gets whether or not this awaiter is completed.
- /// </summary>
- public bool IsCompleted => UnityGame.OnMainThread;
-
- /// <summary>
- /// Gets the result of this awaiter.
- /// </summary>
- public void GetResult() { }
-
- /// <summary>
- /// Registers a continuation to be called when this awaiter finishes.
- /// </summary>
- /// <param name="continuation">The continuation.</param>
- public void OnCompleted(Action continuation)
- {
- var ec = ExecutionContext.Capture();
- UnityMainThreadTaskScheduler.Default.QueueAction(() => ExecutionContext.Run(ec, InvokeAction, continuation));
- }
-
- /// <summary>
- /// Registers a continuation to be called when this awaiter finishes, without capturing the execution context.
- /// </summary>
- /// <param name="continuation">The continuation.</param>
- public void UnsafeOnCompleted(Action continuation)
- {
- UnityMainThreadTaskScheduler.Default.QueueAction(continuation);
- }
- }
- }
|