Browse Source

Renamed BeatSaber class to UnityGame

4.0.0-beta
Anairkoen Schno 4 years ago
parent
commit
77e86cb03d
11 changed files with 317 additions and 263 deletions
  1. +70
    -70
      IPA.Injector/GameVersionEarly.cs
  2. +1
    -1
      IPA.Injector/Injector.cs
  3. +161
    -161
      IPA.Injector/Updates.cs
  4. +1
    -1
      IPA.Loader/Config/Config.cs
  5. +1
    -1
      IPA.Loader/IPA.Loader.csproj
  6. +11
    -11
      IPA.Loader/Loader/PluginLoader.cs
  7. +2
    -2
      IPA.Loader/Loader/PluginManager.cs
  8. +1
    -1
      IPA.Loader/Loader/PluginMetadata.cs
  9. +9
    -9
      IPA.Loader/Updating/BeatMods/Updater.cs
  10. +5
    -2
      IPA.Loader/Utilities/UnityGame.cs
  11. +55
    -4
      Net3-Proxy/Utils.cs

+ 70
- 70
IPA.Injector/GameVersionEarly.cs View File

@ -1,70 +1,70 @@
using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
#if NET3
using Net3_Proxy;
using Path = Net3_Proxy.Path;
using File = Net3_Proxy.File;
using Directory = Net3_Proxy.Directory;
#endif
namespace IPA.Injector
{
internal static class GameVersionEarly
{
internal static string ResolveDataPath(string installDir) =>
Directory.EnumerateDirectories(installDir, "*_Data").First();
internal static string GlobalGameManagers(string installDir) =>
Path.Combine(ResolveDataPath(installDir), "globalgamemanagers");
internal static string GetGameVersion()
{
var mgr = GlobalGameManagers(BeatSaber.InstallPath);
using (var stream = File.OpenRead(mgr))
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
const string key = "public.app-category.games";
int pos = 0;
while (stream.Position < stream.Length && pos < key.Length)
{
if (reader.ReadByte() == key[pos]) pos++;
else pos = 0;
}
if (stream.Position == stream.Length) // we went through the entire stream without finding the key
throw new KeyNotFoundException("Could not find key '" + key + "' in " + mgr);
// otherwise pos == key.Length, which means we found it
int offset = 136 - key.Length - sizeof(int);
stream.Seek(offset, SeekOrigin.Current); // advance past junk to beginning of string
int strlen = reader.ReadInt32(); // assumes LE
var strbytes = reader.ReadBytes(strlen);
return Encoding.UTF8.GetString(strbytes);
}
}
internal static SemVer.Version SafeParseVersion() => new SemVer.Version(GetGameVersion(), true);
private static void _Load()
{
BeatSaber.SetEarlyGameVersion(SafeParseVersion());
BeatSaber.CheckGameVersionBoundary();
}
internal static void Load()
{
// This exists for the same reason the wierdness in Injector.Main does
_ = Type.GetType("SemVer.Version, SemVer", false);
_Load();
}
}
}
using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
#if NET3
using Net3_Proxy;
using Path = Net3_Proxy.Path;
using File = Net3_Proxy.File;
using Directory = Net3_Proxy.Directory;
#endif
namespace IPA.Injector
{
internal static class GameVersionEarly
{
internal static string ResolveDataPath(string installDir) =>
Directory.EnumerateDirectories(installDir, "*_Data").First();
internal static string GlobalGameManagers(string installDir) =>
Path.Combine(ResolveDataPath(installDir), "globalgamemanagers");
internal static string GetGameVersion()
{
var mgr = GlobalGameManagers(UnityGame.InstallPath);
using (var stream = File.OpenRead(mgr))
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
const string key = "public.app-category.games";
int pos = 0;
while (stream.Position < stream.Length && pos < key.Length)
{
if (reader.ReadByte() == key[pos]) pos++;
else pos = 0;
}
if (stream.Position == stream.Length) // we went through the entire stream without finding the key
throw new KeyNotFoundException("Could not find key '" + key + "' in " + mgr);
// otherwise pos == key.Length, which means we found it
int offset = 136 - key.Length - sizeof(int);
stream.Seek(offset, SeekOrigin.Current); // advance past junk to beginning of string
int strlen = reader.ReadInt32(); // assumes LE
var strbytes = reader.ReadBytes(strlen);
return Encoding.UTF8.GetString(strbytes);
}
}
internal static SemVer.Version SafeParseVersion() => new SemVer.Version(GetGameVersion(), true);
private static void _Load()
{
UnityGame.SetEarlyGameVersion(SafeParseVersion());
UnityGame.CheckGameVersionBoundary();
}
internal static void Load()
{
// This exists for the same reason the wierdness in Injector.Main does
_ = Type.GetType("SemVer.Version, SemVer", false);
_Load();
}
}
}

