#nullable enable
|
|
using IPA.Config;
|
|
using IPA.Loader;
|
|
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);
|
|
}
|
|
}
|
|
}
|