+ 1
- 1
IPA.Injector/Injector.cs View File

@ -328,7 +328,7 @@ namespace IPA.Injector
pluginAsyncLoadTask.Wait();
permissionFixTask.Wait();
BeatSaber.EnsureRuntimeGameVersion();
UnityGame.EnsureRuntimeGameVersion();
log.Debug("Plugins loaded");
log.Debug(string.Join(", ", PluginLoader.PluginsMetadata.StrJP()));


+ 161
- 161
IPA.Injector/Updates.cs View File

@ -1,161 +1,161 @@
using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using static IPA.Logging.Logger;
#if NET3
using Net3_Proxy;
using Path = Net3_Proxy.Path;
using File = Net3_Proxy.File;
using Directory = Net3_Proxy.Directory;
#endif
namespace IPA.Injector
{
internal static class Updates
{
private const string DeleteFileName = Updating.BeatMods.Updater.SpecialDeletionsFile;
public static void InstallPendingUpdates()
{
InstallPendingSelfUpdates();
InstallPendingModUpdates();
}
private static void InstallPendingSelfUpdates()
{
var path = Path.Combine(BeatSaber.InstallPath, "IPA.exe");
if (!File.Exists(path)) return;
var ipaVersion = new Version(FileVersionInfo.GetVersionInfo(path).FileVersion);
var selfVersion = Assembly.GetExecutingAssembly().GetName().Version;
if (ipaVersion > selfVersion)
{
Process.Start(new ProcessStartInfo
{
FileName = path,
Arguments = $"\"-nw={Process.GetCurrentProcess().Id},s={string.Join(" ", Environment.GetCommandLineArgs().Skip(1).StrJP()).Replace("\\", "\\\\").Replace(",", "\\,")}\"",
UseShellExecute = false
});
updater.Info("Updating BSIPA...");
Environment.Exit(0);
}
}
private static void InstallPendingModUpdates()
{
var pendingDir = Path.Combine(BeatSaber.InstallPath, "IPA", "Pending");
if (!Directory.Exists(pendingDir)) return;
// there are pending updates, install
updater.Info("Installing pending updates");
var toDelete = new string[0];
var delFn = Path.Combine(pendingDir, DeleteFileName);
if (File.Exists(delFn))
{
toDelete = File.ReadAllLines(delFn);
File.Delete(delFn);
}
foreach (var file in toDelete)
{
try
{
File.Delete(Path.Combine(BeatSaber.InstallPath, file));
}
catch (Exception e)
{
updater.Error("While trying to install pending updates: Error deleting file marked for deletion");
updater.Error(e);
}
}
#region Self Protection
string path;
if (Directory.Exists(path = Path.Combine(pendingDir, "IPA")))
{
var dirs = new Stack<string>(20);
dirs.Push(path);
while (dirs.Count > 0)
{
var currentDir = dirs.Pop();
string[] subDirs;
string[] files;
try
{
subDirs = Directory.GetDirectories(currentDir);
files = Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e)
{
updater.Error(e);
continue;
}
catch (DirectoryNotFoundException e)
{
updater.Error(e);
continue;
}
foreach (var file in files)
{
try
{
if (!Utils.GetRelativePath(file, path).Split(Path.PathSeparator).Contains("Pending"))
File.Delete(file);
}
catch (FileNotFoundException e)
{
updater.Error(e);
}
}
foreach (var str in subDirs)
dirs.Push(str);
}
}
if (File.Exists(path = Path.Combine(pendingDir, "IPA.exe")))
{
File.Delete(path);
if (File.Exists(path = Path.Combine(pendingDir, "Mono.Cecil.dll")))
File.Delete(path);
}
#endregion
try
{
Utils.CopyAll(new DirectoryInfo(pendingDir), new DirectoryInfo(BeatSaber.InstallPath), onCopyException: (e, f) =>
{
updater.Error($"Error copying file {Utils.GetRelativePath(f.FullName, pendingDir)} from Pending:");
updater.Error(e);
return true;
});
}
catch (Exception e)
{
updater.Error("While trying to install pending updates: Error copying files in");
updater.Error(e);
}
try
{
Directory.Delete(pendingDir, true);
}
catch (Exception e)
{
updater.Error("Something went wrong performing an operation that should never fail!");
updater.Error(e);
}
}
}
}
using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using static IPA.Logging.Logger;
#if NET3
using Net3_Proxy;
using Path = Net3_Proxy.Path;
using File = Net3_Proxy.File;
using Directory = Net3_Proxy.Directory;
#endif
namespace IPA.Injector
{
internal static class Updates
{
private const string DeleteFileName = Updating.BeatMods.Updater.SpecialDeletionsFile;
public static void InstallPendingUpdates()
{
InstallPendingSelfUpdates();
InstallPendingModUpdates();
}
private static void InstallPendingSelfUpdates()
{
var path = Path.Combine(UnityGame.InstallPath, "IPA.exe");
if (!File.Exists(path)) return;
var ipaVersion = new Version(FileVersionInfo.GetVersionInfo(path).FileVersion);
var selfVersion = Assembly.GetExecutingAssembly().GetName().Version;
if (ipaVersion > selfVersion)
{
Process.Start(new ProcessStartInfo
{
FileName = path,
Arguments = $"\"-nw={Process.GetCurrentProcess().Id},s={string.Join(" ", Environment.GetCommandLineArgs().Skip(1).StrJP()).Replace("\\", "\\\\").Replace(",", "\\,")}\"",
UseShellExecute = false
});
updater.Info("Updating BSIPA...");
Environment.Exit(0);
}
}
private static void InstallPendingModUpdates()
{
var pendingDir = Path.Combine(UnityGame.InstallPath, "IPA", "Pending");
if (!Directory.Exists(pendingDir)) return;
// there are pending updates, install
updater.Info("Installing pending updates");
var toDelete = new string[0];
var delFn = Path.Combine(pendingDir, DeleteFileName);
if (File.Exists(delFn))
{
toDelete = File.ReadAllLines(delFn);
File.Delete(delFn);
}
foreach (var file in toDelete)
{
try
{
File.Delete(Path.Combine(UnityGame.InstallPath, file));
}
catch (Exception e)
{
updater.Error("While trying to install pending updates: Error deleting file marked for deletion");
updater.Error(e);
}
}
#region Self Protection
string path;
if (Directory.Exists(path = Path.Combine(pendingDir, "IPA")))
{
var dirs = new Stack<string>(20);
dirs.Push(path);
while (dirs.Count > 0)
{
var currentDir = dirs.Pop();
string[] subDirs;
string[] files;
try
{
subDirs = Directory.GetDirectories(currentDir);
files = Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e)
{
updater.Error(e);
continue;
}
catch (DirectoryNotFoundException e)
{
updater.Error(e);
continue;
}
foreach (var file in files)
{
try
{
if (!Utils.GetRelativePath(file, path).Split(Path.PathSeparator).Contains("Pending"))
File.Delete(file);
}
catch (FileNotFoundException e)
{
updater.Error(e);
}
}
foreach (var str in subDirs)
dirs.Push(str);
}
}
if (File.Exists(path = Path.Combine(pendingDir, "IPA.exe")))
{
File.Delete(path);
if (File.Exists(path = Path.Combine(pendingDir, "Mono.Cecil.dll")))
File.Delete(path);
}
#endregion
try
{
Utils.CopyAll(new DirectoryInfo(pendingDir), new DirectoryInfo(UnityGame.InstallPath), onCopyException: (e, f) =>
{
updater.Error($"Error copying file {Utils.GetRelativePath(f.FullName, pendingDir)} from Pending:");
updater.Error(e);
return true;
});
}
catch (Exception e)
{
updater.Error("While trying to install pending updates: Error copying files in");
updater.Error(e);
}
try
{
Directory.Delete(pendingDir, true);
}
catch (Exception e)
{
updater.Error("Something went wrong performing an operation that should never fail!");
updater.Error(e);
}
}
}
}

+ 1
- 1
IPA.Loader/Config/Config.cs View File

@ -110,7 +110,7 @@ namespace IPA.Config
var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json";
var provider = registeredProviders[chosenExt];
var filename = Path.Combine(BeatSaber.UserDataPath, configName + "." + provider.Extension);
var filename = Path.Combine(UnityGame.UserDataPath, configName + "." + provider.Extension);
var config = new Config(configName, provider, new FileInfo(filename));
ConfigRuntime.RegisterConfig(config);


+ 1
- 1
IPA.Loader/IPA.Loader.csproj View File

@ -137,7 +137,7 @@
<Compile Include="JsonConverters\SemverRangeConverter.cs" />
<Compile Include="JsonConverters\SemverVersionConverter.cs" />
<Compile Include="Utilities\Async\SingleThreadTaskScheduler.cs" />
<Compile Include="Utilities\BeatSaber.cs" />
<Compile Include="Utilities\UnityGame.cs" />
<Compile Include="Utilities\AlmostVersion.cs" />
<Compile Include="Utilities\CriticalSection.cs" />
<Compile Include="Utilities\EnumerableExtensions.cs" />


+ 11
- 11
IPA.Loader/Loader/PluginLoader.cs View File

@ -46,12 +46,12 @@ namespace IPA.Loader
internal static void YeetIfNeeded()
{
string pluginDir = BeatSaber.PluginsPath;
string pluginDir = UnityGame.PluginsPath;
if (SelfConfig.YeetMods_ && BeatSaber.IsGameVersionBoundary)
if (SelfConfig.YeetMods_ && UnityGame.IsGameVersionBoundary)
{
var oldPluginsName = Path.Combine(BeatSaber.InstallPath, $"Old {BeatSaber.OldVersion} Plugins");
var newPluginsName = Path.Combine(BeatSaber.InstallPath, $"Old {BeatSaber.GameVersion} Plugins");
var oldPluginsName = Path.Combine(UnityGame.InstallPath, $"Old {UnityGame.OldVersion} Plugins");
var newPluginsName = Path.Combine(UnityGame.InstallPath, $"Old {UnityGame.GameVersion} Plugins");
if (Directory.Exists(oldPluginsName))
Directory.Delete(oldPluginsName, true);
@ -70,14 +70,14 @@ namespace IPA.Loader
internal static void LoadMetadata()
{
string[] plugins = Directory.GetFiles(BeatSaber.PluginsPath, "*.dll");
string[] plugins = Directory.GetFiles(UnityGame.PluginsPath, "*.dll");
try
{
var selfMeta = new PluginMetadata
{
Assembly = Assembly.GetExecutingAssembly(),
File = new FileInfo(Path.Combine(BeatSaber.InstallPath, "IPA.exe")),
File = new FileInfo(Path.Combine(UnityGame.InstallPath, "IPA.exe")),
PluginType = null,
IsSelf = true
};
@ -103,7 +103,7 @@ namespace IPA.Loader
{
var metadata = new PluginMetadata
{
File = new FileInfo(Path.Combine(BeatSaber.PluginsPath, plugin)),
File = new FileInfo(Path.Combine(UnityGame.PluginsPath, plugin)),
IsSelf = false
};
@ -218,15 +218,15 @@ namespace IPA.Loader
}
}
IEnumerable<string> bareManifests = Directory.GetFiles(BeatSaber.PluginsPath, "*.json");
bareManifests = bareManifests.Concat(Directory.GetFiles(BeatSaber.PluginsPath, "*.manifest"));
IEnumerable<string> bareManifests = Directory.GetFiles(UnityGame.PluginsPath, "*.json");
bareManifests = bareManifests.Concat(Directory.GetFiles(UnityGame.PluginsPath, "*.manifest"));
foreach (var manifest in bareManifests)
{ // TODO: maybe find a way to allow a bare manifest to specify an associated file
try
{
var metadata = new PluginMetadata
{
File = new FileInfo(Path.Combine(BeatSaber.PluginsPath, manifest)),
File = new FileInfo(Path.Combine(UnityGame.PluginsPath, manifest)),
IsSelf = false,
IsBare = true,
};
@ -601,7 +601,7 @@ namespace IPA.Loader
internal static PluginExecutor InitPlugin(PluginMetadata meta, IEnumerable<PluginMetadata> alreadyLoaded)
{
if (meta.Manifest.GameVersion != BeatSaber.GameVersion)
if (meta.Manifest.GameVersion != UnityGame.GameVersion)
Logger.loader.Warn($"Mod {meta.Name} developed for game version {meta.Manifest.GameVersion}, so it may not work properly.");
if (!meta.IsAttributePlugin)


+ 2
- 2
IPA.Loader/Loader/PluginManager.cs View File

@ -283,7 +283,7 @@ namespace IPA.Loader
internal static void Load()
{
string pluginDirectory = BeatSaber.PluginsPath;
string pluginDirectory = UnityGame.PluginsPath;
// Process.GetCurrentProcess().MainModule crashes the game and Assembly.GetEntryAssembly() is NULL,
// so we need to resort to P/Invoke
@ -373,7 +373,7 @@ namespace IPA.Loader
Logger.log.Info(exeName);
Logger.log.Info($"Running on Unity {Application.unityVersion}");
Logger.log.Info($"Game version {BeatSaber.GameVersion}");
Logger.log.Info($"Game version {UnityGame.GameVersion}");
Logger.log.Info("-----------------------------");
Logger.log.Info($"Loading plugins from {Utils.GetRelativePath(pluginDirectory, Environment.CurrentDirectory)} and found {_bsPlugins.Count + _ipaPlugins.Count}");
Logger.log.Info("-----------------------------");


+ 1
- 1
IPA.Loader/Loader/PluginMetadata.cs View File

@ -99,6 +99,6 @@ namespace IPA.Loader
/// Gets all of the metadata as a readable string.
/// </summary>
/// <returns>the readable printable metadata string</returns>
public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.FullName}) from '{Utils.GetRelativePath(File?.FullName, BeatSaber.InstallPath)}'";
public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.FullName}) from '{Utils.GetRelativePath(File?.FullName, UnityGame.InstallPath)}'";
}
}

+ 9
- 9
IPA.Loader/Updating/BeatMods/Updater.cs View File

@ -388,7 +388,7 @@ namespace IPA.Updating.BeatMods
var ver = modsMatching.Value
.NonNull() // entry is not null
.Where(versionCheck => versionCheck.GameVersion == BeatSaber.GameVersion) // game version matches
.Where(versionCheck => versionCheck.GameVersion == UnityGame.GameVersion) // game version matches
.Where(approvalCheck => approvalCheck.Status == ApiEndpoint.Mod.ApprovedStatus) // version approved
// TODO: fix; it seems wrong somehow
.Where(conflictsCheck => dep.Conflicts == null || !dep.Conflicts.IsSatisfied(conflictsCheck.Version)) // not a conflicting version
@ -458,7 +458,7 @@ namespace IPA.Updating.BeatMods
DownloadProgress progress, DownloadFailed dlFail, DownloadFinish finish,
InstallFailed installFail, InstallFinish installFinish)
{ // (3.2)
Logger.updater.Debug($"Release: {BeatSaber.ReleaseType}");
Logger.updater.Debug($"Release: {UnityGame.ReleaseType}");
var mod = new Ref<ApiEndpoint.Mod>(null);
yield return GetModInfo(item.Name, item.ResolvedVersion.ToString(), mod);
@ -471,7 +471,7 @@ namespace IPA.Updating.BeatMods
yield break;
}
var releaseName = BeatSaber.ReleaseType == BeatSaber.Release.Steam
var releaseName = UnityGame.ReleaseType == UnityGame.Release.Steam
? ApiEndpoint.Mod.DownloadsObject.TypeSteam : ApiEndpoint.Mod.DownloadsObject.TypeOculus;
var platformFile = mod.Value.Downloads.First(f => f.Type == ApiEndpoint.Mod.DownloadsObject.TypeUniversal || f.Type == releaseName);
@ -628,10 +628,10 @@ namespace IPA.Updating.BeatMods
if (!Utils.UnsafeCompare(hash, fileInfo.Hash))
throw new Exception("The hash for the file doesn't match what is defined");*/
var targetDir = Path.Combine(BeatSaber.InstallPath, "IPA", Path.GetRandomFileName() + "_Pending");
var targetDir = Path.Combine(UnityGame.InstallPath, "IPA", Path.GetRandomFileName() + "_Pending");
Directory.CreateDirectory(targetDir);
var eventualOutput = Path.Combine(BeatSaber.InstallPath, "IPA", "Pending");
var eventualOutput = Path.Combine(UnityGame.InstallPath, "IPA", "Pending");
if (!Directory.Exists(eventualOutput))
Directory.CreateDirectory(eventualOutput);
@ -674,7 +674,7 @@ namespace IPA.Updating.BeatMods
Directory.CreateDirectory(targetFile.DirectoryName ?? throw new InvalidOperationException());
if (item.LocalPluginMeta != null &&
Utils.GetRelativePath(targetFile.FullName, targetDir) == Utils.GetRelativePath(item.LocalPluginMeta?.File.FullName, BeatSaber.InstallPath))
Utils.GetRelativePath(targetFile.FullName, targetDir) == Utils.GetRelativePath(item.LocalPluginMeta?.File.FullName, UnityGame.InstallPath))
shouldDeleteOldFile = false; // overwriting old file, no need to delete
/*if (targetFile.Exists)
@ -693,7 +693,7 @@ namespace IPA.Updating.BeatMods
}
if (shouldDeleteOldFile && item.LocalPluginMeta != null)
File.AppendAllLines(Path.Combine(targetDir, SpecialDeletionsFile), new[] { Utils.GetRelativePath(item.LocalPluginMeta?.File.FullName, BeatSaber.InstallPath) });
File.AppendAllLines(Path.Combine(targetDir, SpecialDeletionsFile), new[] { Utils.GetRelativePath(item.LocalPluginMeta?.File.FullName, UnityGame.InstallPath) });
}
catch (Exception)
{ // something failed; restore
@ -705,8 +705,8 @@ namespace IPA.Updating.BeatMods
if ((item.LocalPluginMeta?.IsSelf).Unwrap())
{ // currently updating self, so copy to working dir and update
NeedsManualRestart = true; // flag so that ModList keeps the restart button hidden
Utils.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(BeatSaber.InstallPath));
var deleteFile = Path.Combine(BeatSaber.InstallPath, SpecialDeletionsFile);
Utils.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(UnityGame.InstallPath));
var deleteFile = Path.Combine(UnityGame.InstallPath, SpecialDeletionsFile);
if (File.Exists(deleteFile)) File.Delete(deleteFile);
Process.Start(new ProcessStartInfo
{


IPA.Loader/Utilities/BeatSaber.cs → IPA.Loader/Utilities/UnityGame.cs View File

@ -12,7 +12,7 @@ namespace IPA.Utilities
/// <summary>
/// Provides some basic utility methods and properties of Beat Saber
/// </summary>
public static class BeatSaber
public static class UnityGame
{
private static AlmostVersion _gameVersion;
/// <summary>
@ -77,6 +77,9 @@ namespace IPA.Utilities
/// <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 ?? (_releaseCache = FindSteamVRAsset() ? Release.Steam : Release.Oculus)).Value;
@ -118,7 +121,7 @@ namespace IPA.Utilities
private static bool FindSteamVRAsset()
{
// these require assembly qualified names....
// TODO: fix this so that it works more consistently and generally
var steamUser = Type.GetType("Steamworks.SteamUser, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
return steamUser != null;

+ 55
- 4
Net3-Proxy/Utils.cs View File

@ -23,6 +23,14 @@ namespace Net3_Proxy
}
return true;
}
/// <summary>
/// Adds a value to the beginning of the sequence.
/// </summary>
/// <typeparam name="T">the type of the elements of <paramref name="seq"/></typeparam>
/// <param name="seq">a sequence of values</param>
/// <param name="prep">the value to prepend to <paramref name="seq"/></param>
/// <returns>a new sequence beginning with <paramref name="prep"/></returns>
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> seq, T prep)
=> new PrependEnumerable<T>(seq, prep);
@ -37,10 +45,53 @@ namespace Net3_Proxy
this.first = first;
}
public IEnumerator<T> GetEnumerator()
{ // TODO: a custom impl that is less garbage
yield return first;
foreach (var v in rest) yield return v;
public IEnumerator<T> GetEnumerator() => new PrependEnumerator(this);
private sealed class PrependEnumerator : IEnumerator<T>
{
private readonly IEnumerator<T> restEnum;
private readonly PrependEnumerable<T> enumerable;
private int state = 0;
public PrependEnumerator(PrependEnumerable<T> enumerable)
{
this.enumerable = enumerable;
restEnum = enumerable.rest.GetEnumerator();
}
public T Current { get; private set; }
object IEnumerator.Current => Current;
public void Dispose() => restEnum.Dispose();
public bool MoveNext()
{
switch (state)
{
case 0:
Current = enumerable.first;
state++;
return true;
case 1:
if (!restEnum.MoveNext())
{
state = 2;
return false;
}
else
Current = restEnum.Current;
return true;
case 2:
default:
return false;
}
}
public void Reset()
{
restEnum.Reset();
state = 0;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();


Loading…
Cancel
Save