mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
Attempt to merge BepInEx master
This commit is contained in:
commit
d0f1eabd78
36 files changed
+1638
-347
No files matched your search
@@ -34,9 +34,9 @@ jobs:
|
||||
branch: master
|
||||
workflow_id: build.yml
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/setup-dotnet@v1
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
dotnet-version: "8.0.x"
|
||||
- name: Build
|
||||
run: |
|
||||
./build.sh --target Publish --build-type ${{ steps.info.outputs.build_type }} --build-id ${{ steps.info.outputs.build_id }} --last-build-commit \"${{ steps.last_successful_commit.outputs.commit_hash }}\" --nuget-api-key \"${{ secrets.BEPIN_DEV_NUGET_KEY }}\"
|
||||
@@ -51,10 +51,10 @@ jobs:
|
||||
id: generate_changelog
|
||||
if: ${{ steps.info.outputs.build_type == 'BleedingEdge' }}
|
||||
run: |
|
||||
changelog=$(git --no-pager log --no-merges --pretty="format:- (\`%h\`) [%an] %s" ${{ steps.last_successful_commit.outputs.commit_hash }}..HEAD | sed ':a;N;$!ba;s/"/\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
|
||||
changelog=$(git --no-pager log --no-merges --pretty="format:- (\`%h\`) [%an] %s" ${{ steps.last_successful_commit.outputs.commit_hash }}..HEAD | sed ':a;N;$!ba;s/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
|
||||
echo "::set-output name=changelog::$changelog"
|
||||
- name: Notify success
|
||||
uses: joelwmale/webhook-action@2.1.0
|
||||
uses: joelwmale/webhook-action@2.4.1
|
||||
env:
|
||||
BEPINEX_DISCORD_WEBHOOK: ${{ secrets.BEPINEX_DISCORD_WEBHOOK }}
|
||||
if: ${{ steps.info.outputs.build_type == 'BleedingEdge' && env.BEPINEX_DISCORD_WEBHOOK != null }}
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
]
|
||||
}
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: "./bin/dist/*.zip"
|
||||
name: "BepInEx_CI_${{ steps.info.outputs.build_type }}_${{ steps.info.outputs.sha_short }}_${{ steps.info.outputs.build_id || 0 }}"
|
||||
|
||||
@@ -10,14 +10,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.5
|
||||
- uses: actions/setup-dotnet@v1
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
dotnet-version: "8.0.x"
|
||||
- name: Build
|
||||
run: |
|
||||
./build.sh --target Publish
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: "./bin/dist/*.zip"
|
||||
name: "BepInEx_CI"
|
||||
@@ -8,9 +8,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
<PackageReference Include="SemanticVersioning" Version="2.0.2"/>
|
||||
<PackageReference Include="MonoMod.Utils" Version="22.5.1.1"/>
|
||||
<PackageReference Include="MonoMod.Utils" Version="22.7.31.1"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Contract\IPlugin.cs"/>
|
||||
|
||||
@@ -315,7 +315,15 @@ public abstract class BaseChainloader<TPlugin>
|
||||
var plugins = DiscoverPlugins();
|
||||
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
|
||||
LoadPlugins(plugins);
|
||||
Finished?.Invoke();
|
||||
|
||||
try
|
||||
{
|
||||
Finished?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(LogLevel.Error, $"Error occurred in a Chainloader.Finished event handler: {ex}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -344,6 +352,7 @@ public abstract class BaseChainloader<TPlugin>
|
||||
{
|
||||
var dependsOnInvalidPlugin = false;
|
||||
var missingDependencies = new List<BepInDependency>();
|
||||
var incompatibleDependencies = new List<KeyValuePair<BepInDependency, string>>();
|
||||
foreach (var dependency in plugin.Dependencies)
|
||||
{
|
||||
static bool IsHardDependency(BepInDependency dep) =>
|
||||
@@ -359,15 +368,22 @@ public abstract class BaseChainloader<TPlugin>
|
||||
pluginVersion = pluginInfo?.Metadata.Version;
|
||||
}
|
||||
|
||||
if (!dependencyExists || dependency.VersionRange != null &&
|
||||
!dependency.VersionRange.IsSatisfied(pluginVersion))
|
||||
// The dependency is not installed at all.
|
||||
if (!dependencyExists)
|
||||
{
|
||||
// If the dependency is hard, collect it into a list to show
|
||||
if (IsHardDependency(dependency))
|
||||
missingDependencies.Add(dependency);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The dependency is installed, but its version does not satisfy the requested range.
|
||||
if (dependency.VersionRange != null && !dependency.VersionRange.IsSatisfied(pluginVersion))
|
||||
{
|
||||
if (IsHardDependency(dependency))
|
||||
incompatibleDependencies.Add(new KeyValuePair<BepInDependency, string>(dependency, pluginVersion?.ToString()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
|
||||
if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
|
||||
{
|
||||
@@ -387,13 +403,25 @@ public abstract class BaseChainloader<TPlugin>
|
||||
continue;
|
||||
}
|
||||
|
||||
if (missingDependencies.Count != 0)
|
||||
if (missingDependencies.Count != 0 || incompatibleDependencies.Count != 0)
|
||||
{
|
||||
var message = $@"Could not load [{plugin}] because it has missing dependencies: {
|
||||
string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
|
||||
}";
|
||||
DependencyErrors.Add(message);
|
||||
Logger.Log(LogLevel.Error, message);
|
||||
if (missingDependencies.Count != 0)
|
||||
{
|
||||
var message = $@"Could not load [{plugin}] because it has missing dependencies: {
|
||||
string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
|
||||
}. Install the listed plugin(s) and restart the game.";
|
||||
DependencyErrors.Add(message);
|
||||
Logger.Log(LogLevel.Error, message);
|
||||
}
|
||||
|
||||
if (incompatibleDependencies.Count != 0)
|
||||
{
|
||||
var message = $@"Could not load [{plugin}] because the following dependencies are installed with an incompatible version: {
|
||||
string.Join(", ", incompatibleDependencies.Select(s => $"{s.Key.DependencyGUID} (found {s.Value}, requires {s.Key.VersionRange})").ToArray())
|
||||
}. Update the listed plugin(s) to a version that satisfies the requirement.";
|
||||
DependencyErrors.Add(message);
|
||||
Logger.Log(LogLevel.Error, message);
|
||||
}
|
||||
|
||||
invalidPlugins.Add(plugin.Metadata.GUID);
|
||||
continue;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ConfigFile : IDictionary<ConfigDefinition, ConfigEntryBase>
|
||||
|
||||
if (File.Exists(ConfigFilePath))
|
||||
Reload();
|
||||
else if (saveOnInit) Save();
|
||||
else if (saveOnInit) TrySave();
|
||||
}
|
||||
|
||||
public static ConfigFile CoreConfig { get; } = new(Paths.BepInExConfigPath, true);
|
||||
@@ -352,6 +352,24 @@ public class ConfigFile : IDictionary<ConfigDefinition, ConfigEntryBase>
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the config to disk like <see cref="Save" />, but logs and swallows I/O failures (e.g. a read-only
|
||||
/// or locked config file) instead of throwing. Used only for the creation-time saves (initial file write and
|
||||
/// new-entry binds), which can run before logging is up; explicit saves and setting changes still throw.
|
||||
/// </summary>
|
||||
private void TrySave()
|
||||
{
|
||||
try
|
||||
{
|
||||
Save();
|
||||
}
|
||||
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
Logger.Log(LogLevel.Warning,
|
||||
$"Could not write config file {ConfigFilePath}: {e.Message}. Continuing with the in-memory configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wraps
|
||||
@@ -452,7 +470,7 @@ public class ConfigFile : IDictionary<ConfigDefinition, ConfigEntryBase>
|
||||
}
|
||||
|
||||
if (SaveOnConfigSet)
|
||||
Save();
|
||||
TrySave();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ internal partial class ConsoleEncoding : Encoding
|
||||
public override int GetByteCount(char[] chars, int index, int count)
|
||||
{
|
||||
WriteCharBuffer(chars, index, count);
|
||||
var result = WideCharToMultiByte(_codePage, 0, chars, count, _zeroByte, 0, IntPtr.Zero, IntPtr.Zero);
|
||||
var result = WideCharToMultiByte(_codePage, 0, _charBuffer, count, _zeroByte, 0, IntPtr.Zero, IntPtr.Zero);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ internal partial class ConsoleEncoding : Encoding
|
||||
var byteCount = GetByteCount(chars, charIndex, charCount);
|
||||
WriteCharBuffer(chars, charIndex, charCount);
|
||||
ExpandByteBuffer(byteCount);
|
||||
_ = WideCharToMultiByte(_codePage, 0, chars, charCount, _byteBuffer, byteCount, IntPtr.Zero,
|
||||
_ = WideCharToMultiByte(_codePage, 0, _charBuffer, charCount, _byteBuffer, byteCount, IntPtr.Zero,
|
||||
IntPtr.Zero);
|
||||
var readCount = Math.Min(bytes.Length, byteCount);
|
||||
ReadByteBuffer(bytes, byteIndex, readCount);
|
||||
@@ -60,7 +60,7 @@ internal partial class ConsoleEncoding : Encoding
|
||||
public override int GetCharCount(byte[] bytes, int index, int count)
|
||||
{
|
||||
WriteByteBuffer(bytes, index, count);
|
||||
var result = MultiByteToWideChar(_codePage, 0, bytes, count, _zeroChar, 0);
|
||||
var result = MultiByteToWideChar(_codePage, 0, _byteBuffer, count, _zeroChar, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ internal partial class ConsoleEncoding : Encoding
|
||||
var charCount = GetCharCount(bytes, byteIndex, byteCount);
|
||||
WriteByteBuffer(bytes, byteIndex, byteCount);
|
||||
ExpandCharBuffer(charCount);
|
||||
_ = MultiByteToWideChar(_codePage, 0, bytes, byteCount, _charBuffer, charCount);
|
||||
_ = MultiByteToWideChar(_codePage, 0, _byteBuffer, byteCount, _charBuffer, charCount);
|
||||
var readCount = Math.Min(chars.Length, charCount);
|
||||
ReadCharBuffer(chars, charIndex, readCount);
|
||||
return readCount;
|
||||
|
||||
@@ -92,8 +92,9 @@ internal class WindowsConsoleDriver : IConsoleDriver
|
||||
// Make sure of ConsoleEncoding helper class because on some Monos
|
||||
// Encoding.GetEncoding throws NotImplementedException on most codepages
|
||||
// NOTE: We don't set Console.OutputEncoding because it resets any existing Console.Out writers
|
||||
if (!useManagedEncoder)
|
||||
ConsoleEncoding.ConsoleCodePage = codepage;
|
||||
// Always set the console output codepage so the Windows console interprets bytes correctly,
|
||||
// regardless of whether we use a managed encoder or ConsoleEncoding to produce those bytes.
|
||||
ConsoleEncoding.ConsoleCodePage = codepage;
|
||||
|
||||
// If stdout exists, write to it, otherwise make it the same as console out
|
||||
// Not sure if this is needed? Does the original Console.Out still work?
|
||||
|
||||
@@ -120,8 +120,18 @@ public class BepInDependency : Attribute, ICacheable
|
||||
/// not load and an error will be logged instead.
|
||||
/// </summary>
|
||||
/// <param name="guid">The GUID of the referenced plugin.</param>
|
||||
/// <param name="version">The version range of the referenced plugin.</param>
|
||||
/// <remarks>When version is supplied the dependency is always treated as HardDependency</remarks>
|
||||
/// <param name="version">
|
||||
/// The version requirement of the referenced plugin, parsed as a SemVer range
|
||||
/// (see <see href="https://github.com/adamreeve/semver.net#ranges" />). A plain version such as
|
||||
/// <c>1.2.0</c> requires that exact version; use a range such as <c>>=1.2.0</c>, <c>1.2.*</c>,
|
||||
/// <c>~1.2.0</c> or <c>^1.2.0</c> to accept more than one version.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// When a version is supplied the dependency is always treated as a hard dependency.
|
||||
/// Plugins migrating from BepInEx 5 should note a behaviour change: a bare version was previously
|
||||
/// treated as a minimum (<c>>=</c>), whereas in BepInEx 6 it is an exact match. Use
|
||||
/// <c>>=1.2.0</c> to keep the old behaviour.
|
||||
/// </remarks>
|
||||
public BepInDependency(string guid, string version) : this(guid)
|
||||
{
|
||||
VersionRange = Range.Parse(version);
|
||||
|
||||
+151
-32
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BepInEx.Core.Logging.Interpolation;
|
||||
@@ -25,7 +26,7 @@ public static class Logger
|
||||
/// <summary>
|
||||
/// Log levels that are currently listened to by at least one listener.
|
||||
/// </summary>
|
||||
public static LogLevel ListenedLogLevels => listeners.activeLogLevels;
|
||||
public static LogLevel ListenedLogLevels => listeners.ActiveLogLevels;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of all log listeners that receive log events.
|
||||
@@ -39,10 +40,7 @@ public static class Logger
|
||||
|
||||
internal static void InternalLogEvent(object sender, LogEventArgs eventArgs)
|
||||
{
|
||||
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator Prevent extra allocations
|
||||
foreach (var listener in listeners)
|
||||
if ((eventArgs.Level & listener.LogLevelFilter) != LogLevel.None)
|
||||
listener?.LogEvent(sender, eventArgs);
|
||||
listeners.SendLogEvent(sender, eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,69 +72,190 @@ public static class Logger
|
||||
return source;
|
||||
}
|
||||
|
||||
private class LogListenerCollection : List<ILogListener>, ICollection<ILogListener>
|
||||
private class LogListenerCollection : ThreadSafeCollection<ILogListener>
|
||||
{
|
||||
public LogLevel activeLogLevels = LogLevel.None;
|
||||
internal LogLevel ActiveLogLevels = LogLevel.None;
|
||||
|
||||
void ICollection<ILogListener>.Add(ILogListener item)
|
||||
internal void SendLogEvent(object sender, LogEventArgs eventArgs)
|
||||
{
|
||||
// Do this instead of foreach to avoid boxing, also very slightly faster
|
||||
var aListInTime = BaseList;
|
||||
// ReSharper disable once ForCanBeConvertedToForeach
|
||||
for (int i = 0; i < aListInTime.Count; i++)
|
||||
{
|
||||
if ((eventArgs.Level & aListInTime[i].LogLevelFilter) != LogLevel.None)
|
||||
aListInTime[i].LogEvent(sender, eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Add(ILogListener item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
activeLogLevels |= item.LogLevelFilter;
|
||||
|
||||
base.Add(item);
|
||||
lock (SpinLock)
|
||||
{
|
||||
ActiveLogLevels |= item.LogLevelFilter;
|
||||
base.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<ILogListener>.Clear()
|
||||
public override void Clear()
|
||||
{
|
||||
activeLogLevels = LogLevel.None;
|
||||
base.Clear();
|
||||
lock (SpinLock)
|
||||
{
|
||||
ActiveLogLevels = LogLevel.None;
|
||||
base.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<ILogListener>.Remove(ILogListener item)
|
||||
public override bool Remove(ILogListener item)
|
||||
{
|
||||
if (item == null || !base.Remove(item))
|
||||
return false;
|
||||
|
||||
activeLogLevels = LogLevel.None;
|
||||
lock (SpinLock)
|
||||
{
|
||||
ActiveLogLevels = LogLevel.None;
|
||||
|
||||
foreach (var i in this)
|
||||
activeLogLevels |= i.LogLevelFilter;
|
||||
foreach (var i in this)
|
||||
ActiveLogLevels |= i.LogLevelFilter;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class LogSourceCollection : List<ILogSource>, ICollection<ILogSource>
|
||||
private class LogSourceCollection : ThreadSafeCollection<ILogSource>
|
||||
{
|
||||
void ICollection<ILogSource>.Add(ILogSource item)
|
||||
public override void Add(ILogSource item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item),
|
||||
"Log sources cannot be null when added to the source list.");
|
||||
|
||||
item.LogEvent += InternalLogEvent;
|
||||
|
||||
base.Add(item);
|
||||
lock (SpinLock)
|
||||
{
|
||||
item.LogEvent += InternalLogEvent;
|
||||
base.Add(item);
|
||||
var copy = new List<ILogSource>(BaseList.Count + 1);
|
||||
copy.AddRange(BaseList);
|
||||
copy.Add(item);
|
||||
BaseList = copy;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<ILogSource>.Clear()
|
||||
public override void Clear()
|
||||
{
|
||||
foreach (var item in this)
|
||||
item.LogEvent -= InternalLogEvent;
|
||||
if (Count == 0)
|
||||
return;
|
||||
|
||||
base.Clear();
|
||||
lock (SpinLock)
|
||||
{
|
||||
for (var i = 0; i < BaseList.Count; i++)
|
||||
BaseList[i].LogEvent -= InternalLogEvent;
|
||||
|
||||
BaseList = [];
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<ILogSource>.Remove(ILogSource item)
|
||||
public override bool Remove(ILogSource item)
|
||||
{
|
||||
if (item == null || !Remove(item))
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
item.LogEvent -= InternalLogEvent;
|
||||
return true;
|
||||
lock (SpinLock)
|
||||
{
|
||||
var wasPresent = base.Remove(item);
|
||||
if (wasPresent)
|
||||
item.LogEvent -= InternalLogEvent;
|
||||
return wasPresent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple thread safe list that prioritizes read speed over write speed.
|
||||
/// Read is the same as a normal list, while write locks and allocates a copy of the list.
|
||||
/// Logger lists are rarely updated so this tradeoff should be fine.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
private class ThreadSafeCollection<T> : ICollection<T> where T : class
|
||||
{
|
||||
protected readonly object SpinLock = new();
|
||||
protected List<T> BaseList = [];
|
||||
|
||||
public int Count => BaseList.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
// Can't avoid boxing
|
||||
return BaseList.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
// Can't avoid boxing
|
||||
return BaseList.GetEnumerator();
|
||||
}
|
||||
|
||||
public virtual void Add(T item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item), "item can't be null");
|
||||
|
||||
lock (SpinLock)
|
||||
{
|
||||
var copy = new List<T>(BaseList.Count + 1);
|
||||
copy.AddRange(BaseList);
|
||||
copy.Add(item);
|
||||
BaseList = copy;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
if (Count == 0)
|
||||
return;
|
||||
|
||||
lock (SpinLock)
|
||||
BaseList = [];
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return BaseList.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
BaseList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public virtual bool Remove(T item)
|
||||
{
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
lock (SpinLock)
|
||||
{
|
||||
var copy = new List<T>(BaseList.Count);
|
||||
var any = false;
|
||||
for (int i = 0; i < BaseList.Count; i++)
|
||||
{
|
||||
var existingItem = BaseList[i];
|
||||
if (existingItem.Equals(item))
|
||||
any = true;
|
||||
else
|
||||
copy.Add(existingItem);
|
||||
}
|
||||
|
||||
BaseList = copy;
|
||||
return any;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-4
@@ -1,4 +1,4 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MonoMod.Utils;
|
||||
@@ -19,6 +19,12 @@ public static class Paths
|
||||
Version.Parse(MetadataHelper.GetAttributes<AssemblyInformationalVersionAttribute>(typeof(Paths).Assembly)[0]
|
||||
.InformationalVersion);
|
||||
|
||||
/// <summary>
|
||||
/// BepInEx version without the build suffix.
|
||||
/// </summary>
|
||||
public static Version DisplayBepInExVersion { get; } =
|
||||
new(BepInExVersion.Major, BepInExVersion.Minor, BepInExVersion.Patch, BepInExVersion.PreRelease);
|
||||
|
||||
/// <summary>
|
||||
/// The path to the Managed folder that contains the main managed assemblies.
|
||||
/// </summary>
|
||||
@@ -106,9 +112,23 @@ public static class Paths
|
||||
? Utility.ParentDirectory(executablePath, 4)
|
||||
: Path.GetDirectoryName(executablePath);
|
||||
|
||||
GameDataPath = managedPath != null && gameDataRelativeToManaged
|
||||
? Path.GetDirectoryName(managedPath)
|
||||
: Path.Combine(GameRootPath, $"{ProcessName}_Data");
|
||||
if (managedPath != null && gameDataRelativeToManaged)
|
||||
{
|
||||
GameDataPath = Path.GetDirectoryName(managedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// According to some experiments, Unity checks whether globalgamemanagers/data.unity3d exists in the data folder before picking it.
|
||||
// 'ProcessName_Data' folder is checked first, then if that fails 'Data' folder is checked. If neither is valid, the player crashes.
|
||||
// A simple Directory.Exists check is accurate enough while being less likely to break in case these conditions change.
|
||||
GameDataPath = Path.Combine(GameRootPath, $"{ProcessName}_Data");
|
||||
if (!Directory.Exists(GameDataPath))
|
||||
GameDataPath = Path.Combine(GameRootPath, "Data");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(GameDataPath) || !Directory.Exists(GameDataPath))
|
||||
throw new DirectoryNotFoundException("Failed to extract valid GameDataPath from executablePath: " + executablePath);
|
||||
|
||||
ManagedPath = managedPath ?? Path.Combine(GameDataPath, "Managed");
|
||||
BepInExRootPath = bepinRootPath ?? Path.Combine(GameRootPath, "BepInEx");
|
||||
ConfigPath = Path.Combine(BepInExRootPath, "config");
|
||||
|
||||
+15
-4
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using BepInEx.Logging;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace BepInEx;
|
||||
@@ -416,11 +417,21 @@ public static class Utility
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var directory in directories)
|
||||
foreach (var file in Directory.GetFiles(directory, pattern))
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
if (!result.ContainsKey(fileName))
|
||||
result[fileName] = file;
|
||||
// A configured search directory (e.g. a missing unstripped_corlib) may not exist; warn and skip it
|
||||
// instead of letting Directory.GetFiles throw DirectoryNotFoundException.
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Logger.Log(LogLevel.Warning, $"Skipping search directory that does not exist: {directory}");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var file in Directory.GetFiles(directory, pattern))
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
if (!result.ContainsKey(fileName))
|
||||
result[fileName] = file;
|
||||
}
|
||||
}
|
||||
|
||||
return result.Values;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.5.1.1"/>
|
||||
<PackageReference Include="MonoMod.Utils" Version="22.5.1.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.7.31.1"/>
|
||||
<PackageReference Include="MonoMod.Utils" Version="22.7.31.1"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BepInEx.Logging;
|
||||
@@ -34,17 +35,14 @@ public static class ChainloaderLogHelper
|
||||
|
||||
public static void PrintLogInfo(ManualLogSource log)
|
||||
{
|
||||
var bepinVersion = Paths.BepInExVersion;
|
||||
var versionMini = new SemanticVersioning.Version(bepinVersion.Major, bepinVersion.Minor, bepinVersion.Patch,
|
||||
bepinVersion.PreRelease);
|
||||
var consoleTitle = $"BepInEx {versionMini} - {Paths.ProcessName}";
|
||||
log.Log(LogLevel.Message, consoleTitle);
|
||||
var consoleTitle = $"BepInEx {Paths.DisplayBepInExVersion} - {Paths.ProcessName}";
|
||||
log.Log(LogLevel.Message, $"{consoleTitle} ({File.GetLastWriteTime(Paths.ExecutablePath)})");
|
||||
|
||||
if (ConsoleManager.ConsoleActive)
|
||||
ConsoleManager.SetConsoleTitle(consoleTitle);
|
||||
|
||||
if (!string.IsNullOrEmpty(bepinVersion.Build))
|
||||
log.Log(LogLevel.Message, $"Built from commit {bepinVersion.Build}");
|
||||
if (!string.IsNullOrEmpty(Paths.BepInExVersion.Build))
|
||||
log.Log(LogLevel.Message, $"Built from commit {Paths.BepInExVersion.Build}");
|
||||
|
||||
Logger.Log(LogLevel.Info, $"System platform: {GetPlatformString()}");
|
||||
Logger.Log(LogLevel.Info,
|
||||
|
||||
@@ -82,7 +82,7 @@ public class PatcherPluginInfoAttribute : Attribute
|
||||
/// <summary>
|
||||
/// Defines an assembly that a patch method will target.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class TargetAssemblyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
@@ -108,7 +108,7 @@ public class TargetAssemblyAttribute : Attribute
|
||||
/// <summary>
|
||||
/// Defines a type that a patch method will target.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class TargetTypeAttribute : Attribute
|
||||
{
|
||||
/// <param name="targetAssembly">The short filename of the assembly of which <paramref name="targetType" /> belongs to.</param>
|
||||
|
||||
@@ -80,7 +80,12 @@ internal static class PlatformUtils
|
||||
if (wineGetVersion != IntPtr.Zero)
|
||||
{
|
||||
current |= Platform.Wine;
|
||||
var getVersion = wineGetVersion.AsDelegate<GetWineVersionDelegate>();
|
||||
// It's not safe to use the AsDelegate() extension method here because:
|
||||
// - It comes from the MonoMod.Utils.DynDll class, defined in MonoMod.Common.
|
||||
// - The DynDll class has a static constructor that reads PlatformHelper.Current.
|
||||
// - Reading from that property freezes it: subsequent writes will throw an exception.
|
||||
// - This method only sets PlatformHelper.Current at the very end.
|
||||
var getVersion = Marshal.GetDelegateForFunctionPointer(wineGetVersion, typeof(GetWineVersionDelegate)) as GetWineVersionDelegate;
|
||||
WineVersion = getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ A more comprehensive comparison list of features and compatibility is available
|
||||
|
||||
## Used libraries
|
||||
|
||||
- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.0.0
|
||||
- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.1
|
||||
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.5.1.1
|
||||
- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - v4.5.0
|
||||
- [BepInEx/HarmonyX](https://github.com/BepInEx/HarmonyX) - v2.10.2
|
||||
- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v22.7.31.1
|
||||
- [jbevain/cecil](https://github.com/jbevain/cecil) - v0.10.4
|
||||
|
||||
#### IL2CPP libraries
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<ProjectReference Include="..\..\..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
<Import Project="..\BepInEx.NET.Shared\BepInEx.NET.Shared.projitems" Label="Shared"/>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.VersionUtilities" Version="1.2.1"/>
|
||||
<PackageReference Include="AssetRipper.Primitives" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all"/>
|
||||
<PackageReference Include="MonoMod.Utils" Version="22.5.1.1"/>
|
||||
<PackageReference Include="MonoMod.Utils" Version="22.7.31.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using AssetRipper.VersionUtilities;
|
||||
using AssetRipper.Primitives;
|
||||
using MonoMod.Utils;
|
||||
|
||||
[assembly: InternalsVisibleTo("BepInEx.Unity.Mono.Preloader")]
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
<ProjectReference Include="..\BepInEx.Unity.Common\BepInEx.Unity.Common.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="Iced" Version="1.18.0"/>
|
||||
<PackageReference Include="Il2CppInterop.Generator" Version="1.4.6"/>
|
||||
<PackageReference Include="Il2CppInterop.HarmonySupport" Version="1.4.6"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
<PackageReference Include="Iced" Version="1.21.0" />
|
||||
<PackageReference Include="Il2CppInterop.Generator" Version="1.5.1-ci.829"/>
|
||||
<PackageReference Include="Il2CppInterop.HarmonySupport" Version="1.5.1-ci.829"/>
|
||||
<PackageReference Include="Il2CppInterop.ReferenceLibs" Version="1.0.0" IncludeAssets="compile" PrivateAssets="all"/>
|
||||
<PackageReference Include="Il2CppInterop.Runtime" Version="1.4.6"/>
|
||||
<PackageReference Include="Il2CppInterop.Runtime" Version="1.5.1-ci.829"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" IncludeAssets="compile" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" IncludeAssets="compile" PrivateAssets="all"/>
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.5.1.1"/>
|
||||
<PackageReference Include="Samboy063.Cpp2IL.Core" Version="2022.1.0-development.866"/>
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.7.31.1"/>
|
||||
<PackageReference Include="Samboy063.Cpp2IL.Core" Version="2022.1.0-development.1452"/>
|
||||
<PackageReference Include="Il2CppDumper" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -83,21 +83,19 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
if (methodName == "Internal_ActiveSceneChanged")
|
||||
try
|
||||
{
|
||||
if (ConfigUnityLogging.Value)
|
||||
{
|
||||
Logger.Sources.Add(new IL2CPPUnityLogSource());
|
||||
|
||||
Application.CallLogCallback("Test call after applying unity logging hook", "", LogType.Assert,
|
||||
true);
|
||||
}
|
||||
|
||||
// Unhook up front so the detour fires once even if setup below throws.
|
||||
unhook = true;
|
||||
|
||||
// Isolated so a missing-interop JIT failure happens inside the try (caught), not in OnInvokeMethod itself.
|
||||
SetupUnityLogging();
|
||||
|
||||
Il2CppInteropManager.PreloadInteropAssemblies();
|
||||
|
||||
Instance.Execute();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(LogLevel.Fatal, "Unable to execute IL2CPP chainloader");
|
||||
Logger.Log(LogLevel.Fatal, "Unable to execute IL2CPP chainloader, no plugins will be loaded");
|
||||
Logger.Log(LogLevel.Error, ex);
|
||||
}
|
||||
|
||||
@@ -113,6 +111,17 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
return result;
|
||||
}
|
||||
|
||||
// JIT-compiled only when called here, so OnInvokeMethod needs no interop assemblies present to JIT.
|
||||
private static void SetupUnityLogging()
|
||||
{
|
||||
if (!ConfigUnityLogging.Value)
|
||||
return;
|
||||
|
||||
Logger.Sources.Add(new IL2CPPUnityLogSource());
|
||||
|
||||
Application.CallLogCallback("Test call after applying unity logging hook", "", LogType.Assert, true);
|
||||
}
|
||||
|
||||
protected override void InitializeLoggers()
|
||||
{
|
||||
base.InitializeLoggers();
|
||||
|
||||
@@ -3,11 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using BepInEx.Unity.Common;
|
||||
@@ -55,9 +58,10 @@ internal static partial class Il2CppInteropManager
|
||||
"IL2CPP", "UnityBaseLibrariesSource",
|
||||
"https://unity.bepinex.dev/libraries/{VERSION}.zip",
|
||||
new StringBuilder()
|
||||
.AppendLine("URL to the ZIP of managed Unity base libraries.")
|
||||
.AppendLine("The base libraries are used by Il2CppInterop to generate interop assemblies.")
|
||||
.AppendLine("URL to a ZIP file with managed Unity base libraries. They are used by Il2CppInterop to generate interop assemblies.")
|
||||
.AppendLine("The URL can include {VERSION} template which will be replaced with the game's Unity engine version.")
|
||||
.AppendLine("If a .zip file with the same filename as the URL (after template replacement) already exists in unity-libs, it will be used instead of downloading a new copy.")
|
||||
.AppendLine("If you want to ensure BepInEx doesn't try to connect to the internet, set this to only the .zip filename (without a URL) and manually place the file in the unity-libs directory.")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<string> ConfigUnhollowerDeobfuscationRegex = ConfigFile.CoreConfig.Bind(
|
||||
@@ -89,6 +93,25 @@ internal static partial class Il2CppInteropManager
|
||||
.AppendLine("{ProcessName} - Name of the current process")
|
||||
.AppendLine("{ApplicationData} - Path to the user AppData/Roaming folder")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<bool> PreloadIL2CPPInteropAssemblies = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "PreloadIL2CPPInteropAssemblies",
|
||||
true,
|
||||
new StringBuilder()
|
||||
.AppendLine("Automatically load all interop assemblies right before loading plugins.")
|
||||
.AppendLine("Some plugins may not work properly without this, but it may cause issues in some games.")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<string> GlobalMetadataPath = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "GlobalMetadataPath",
|
||||
"{GameDataPath}/il2cpp_data/Metadata/global-metadata.dat",
|
||||
new StringBuilder()
|
||||
.AppendLine("The path to the IL2CPP metadata file.")
|
||||
.AppendLine("Supports the following placeholders:")
|
||||
.AppendLine("{BepInEx} - Path to the BepInEx folder.")
|
||||
.AppendLine("{ProcessName} - Name of the current process")
|
||||
.AppendLine("{GameDataPath} - Path to the game's Data folder.")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<bool> UseRuntimeAssemblies = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "UseRuntimeAssemblies",
|
||||
@@ -140,10 +163,8 @@ internal static partial class Il2CppInteropManager
|
||||
|
||||
private static string HashPath => Path.Combine(IL2CPPInteropAssemblyPath, "assembly-hash.txt");
|
||||
|
||||
private static string IL2CPPBasePath
|
||||
{
|
||||
get
|
||||
{
|
||||
private static string IL2CPPBasePath {
|
||||
get {
|
||||
if (il2cppInteropBasePath != null)
|
||||
return il2cppInteropBasePath;
|
||||
var path = Utility.GetCommandLineArgValue("--unhollowed-path") ?? IL2CPPInteropAssembliesPath.Value;
|
||||
@@ -160,6 +181,8 @@ internal static partial class Il2CppInteropManager
|
||||
|
||||
internal static string IL2CPPRuntimeBinariesPath => Path.Combine(IL2CPPBasePath, "runtime-bins");
|
||||
|
||||
private static string RenameMapPath => Path.Combine(Paths.BepInExRootPath, "DeobfuscationMap.csv.gz");
|
||||
|
||||
private static ILoggerFactory LoggerFactory { get; } = MSLoggerFactory.Create(b =>
|
||||
{
|
||||
b.AddProvider(new BepInExLoggerProvider())
|
||||
@@ -196,6 +219,11 @@ internal static partial class Il2CppInteropManager
|
||||
HashFile(md5, file);
|
||||
}
|
||||
|
||||
if (File.Exists(RenameMapPath))
|
||||
{
|
||||
HashFile(md5, RenameMapPath);
|
||||
}
|
||||
|
||||
// Hash some common dependencies as they can affect output
|
||||
HashString(md5, typeof(InteropAssemblyGenerator).Assembly.GetName().Version.ToString());
|
||||
HashString(md5, typeof(Cpp2IlApi).Assembly.GetName().Version.ToString());
|
||||
@@ -256,10 +284,10 @@ internal static partial class Il2CppInteropManager
|
||||
|
||||
var unityVersion = UnityInfo.Version;
|
||||
Il2CppInteropRuntime.Create(new RuntimeConfiguration
|
||||
{
|
||||
UnityVersion = new Version(unityVersion.Major, unityVersion.Minor, unityVersion.Build),
|
||||
DetourProvider = new Il2CppInteropDetourProvider()
|
||||
})
|
||||
{
|
||||
UnityVersion = new Version(unityVersion.Major, unityVersion.Minor, unityVersion.Build),
|
||||
DetourProvider = new Il2CppInteropDetourProvider()
|
||||
})
|
||||
.AddLogger(interopLogger)
|
||||
.AddHarmonySupport()
|
||||
.Start();
|
||||
@@ -277,17 +305,17 @@ internal static partial class Il2CppInteropManager
|
||||
|
||||
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(UnityBaseLibsDirectory);
|
||||
DownloadUnityAssemblies();
|
||||
var cecilAssemblies = UseRuntimeAssemblies.Value ? RunIl2CppDumperWithRuntimeBinaries() : new AsmToCecilConverter(RunCpp2Il()).ConvertAll();
|
||||
var asmResolverAssemblies = UseRuntimeAssemblies.Value ? RunIl2CppDumperWithRuntimeBinaries() : RunCpp2Il();
|
||||
|
||||
if (DumpDummyAssemblies.Value)
|
||||
{
|
||||
var dummyPath = Path.Combine(IL2CPPBasePath, "dummy");
|
||||
Directory.CreateDirectory(dummyPath);
|
||||
foreach (var assemblyDefinition in cecilAssemblies)
|
||||
assemblyDefinition.Write(Path.Combine(dummyPath, $"{assemblyDefinition.Name.Name}.dll"));
|
||||
foreach (var assemblyDefinition in asmResolverAssemblies)
|
||||
assemblyDefinition.Write(Path.Combine(dummyPath, $"{assemblyDefinition.Name}.dll"));
|
||||
}
|
||||
|
||||
RunIl2CppInteropGenerator(cecilAssemblies);
|
||||
RunIl2CppInteropGenerator(asmResolverAssemblies);
|
||||
|
||||
File.WriteAllText(HashPath, ComputeHash());
|
||||
}
|
||||
@@ -297,38 +325,72 @@ internal static partial class Il2CppInteropManager
|
||||
}
|
||||
}
|
||||
|
||||
private static void DownloadUnityAssemblies()
|
||||
{
|
||||
private static void DownloadUnityAssemblies() {
|
||||
var unityVersion = UnityInfo.Version;
|
||||
var source =
|
||||
UnityBaseLibrariesSource.Value.Replace("{VERSION}",
|
||||
$"{unityVersion.Major}.{unityVersion.Minor}.{unityVersion.Build}");
|
||||
var version = $"{unityVersion.Major}.{unityVersion.Minor}.{unityVersion.Build}";
|
||||
var source = UnityBaseLibrariesSource.Value.Replace("{VERSION}", version);
|
||||
if (string.IsNullOrEmpty(source)) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(source))
|
||||
var baseFolder = Directory.CreateDirectory(UnityBaseLibsDirectory);
|
||||
baseFolder.EnumerateFiles("*.dll").Do(a=>a.Delete());
|
||||
|
||||
var uriIsValid = Uri.TryCreate(source, UriKind.Absolute, out var uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
var fileName = Path.GetFileName(uriIsValid ? uri.AbsolutePath : source);
|
||||
var zipFilePath = Path.Combine(baseFolder.FullName, fileName);
|
||||
if (!File.Exists(zipFilePath))
|
||||
{
|
||||
Logger.LogMessage("Downloading unity base libraries");
|
||||
|
||||
Directory.CreateDirectory(UnityBaseLibsDirectory);
|
||||
Directory.EnumerateFiles(UnityBaseLibsDirectory, "*.dll").Do(File.Delete);
|
||||
// Check if URI is valid before attempting download
|
||||
if (!uriIsValid)
|
||||
throw new ArgumentException($"Unity base libraries source \"{source}\" is not a valid URL and the .zip file does not exist locally. Either provide a valid HTTP/HTTPS URL, or place the .zip file in the unity-libs directory.");
|
||||
|
||||
Logger.LogMessage($"Downloading unity base libraries from {source}");
|
||||
using var httpClient = new HttpClient();
|
||||
using var zipStream = httpClient.GetStreamAsync(source).GetAwaiter().GetResult();
|
||||
using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read);
|
||||
|
||||
Logger.LogMessage("Extracting downloaded unity base libraries");
|
||||
zipArchive.ExtractToDirectory(UnityBaseLibsDirectory);
|
||||
Stream zipStream;
|
||||
try
|
||||
{
|
||||
zipStream = httpClient.GetStreamAsync(uri).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException(
|
||||
$"Failed to download Unity base libraries for Unity {unityVersion} from {source}: {e.Message}. " +
|
||||
$"Base libraries for this Unity version may not be available on the server yet. Download a matching " +
|
||||
$"base-libraries .zip and place it in the \"{UnityBaseLibsDirectory}\" (unity-libs) directory, or set " +
|
||||
$"the [IL2CPP] UnityBaseLibrariesSource config option to a valid URL.", e);
|
||||
}
|
||||
|
||||
using (zipStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var writeStream = new FileStream(zipFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
zipStream.CopyTo(writeStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Delete the incomplete file to avoid issues on next startup
|
||||
try { File.Delete(zipFilePath); } catch { }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogMessage($"Extracting unity base libraries from {zipFilePath}");
|
||||
using var readStream = File.OpenRead(zipFilePath);
|
||||
using var zipArchive = new ZipArchive(readStream, ZipArchiveMode.Read);
|
||||
zipArchive.ExtractToDirectory(UnityBaseLibsDirectory);
|
||||
}
|
||||
|
||||
private static List<AsmResolver.DotNet.AssemblyDefinition> RunCpp2Il()
|
||||
{
|
||||
Logger.LogMessage("Running Cpp2IL to generate dummy assemblies");
|
||||
|
||||
var metadataPath = Path.Combine(Paths.GameRootPath,
|
||||
$"{Paths.ProcessName}_Data",
|
||||
"il2cpp_data",
|
||||
"Metadata",
|
||||
"global-metadata.dat");
|
||||
GlobalMetadataPath.Value
|
||||
.Replace("{BepInEx}", Paths.BepInExRootPath)
|
||||
.Replace("{ProcessName}", Paths.ProcessName)
|
||||
.Replace("{GameDataPath}", Paths.GameDataPath));
|
||||
|
||||
Logger.LogMessage("Running Cpp2IL to generate dummy assemblies from " + metadataPath);
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
@@ -359,7 +421,7 @@ internal static partial class Il2CppInteropManager
|
||||
cpp2IlProcessingLayer.Process(Cpp2IlApi.CurrentAppContext);
|
||||
}
|
||||
|
||||
var assemblies = new AsmResolverDummyDllOutputFormat().BuildAssemblies(Cpp2IlApi.CurrentAppContext);
|
||||
var assemblies = new AsmResolverDllOutputFormatDefault().BuildAssemblies(Cpp2IlApi.CurrentAppContext);
|
||||
|
||||
LibCpp2IlMain.Reset();
|
||||
Cpp2IlApi.CurrentAppContext = null;
|
||||
@@ -370,7 +432,7 @@ internal static partial class Il2CppInteropManager
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
private static List<AssemblyDefinition> RunIl2CppDumperWithRuntimeBinaries()
|
||||
private static List<AsmResolver.DotNet.AssemblyDefinition> RunIl2CppDumperWithRuntimeBinaries()
|
||||
{
|
||||
var metadataPath = Path.Combine(Paths.GameRootPath,
|
||||
$"{Paths.ProcessName}_Data",
|
||||
@@ -423,7 +485,7 @@ internal static partial class Il2CppInteropManager
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
private static void RunIl2CppInteropGenerator(List<AssemblyDefinition> sourceAssemblies)
|
||||
private static void RunIl2CppInteropGenerator(List<AsmResolver.DotNet.AssemblyDefinition> sourceAssemblies)
|
||||
{
|
||||
var opts = new GeneratorOptions
|
||||
{
|
||||
@@ -436,11 +498,10 @@ internal static partial class Il2CppInteropManager
|
||||
: null,
|
||||
};
|
||||
|
||||
var renameMapLocation = Path.Combine(Paths.BepInExRootPath, "DeobfuscationMap.csv.gz");
|
||||
if (File.Exists(renameMapLocation))
|
||||
if (File.Exists(RenameMapPath))
|
||||
{
|
||||
Logger.LogInfo("Parsing deobfuscation rename mappings");
|
||||
opts.ReadRenameMap(renameMapLocation);
|
||||
opts.ReadRenameMap(RenameMapPath);
|
||||
}
|
||||
|
||||
Logger.LogInfo("Generating interop assemblies");
|
||||
@@ -451,7 +512,35 @@ internal static partial class Il2CppInteropManager
|
||||
.AddLogger(logger)
|
||||
.AddInteropAssemblyGenerator()
|
||||
.Run();
|
||||
}
|
||||
|
||||
sourceAssemblies.Do(x => x.Dispose());
|
||||
internal static void PreloadInteropAssemblies()
|
||||
{
|
||||
if (!PreloadIL2CPPInteropAssemblies.Value)
|
||||
return;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
var files = Directory.EnumerateFiles(IL2CPPInteropAssemblyPath);
|
||||
var loaded = 0;
|
||||
Parallel.ForEach(files, file =>
|
||||
{
|
||||
if (!file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) return;
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
if (name.Equals("netstandard", StringComparison.OrdinalIgnoreCase)) return;
|
||||
if (name.Equals("Il2Cppnetstandard", StringComparison.OrdinalIgnoreCase)) return;
|
||||
try
|
||||
{
|
||||
// Do not use LoadFrom since it overrides preloader patches
|
||||
Assembly.Load(name);
|
||||
Interlocked.Increment(ref loaded);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning($"Failed to preload {file} - {e}");
|
||||
}
|
||||
});
|
||||
|
||||
Logger.LogDebug($"Preloaded {loaded} interop assemblies in {sw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -31,9 +31,9 @@ internal class EntrypointPatcher : BasePatcher
|
||||
"The name of the method in the specified entrypoint assembly and type to hook and load Chainloader from.");
|
||||
|
||||
private static string DefaultEntrypointAssembly =>
|
||||
UnityInfo.Version.IsLess(2017) ? "UnityEngine.dll" : "UnityEngine.CoreModule.dll";
|
||||
UnityInfo.Version.LessThan(2017) ? "UnityEngine.dll" : "UnityEngine.CoreModule.dll";
|
||||
|
||||
private static string DefaultEntrypointType => UnityInfo.Version.IsLess(5) ? "MonoBehaviour" : "Application";
|
||||
private static string DefaultEntrypointType => UnityInfo.Version.LessThan(5) ? "MonoBehaviour" : "Application";
|
||||
|
||||
private bool HasLoaded { get; set; }
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@ internal static class UnityPreloaderRunner
|
||||
"Mono.Cecil.dll",
|
||||
"Mono.Cecil.Mdb.dll",
|
||||
"Mono.Cecil.Pdb.dll",
|
||||
"Mono.Cecil.Rocks.dll"
|
||||
"Mono.Cecil.Rocks.dll",
|
||||
"MonoMod.Utils.dll",
|
||||
"MonoMod.RuntimeDetour.dll",
|
||||
"0Harmony.dll"
|
||||
};
|
||||
|
||||
private static void LoadCriticalAssemblies()
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.2" />
|
||||
<PackageReference Include="UnityEngine" Version="5.6.1" IncludeAssets="compile"/>
|
||||
<PackageReference Include="Unity.InputSystem" version="1.5.0" IncludeAssets="compile"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -35,17 +35,8 @@ public struct KeyboardShortcut
|
||||
/// </summary>
|
||||
public static readonly KeyboardShortcut Empty = new();
|
||||
|
||||
/// <summary>
|
||||
/// All KeyCode values that can be used in a keyboard shortcut.
|
||||
/// </summary>
|
||||
public static readonly IEnumerable<KeyCode> AllKeyCodes = (KeyCode[]) Enum.GetValues(typeof(KeyCode));
|
||||
|
||||
// Don't block hotkeys if mouse is being pressed, e.g. when shooting and trying to strafe
|
||||
private static readonly KeyCode[] _modifierBlockKeyCodes = AllKeyCodes.Except(new[]
|
||||
{
|
||||
KeyCode.Mouse0, KeyCode.Mouse1, KeyCode.Mouse2, KeyCode.Mouse3, KeyCode.Mouse4, KeyCode.Mouse5,
|
||||
KeyCode.Mouse6, KeyCode.None
|
||||
}).ToArray();
|
||||
private static KeyCode[] modifierBlockKeyCodes;
|
||||
|
||||
private readonly KeyCode[] _allKeys;
|
||||
|
||||
@@ -122,7 +113,7 @@ public struct KeyboardShortcut
|
||||
var mainKey = MainKey;
|
||||
if (mainKey == KeyCode.None) return false;
|
||||
|
||||
return Input.GetKeyDown(mainKey) && ModifierKeyTest();
|
||||
return UnityInput.Current.GetKeyDown(mainKey) && ModifierKeyTest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,7 +124,7 @@ public struct KeyboardShortcut
|
||||
var mainKey = MainKey;
|
||||
if (mainKey == KeyCode.None) return false;
|
||||
|
||||
return Input.GetKey(mainKey) && ModifierKeyTest();
|
||||
return UnityInput.Current.GetKey(mainKey) && ModifierKeyTest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +135,7 @@ public struct KeyboardShortcut
|
||||
var mainKey = MainKey;
|
||||
if (mainKey == KeyCode.None) return false;
|
||||
|
||||
return Input.GetKeyUp(mainKey) && ModifierKeyTest();
|
||||
return UnityInput.Current.GetKeyUp(mainKey) && ModifierKeyTest();
|
||||
}
|
||||
|
||||
private bool ModifierKeyTest()
|
||||
@@ -152,10 +143,15 @@ public struct KeyboardShortcut
|
||||
var allKeys = _allKeys;
|
||||
var mainKey = MainKey;
|
||||
|
||||
var allModifiersPressed = allKeys.All(c => c == mainKey || Input.GetKey(c));
|
||||
var allModifiersPressed = allKeys.All(c => c == mainKey || UnityInput.Current.GetKey(c));
|
||||
if (!allModifiersPressed) return false;
|
||||
|
||||
var noOtherModifiersPressed = _modifierBlockKeyCodes.All(c => !Input.GetKey(c) || allKeys.Contains(c));
|
||||
// Lazy init to make sure the game is initialized, and we're in main thread before calling UnityInput.Current
|
||||
modifierBlockKeyCodes ??= UnityInput.Current.SupportedKeyCodes.Except([KeyCode.Mouse0, KeyCode.Mouse1,
|
||||
KeyCode.Mouse2, KeyCode.Mouse3, KeyCode.Mouse4, KeyCode.Mouse5, KeyCode.Mouse6, KeyCode.None
|
||||
]).ToArray();
|
||||
|
||||
bool noOtherModifiersPressed = modifierBlockKeyCodes.All(c => !UnityInput.Current.GetKey(c) || allKeys.Contains(c));
|
||||
return noOtherModifiersPressed;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,854 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BepInEx.Logging;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace BepInEx
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction layer over Unity's input systems for use in universal plugins that need to use hotkeys.
|
||||
/// It can use either Input or Unity.InputSystem, depending on what's available. Input is preferred.
|
||||
/// WARNING: Use only inside of Unity's main thread!
|
||||
/// </summary>
|
||||
public class UnityInput
|
||||
{
|
||||
private static IInputSystem current;
|
||||
/// <summary>
|
||||
/// Best currently supported input system.
|
||||
/// </summary>
|
||||
public static IInputSystem Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (current == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Input.GetKeyDown(KeyCode.A);
|
||||
current = new LegacyInputSystem();
|
||||
Logging.Logger.Log(LogLevel.Debug, "[UnityInput] Using LegacyInputSystem");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
current = new NewInputSystem();
|
||||
Logging.Logger.Log(LogLevel.Debug, "[UnityInput] Using NewInputSystem");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Logger.Log(LogLevel.Warning, "[UnityInput] Failed to detect available input systems - " + ex);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the Input class is not disabled.
|
||||
/// </summary>
|
||||
public bool LegacyInputSystemAvailable => Current is LegacyInputSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic input system interface. Just barely good enough for hotkeys.
|
||||
/// </summary>
|
||||
public interface IInputSystem
|
||||
{
|
||||
// No easy way to use these with the new input system
|
||||
//bool GetButton(string buttonName);
|
||||
//bool GetButtonDown(string buttonName);
|
||||
//bool GetButtonUp(string buttonName);
|
||||
|
||||
/// <inheritdoc cref="Input.GetKey(string)"/>
|
||||
bool GetKey(string name);
|
||||
/// <inheritdoc cref="Input.GetKey(KeyCode)"/>
|
||||
bool GetKey(KeyCode key);
|
||||
|
||||
/// <inheritdoc cref="Input.GetKeyDown(string)"/>
|
||||
bool GetKeyDown(string name);
|
||||
/// <inheritdoc cref="Input.GetKeyDown(KeyCode)"/>
|
||||
bool GetKeyDown(KeyCode key);
|
||||
|
||||
/// <inheritdoc cref="Input.GetKeyUp(string)"/>
|
||||
bool GetKeyUp(string name);
|
||||
/// <inheritdoc cref="Input.GetKeyUp(KeyCode)"/>
|
||||
bool GetKeyUp(KeyCode key);
|
||||
|
||||
|
||||
/// <inheritdoc cref="Input.GetMouseButton(int)"/>
|
||||
bool GetMouseButton(int button);
|
||||
/// <inheritdoc cref="Input.GetMouseButtonDown(int)"/>
|
||||
bool GetMouseButtonDown(int button);
|
||||
/// <inheritdoc cref="Input.GetMouseButtonUp(int)"/>
|
||||
bool GetMouseButtonUp(int button);
|
||||
|
||||
/// <inheritdoc cref="Input.ResetInputAxes()"/>
|
||||
void ResetInputAxes();
|
||||
|
||||
/// <inheritdoc cref="Input.mousePosition"/>
|
||||
Vector3 mousePosition { get; }
|
||||
/// <inheritdoc cref="Input.mouseScrollDelta"/>
|
||||
Vector2 mouseScrollDelta { get; }
|
||||
|
||||
/// <inheritdoc cref="Input.mousePresent"/>
|
||||
bool mousePresent { get; }
|
||||
|
||||
/// <inheritdoc cref="Input.anyKey"/>
|
||||
bool anyKey { get; }
|
||||
/// <inheritdoc cref="Input.anyKeyDown"/>
|
||||
bool anyKeyDown { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All KeyCodes supported by the current input system.
|
||||
/// </summary>
|
||||
IEnumerable<KeyCode> SupportedKeyCodes { get; }
|
||||
}
|
||||
|
||||
internal class NewInputSystem : IInputSystem
|
||||
{
|
||||
public bool GetKey(string name) => GetControl(name)?.isPressed ?? false;
|
||||
|
||||
public bool GetKey(KeyCode key) => GetControl(key)?.isPressed ?? false;
|
||||
|
||||
public bool GetKeyDown(string name) => GetControl(name)?.wasPressedThisFrame ?? false;
|
||||
|
||||
public bool GetKeyDown(KeyCode key) => GetControl(key)?.wasPressedThisFrame ?? false;
|
||||
|
||||
public bool GetKeyUp(string name) => GetControl(name)?.wasReleasedThisFrame ?? false;
|
||||
|
||||
public bool GetKeyUp(KeyCode key) => GetControl(key)?.wasReleasedThisFrame ?? false;
|
||||
|
||||
public bool GetMouseButton(int button) => GetControl(KeyCode.Mouse0 + button)?.isPressed ?? false;
|
||||
|
||||
public bool GetMouseButtonDown(int button) => GetControl(KeyCode.Mouse0 + button)?.wasPressedThisFrame ?? false;
|
||||
|
||||
public bool GetMouseButtonUp(int button) => GetControl(KeyCode.Mouse0 + button)?.wasReleasedThisFrame ?? false;
|
||||
|
||||
public void ResetInputAxes() { /*Not supported*/ }
|
||||
|
||||
public Vector3 mousePosition => Mouse.current.position.ReadValue();
|
||||
public Vector2 mouseScrollDelta => Mouse.current.scroll.ReadValue();
|
||||
public bool mousePresent => Mouse.current.enabled;
|
||||
|
||||
public bool anyKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Keyboard.current.anyKey.isPressed)
|
||||
return true;
|
||||
var current = Mouse.current;
|
||||
return current.leftButton.isPressed ||
|
||||
current.rightButton.isPressed ||
|
||||
current.forwardButton.isPressed ||
|
||||
current.backButton.isPressed ||
|
||||
current.middleButton.isPressed;
|
||||
}
|
||||
}
|
||||
|
||||
public bool anyKeyDown
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Keyboard.current.anyKey.wasPressedThisFrame)
|
||||
return true;
|
||||
var current = Mouse.current;
|
||||
return current.leftButton.wasPressedThisFrame ||
|
||||
current.rightButton.wasPressedThisFrame ||
|
||||
current.forwardButton.wasPressedThisFrame ||
|
||||
current.backButton.wasPressedThisFrame ||
|
||||
current.middleButton.wasPressedThisFrame;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyCode> SupportedKeyCodes { get; } = Enum.GetValues(typeof(KeyCode)).Cast<KeyCode>().Where(x => GetControl(x, true) != null).ToList();
|
||||
|
||||
private static ButtonControl GetControl(string name) => GetControl((KeyCode)Enum.Parse(typeof(KeyCode), name, true));
|
||||
|
||||
private static ButtonControl GetControl(KeyCode key, bool silent = false)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyCode.None:
|
||||
break;
|
||||
case KeyCode.Backspace:
|
||||
return Keyboard.current.backspaceKey;
|
||||
case KeyCode.Delete:
|
||||
return Keyboard.current.deleteKey;
|
||||
case KeyCode.Tab:
|
||||
return Keyboard.current.tabKey;
|
||||
case KeyCode.Clear:
|
||||
break;
|
||||
case KeyCode.Return:
|
||||
return Keyboard.current.enterKey;
|
||||
case KeyCode.Pause:
|
||||
return Keyboard.current.pauseKey;
|
||||
case KeyCode.Escape:
|
||||
return Keyboard.current.escapeKey;
|
||||
case KeyCode.Space:
|
||||
return Keyboard.current.spaceKey;
|
||||
case KeyCode.Keypad0:
|
||||
return Keyboard.current.numpad0Key;
|
||||
case KeyCode.Keypad1:
|
||||
return Keyboard.current.numpad1Key;
|
||||
case KeyCode.Keypad2:
|
||||
return Keyboard.current.numpad2Key;
|
||||
case KeyCode.Keypad3:
|
||||
return Keyboard.current.numpad3Key;
|
||||
case KeyCode.Keypad4:
|
||||
return Keyboard.current.numpad4Key;
|
||||
case KeyCode.Keypad5:
|
||||
return Keyboard.current.numpad5Key;
|
||||
case KeyCode.Keypad6:
|
||||
return Keyboard.current.numpad6Key;
|
||||
case KeyCode.Keypad7:
|
||||
return Keyboard.current.numpad7Key;
|
||||
case KeyCode.Keypad8:
|
||||
return Keyboard.current.numpad8Key;
|
||||
case KeyCode.Keypad9:
|
||||
return Keyboard.current.numpad9Key;
|
||||
case KeyCode.KeypadPeriod:
|
||||
return Keyboard.current.numpadPeriodKey;
|
||||
case KeyCode.KeypadDivide:
|
||||
return Keyboard.current.numpadDivideKey;
|
||||
case KeyCode.KeypadMultiply:
|
||||
return Keyboard.current.numpadMultiplyKey;
|
||||
case KeyCode.KeypadMinus:
|
||||
return Keyboard.current.numpadMinusKey;
|
||||
case KeyCode.KeypadPlus:
|
||||
return Keyboard.current.numpadPlusKey;
|
||||
case KeyCode.KeypadEnter:
|
||||
return Keyboard.current.numpadEnterKey;
|
||||
case KeyCode.KeypadEquals:
|
||||
return Keyboard.current.numpadEqualsKey;
|
||||
case KeyCode.UpArrow:
|
||||
return Keyboard.current.upArrowKey;
|
||||
case KeyCode.DownArrow:
|
||||
return Keyboard.current.downArrowKey;
|
||||
case KeyCode.RightArrow:
|
||||
return Keyboard.current.rightArrowKey;
|
||||
case KeyCode.LeftArrow:
|
||||
return Keyboard.current.leftArrowKey;
|
||||
case KeyCode.Insert:
|
||||
return Keyboard.current.insertKey;
|
||||
case KeyCode.Home:
|
||||
return Keyboard.current.homeKey;
|
||||
case KeyCode.End:
|
||||
return Keyboard.current.endKey;
|
||||
case KeyCode.PageUp:
|
||||
return Keyboard.current.pageUpKey;
|
||||
case KeyCode.PageDown:
|
||||
return Keyboard.current.pageDownKey;
|
||||
case KeyCode.F1:
|
||||
return Keyboard.current.f1Key;
|
||||
case KeyCode.F2:
|
||||
return Keyboard.current.f2Key;
|
||||
case KeyCode.F3:
|
||||
return Keyboard.current.f3Key;
|
||||
case KeyCode.F4:
|
||||
return Keyboard.current.f4Key;
|
||||
case KeyCode.F5:
|
||||
return Keyboard.current.f5Key;
|
||||
case KeyCode.F6:
|
||||
return Keyboard.current.f6Key;
|
||||
case KeyCode.F7:
|
||||
return Keyboard.current.f7Key;
|
||||
case KeyCode.F8:
|
||||
return Keyboard.current.f8Key;
|
||||
case KeyCode.F9:
|
||||
return Keyboard.current.f9Key;
|
||||
case KeyCode.F10:
|
||||
return Keyboard.current.f10Key;
|
||||
case KeyCode.F11:
|
||||
return Keyboard.current.f11Key;
|
||||
case KeyCode.F12:
|
||||
return Keyboard.current.f12Key;
|
||||
case KeyCode.F13:
|
||||
break;
|
||||
case KeyCode.F14:
|
||||
break;
|
||||
case KeyCode.F15:
|
||||
break;
|
||||
case KeyCode.Alpha0:
|
||||
return Keyboard.current.digit0Key;
|
||||
case KeyCode.Alpha1:
|
||||
return Keyboard.current.digit1Key;
|
||||
case KeyCode.Alpha2:
|
||||
return Keyboard.current.digit2Key;
|
||||
case KeyCode.Alpha3:
|
||||
return Keyboard.current.digit3Key;
|
||||
case KeyCode.Alpha4:
|
||||
return Keyboard.current.digit4Key;
|
||||
case KeyCode.Alpha5:
|
||||
return Keyboard.current.digit5Key;
|
||||
case KeyCode.Alpha6:
|
||||
return Keyboard.current.digit6Key;
|
||||
case KeyCode.Alpha7:
|
||||
return Keyboard.current.digit7Key;
|
||||
case KeyCode.Alpha8:
|
||||
return Keyboard.current.digit8Key;
|
||||
case KeyCode.Alpha9:
|
||||
return Keyboard.current.digit9Key;
|
||||
case KeyCode.Exclaim:
|
||||
break;
|
||||
case KeyCode.DoubleQuote:
|
||||
break;
|
||||
case KeyCode.Hash:
|
||||
break;
|
||||
case KeyCode.Dollar:
|
||||
break;
|
||||
case KeyCode.Ampersand:
|
||||
break;
|
||||
case KeyCode.Quote:
|
||||
return Keyboard.current.quoteKey;
|
||||
case KeyCode.LeftParen:
|
||||
break;
|
||||
case KeyCode.RightParen:
|
||||
break;
|
||||
case KeyCode.Asterisk:
|
||||
break;
|
||||
case KeyCode.Plus:
|
||||
return Keyboard.current.numpadPlusKey;
|
||||
case KeyCode.Comma:
|
||||
return Keyboard.current.commaKey;
|
||||
case KeyCode.Minus:
|
||||
return Keyboard.current.minusKey;
|
||||
case KeyCode.Period:
|
||||
return Keyboard.current.periodKey;
|
||||
case KeyCode.Slash:
|
||||
return Keyboard.current.slashKey;
|
||||
case KeyCode.Colon:
|
||||
break;
|
||||
case KeyCode.Semicolon:
|
||||
return Keyboard.current.semicolonKey;
|
||||
case KeyCode.Less:
|
||||
break;
|
||||
case KeyCode.Equals:
|
||||
return Keyboard.current.equalsKey;
|
||||
case KeyCode.Greater:
|
||||
break;
|
||||
case KeyCode.Question:
|
||||
break;
|
||||
case KeyCode.At:
|
||||
break;
|
||||
case KeyCode.LeftBracket:
|
||||
return Keyboard.current.leftBracketKey;
|
||||
case KeyCode.Backslash:
|
||||
return Keyboard.current.backslashKey;
|
||||
case KeyCode.RightBracket:
|
||||
return Keyboard.current.rightBracketKey;
|
||||
case KeyCode.Caret:
|
||||
break;
|
||||
case KeyCode.Underscore:
|
||||
break;
|
||||
case KeyCode.BackQuote:
|
||||
return Keyboard.current.backquoteKey;
|
||||
case KeyCode.A:
|
||||
return Keyboard.current.aKey;
|
||||
case KeyCode.B:
|
||||
return Keyboard.current.bKey;
|
||||
case KeyCode.C:
|
||||
return Keyboard.current.cKey;
|
||||
case KeyCode.D:
|
||||
return Keyboard.current.dKey;
|
||||
case KeyCode.E:
|
||||
return Keyboard.current.eKey;
|
||||
case KeyCode.F:
|
||||
return Keyboard.current.fKey;
|
||||
case KeyCode.G:
|
||||
return Keyboard.current.gKey;
|
||||
case KeyCode.H:
|
||||
return Keyboard.current.hKey;
|
||||
case KeyCode.I:
|
||||
return Keyboard.current.iKey;
|
||||
case KeyCode.J:
|
||||
return Keyboard.current.jKey;
|
||||
case KeyCode.K:
|
||||
return Keyboard.current.kKey;
|
||||
case KeyCode.L:
|
||||
return Keyboard.current.lKey;
|
||||
case KeyCode.M:
|
||||
return Keyboard.current.mKey;
|
||||
case KeyCode.N:
|
||||
return Keyboard.current.nKey;
|
||||
case KeyCode.O:
|
||||
return Keyboard.current.oKey;
|
||||
case KeyCode.P:
|
||||
return Keyboard.current.pKey;
|
||||
case KeyCode.Q:
|
||||
return Keyboard.current.qKey;
|
||||
case KeyCode.R:
|
||||
return Keyboard.current.rKey;
|
||||
case KeyCode.S:
|
||||
return Keyboard.current.sKey;
|
||||
case KeyCode.T:
|
||||
return Keyboard.current.tKey;
|
||||
case KeyCode.U:
|
||||
return Keyboard.current.uKey;
|
||||
case KeyCode.V:
|
||||
return Keyboard.current.vKey;
|
||||
case KeyCode.W:
|
||||
return Keyboard.current.wKey;
|
||||
case KeyCode.X:
|
||||
return Keyboard.current.xKey;
|
||||
case KeyCode.Y:
|
||||
return Keyboard.current.yKey;
|
||||
case KeyCode.Z:
|
||||
return Keyboard.current.zKey;
|
||||
case KeyCode.Numlock:
|
||||
return Keyboard.current.numLockKey;
|
||||
case KeyCode.CapsLock:
|
||||
return Keyboard.current.capsLockKey;
|
||||
case KeyCode.ScrollLock:
|
||||
return Keyboard.current.scrollLockKey;
|
||||
case KeyCode.RightShift:
|
||||
return Keyboard.current.rightShiftKey;
|
||||
case KeyCode.LeftShift:
|
||||
return Keyboard.current.leftShiftKey;
|
||||
case KeyCode.RightControl:
|
||||
return Keyboard.current.rightCtrlKey;
|
||||
case KeyCode.LeftControl:
|
||||
return Keyboard.current.leftCtrlKey;
|
||||
case KeyCode.RightAlt:
|
||||
return Keyboard.current.rightAltKey;
|
||||
case KeyCode.LeftAlt:
|
||||
return Keyboard.current.leftAltKey;
|
||||
case KeyCode.LeftCommand:
|
||||
return Keyboard.current.leftCommandKey;
|
||||
case KeyCode.LeftWindows:
|
||||
return Keyboard.current.leftWindowsKey;
|
||||
case KeyCode.RightCommand:
|
||||
return Keyboard.current.rightCommandKey;
|
||||
case KeyCode.RightWindows:
|
||||
return Keyboard.current.rightWindowsKey;
|
||||
case KeyCode.AltGr:
|
||||
break;
|
||||
case KeyCode.Help:
|
||||
break;
|
||||
case KeyCode.Print:
|
||||
return Keyboard.current.printScreenKey;
|
||||
case KeyCode.SysReq:
|
||||
break;
|
||||
case KeyCode.Break:
|
||||
break;
|
||||
case KeyCode.Menu:
|
||||
return Keyboard.current.contextMenuKey;
|
||||
case KeyCode.Mouse0:
|
||||
return Mouse.current.leftButton;
|
||||
case KeyCode.Mouse1:
|
||||
return Mouse.current.rightButton;
|
||||
case KeyCode.Mouse2:
|
||||
return Mouse.current.middleButton;
|
||||
case KeyCode.Mouse3:
|
||||
return Mouse.current.backButton;
|
||||
case KeyCode.Mouse4:
|
||||
return Mouse.current.forwardButton;
|
||||
case KeyCode.Mouse5:
|
||||
break;
|
||||
case KeyCode.Mouse6:
|
||||
break;
|
||||
case KeyCode.JoystickButton0:
|
||||
break;
|
||||
case KeyCode.JoystickButton1:
|
||||
break;
|
||||
case KeyCode.JoystickButton2:
|
||||
break;
|
||||
case KeyCode.JoystickButton3:
|
||||
break;
|
||||
case KeyCode.JoystickButton4:
|
||||
break;
|
||||
case KeyCode.JoystickButton5:
|
||||
break;
|
||||
case KeyCode.JoystickButton6:
|
||||
break;
|
||||
case KeyCode.JoystickButton7:
|
||||
break;
|
||||
case KeyCode.JoystickButton8:
|
||||
break;
|
||||
case KeyCode.JoystickButton9:
|
||||
break;
|
||||
case KeyCode.JoystickButton10:
|
||||
break;
|
||||
case KeyCode.JoystickButton11:
|
||||
break;
|
||||
case KeyCode.JoystickButton12:
|
||||
break;
|
||||
case KeyCode.JoystickButton13:
|
||||
break;
|
||||
case KeyCode.JoystickButton14:
|
||||
break;
|
||||
case KeyCode.JoystickButton15:
|
||||
break;
|
||||
case KeyCode.JoystickButton16:
|
||||
break;
|
||||
case KeyCode.JoystickButton17:
|
||||
break;
|
||||
case KeyCode.JoystickButton18:
|
||||
break;
|
||||
case KeyCode.JoystickButton19:
|
||||
break;
|
||||
case KeyCode.Joystick1Button0:
|
||||
break;
|
||||
case KeyCode.Joystick1Button1:
|
||||
break;
|
||||
case KeyCode.Joystick1Button2:
|
||||
break;
|
||||
case KeyCode.Joystick1Button3:
|
||||
break;
|
||||
case KeyCode.Joystick1Button4:
|
||||
break;
|
||||
case KeyCode.Joystick1Button5:
|
||||
break;
|
||||
case KeyCode.Joystick1Button6:
|
||||
break;
|
||||
case KeyCode.Joystick1Button7:
|
||||
break;
|
||||
case KeyCode.Joystick1Button8:
|
||||
break;
|
||||
case KeyCode.Joystick1Button9:
|
||||
break;
|
||||
case KeyCode.Joystick1Button10:
|
||||
break;
|
||||
case KeyCode.Joystick1Button11:
|
||||
break;
|
||||
case KeyCode.Joystick1Button12:
|
||||
break;
|
||||
case KeyCode.Joystick1Button13:
|
||||
break;
|
||||
case KeyCode.Joystick1Button14:
|
||||
break;
|
||||
case KeyCode.Joystick1Button15:
|
||||
break;
|
||||
case KeyCode.Joystick1Button16:
|
||||
break;
|
||||
case KeyCode.Joystick1Button17:
|
||||
break;
|
||||
case KeyCode.Joystick1Button18:
|
||||
break;
|
||||
case KeyCode.Joystick1Button19:
|
||||
break;
|
||||
case KeyCode.Joystick2Button0:
|
||||
break;
|
||||
case KeyCode.Joystick2Button1:
|
||||
break;
|
||||
case KeyCode.Joystick2Button2:
|
||||
break;
|
||||
case KeyCode.Joystick2Button3:
|
||||
break;
|
||||
case KeyCode.Joystick2Button4:
|
||||
break;
|
||||
case KeyCode.Joystick2Button5:
|
||||
break;
|
||||
case KeyCode.Joystick2Button6:
|
||||
break;
|
||||
case KeyCode.Joystick2Button7:
|
||||
break;
|
||||
case KeyCode.Joystick2Button8:
|
||||
break;
|
||||
case KeyCode.Joystick2Button9:
|
||||
break;
|
||||
case KeyCode.Joystick2Button10:
|
||||
break;
|
||||
case KeyCode.Joystick2Button11:
|
||||
break;
|
||||
case KeyCode.Joystick2Button12:
|
||||
break;
|
||||
case KeyCode.Joystick2Button13:
|
||||
break;
|
||||
case KeyCode.Joystick2Button14:
|
||||
break;
|
||||
case KeyCode.Joystick2Button15:
|
||||
break;
|
||||
case KeyCode.Joystick2Button16:
|
||||
break;
|
||||
case KeyCode.Joystick2Button17:
|
||||
break;
|
||||
case KeyCode.Joystick2Button18:
|
||||
break;
|
||||
case KeyCode.Joystick2Button19:
|
||||
break;
|
||||
case KeyCode.Joystick3Button0:
|
||||
break;
|
||||
case KeyCode.Joystick3Button1:
|
||||
break;
|
||||
case KeyCode.Joystick3Button2:
|
||||
break;
|
||||
case KeyCode.Joystick3Button3:
|
||||
break;
|
||||
case KeyCode.Joystick3Button4:
|
||||
break;
|
||||
case KeyCode.Joystick3Button5:
|
||||
break;
|
||||
case KeyCode.Joystick3Button6:
|
||||
break;
|
||||
case KeyCode.Joystick3Button7:
|
||||
break;
|
||||
case KeyCode.Joystick3Button8:
|
||||
break;
|
||||
case KeyCode.Joystick3Button9:
|
||||
break;
|
||||
case KeyCode.Joystick3Button10:
|
||||
break;
|
||||
case KeyCode.Joystick3Button11:
|
||||
break;
|
||||
case KeyCode.Joystick3Button12:
|
||||
break;
|
||||
case KeyCode.Joystick3Button13:
|
||||
break;
|
||||
case KeyCode.Joystick3Button14:
|
||||
break;
|
||||
case KeyCode.Joystick3Button15:
|
||||
break;
|
||||
case KeyCode.Joystick3Button16:
|
||||
break;
|
||||
case KeyCode.Joystick3Button17:
|
||||
break;
|
||||
case KeyCode.Joystick3Button18:
|
||||
break;
|
||||
case KeyCode.Joystick3Button19:
|
||||
break;
|
||||
case KeyCode.Joystick4Button0:
|
||||
break;
|
||||
case KeyCode.Joystick4Button1:
|
||||
break;
|
||||
case KeyCode.Joystick4Button2:
|
||||
break;
|
||||
case KeyCode.Joystick4Button3:
|
||||
break;
|
||||
case KeyCode.Joystick4Button4:
|
||||
break;
|
||||
case KeyCode.Joystick4Button5:
|
||||
break;
|
||||
case KeyCode.Joystick4Button6:
|
||||
break;
|
||||
case KeyCode.Joystick4Button7:
|
||||
break;
|
||||
case KeyCode.Joystick4Button8:
|
||||
break;
|
||||
case KeyCode.Joystick4Button9:
|
||||
break;
|
||||
case KeyCode.Joystick4Button10:
|
||||
break;
|
||||
case KeyCode.Joystick4Button11:
|
||||
break;
|
||||
case KeyCode.Joystick4Button12:
|
||||
break;
|
||||
case KeyCode.Joystick4Button13:
|
||||
break;
|
||||
case KeyCode.Joystick4Button14:
|
||||
break;
|
||||
case KeyCode.Joystick4Button15:
|
||||
break;
|
||||
case KeyCode.Joystick4Button16:
|
||||
break;
|
||||
case KeyCode.Joystick4Button17:
|
||||
break;
|
||||
case KeyCode.Joystick4Button18:
|
||||
break;
|
||||
case KeyCode.Joystick4Button19:
|
||||
break;
|
||||
case KeyCode.Joystick5Button0:
|
||||
break;
|
||||
case KeyCode.Joystick5Button1:
|
||||
break;
|
||||
case KeyCode.Joystick5Button2:
|
||||
break;
|
||||
case KeyCode.Joystick5Button3:
|
||||
break;
|
||||
case KeyCode.Joystick5Button4:
|
||||
break;
|
||||
case KeyCode.Joystick5Button5:
|
||||
break;
|
||||
case KeyCode.Joystick5Button6:
|
||||
break;
|
||||
case KeyCode.Joystick5Button7:
|
||||
break;
|
||||
case KeyCode.Joystick5Button8:
|
||||
break;
|
||||
case KeyCode.Joystick5Button9:
|
||||
break;
|
||||
case KeyCode.Joystick5Button10:
|
||||
break;
|
||||
case KeyCode.Joystick5Button11:
|
||||
break;
|
||||
case KeyCode.Joystick5Button12:
|
||||
break;
|
||||
case KeyCode.Joystick5Button13:
|
||||
break;
|
||||
case KeyCode.Joystick5Button14:
|
||||
break;
|
||||
case KeyCode.Joystick5Button15:
|
||||
break;
|
||||
case KeyCode.Joystick5Button16:
|
||||
break;
|
||||
case KeyCode.Joystick5Button17:
|
||||
break;
|
||||
case KeyCode.Joystick5Button18:
|
||||
break;
|
||||
case KeyCode.Joystick5Button19:
|
||||
break;
|
||||
case KeyCode.Joystick6Button0:
|
||||
break;
|
||||
case KeyCode.Joystick6Button1:
|
||||
break;
|
||||
case KeyCode.Joystick6Button2:
|
||||
break;
|
||||
case KeyCode.Joystick6Button3:
|
||||
break;
|
||||
case KeyCode.Joystick6Button4:
|
||||
break;
|
||||
case KeyCode.Joystick6Button5:
|
||||
break;
|
||||
case KeyCode.Joystick6Button6:
|
||||
break;
|
||||
case KeyCode.Joystick6Button7:
|
||||
break;
|
||||
case KeyCode.Joystick6Button8:
|
||||
break;
|
||||
case KeyCode.Joystick6Button9:
|
||||
break;
|
||||
case KeyCode.Joystick6Button10:
|
||||
break;
|
||||
case KeyCode.Joystick6Button11:
|
||||
break;
|
||||
case KeyCode.Joystick6Button12:
|
||||
break;
|
||||
case KeyCode.Joystick6Button13:
|
||||
break;
|
||||
case KeyCode.Joystick6Button14:
|
||||
break;
|
||||
case KeyCode.Joystick6Button15:
|
||||
break;
|
||||
case KeyCode.Joystick6Button16:
|
||||
break;
|
||||
case KeyCode.Joystick6Button17:
|
||||
break;
|
||||
case KeyCode.Joystick6Button18:
|
||||
break;
|
||||
case KeyCode.Joystick6Button19:
|
||||
break;
|
||||
case KeyCode.Joystick7Button0:
|
||||
break;
|
||||
case KeyCode.Joystick7Button1:
|
||||
break;
|
||||
case KeyCode.Joystick7Button2:
|
||||
break;
|
||||
case KeyCode.Joystick7Button3:
|
||||
break;
|
||||
case KeyCode.Joystick7Button4:
|
||||
break;
|
||||
case KeyCode.Joystick7Button5:
|
||||
break;
|
||||
case KeyCode.Joystick7Button6:
|
||||
break;
|
||||
case KeyCode.Joystick7Button7:
|
||||
break;
|
||||
case KeyCode.Joystick7Button8:
|
||||
break;
|
||||
case KeyCode.Joystick7Button9:
|
||||
break;
|
||||
case KeyCode.Joystick7Button10:
|
||||
break;
|
||||
case KeyCode.Joystick7Button11:
|
||||
break;
|
||||
case KeyCode.Joystick7Button12:
|
||||
break;
|
||||
case KeyCode.Joystick7Button13:
|
||||
break;
|
||||
case KeyCode.Joystick7Button14:
|
||||
break;
|
||||
case KeyCode.Joystick7Button15:
|
||||
break;
|
||||
case KeyCode.Joystick7Button16:
|
||||
break;
|
||||
case KeyCode.Joystick7Button17:
|
||||
break;
|
||||
case KeyCode.Joystick7Button18:
|
||||
break;
|
||||
case KeyCode.Joystick7Button19:
|
||||
break;
|
||||
case KeyCode.Joystick8Button0:
|
||||
break;
|
||||
case KeyCode.Joystick8Button1:
|
||||
break;
|
||||
case KeyCode.Joystick8Button2:
|
||||
break;
|
||||
case KeyCode.Joystick8Button3:
|
||||
break;
|
||||
case KeyCode.Joystick8Button4:
|
||||
break;
|
||||
case KeyCode.Joystick8Button5:
|
||||
break;
|
||||
case KeyCode.Joystick8Button6:
|
||||
break;
|
||||
case KeyCode.Joystick8Button7:
|
||||
break;
|
||||
case KeyCode.Joystick8Button8:
|
||||
break;
|
||||
case KeyCode.Joystick8Button9:
|
||||
break;
|
||||
case KeyCode.Joystick8Button10:
|
||||
break;
|
||||
case KeyCode.Joystick8Button11:
|
||||
break;
|
||||
case KeyCode.Joystick8Button12:
|
||||
break;
|
||||
case KeyCode.Joystick8Button13:
|
||||
break;
|
||||
case KeyCode.Joystick8Button14:
|
||||
break;
|
||||
case KeyCode.Joystick8Button15:
|
||||
break;
|
||||
case KeyCode.Joystick8Button16:
|
||||
break;
|
||||
case KeyCode.Joystick8Button17:
|
||||
break;
|
||||
case KeyCode.Joystick8Button18:
|
||||
break;
|
||||
case KeyCode.Joystick8Button19:
|
||||
break;
|
||||
//default:
|
||||
// throw new ArgumentOutOfRangeException(nameof(key), key, null);
|
||||
}
|
||||
|
||||
if (!silent)
|
||||
Logging.Logger.Log(LogLevel.Debug, $"[{nameof(NewInputSystem)}] Unsupported key: {key}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegacyInputSystem : IInputSystem
|
||||
{
|
||||
public bool GetKey(string name) => Input.GetKey(name);
|
||||
|
||||
public bool GetKey(KeyCode key) => Input.GetKey(key);
|
||||
|
||||
public bool GetKeyDown(string name) => Input.GetKeyDown(name);
|
||||
|
||||
public bool GetKeyDown(KeyCode key) => Input.GetKeyDown(key);
|
||||
|
||||
public bool GetKeyUp(string name) => Input.GetKeyUp(name);
|
||||
|
||||
public bool GetKeyUp(KeyCode key) => Input.GetKeyUp(key);
|
||||
|
||||
public bool GetMouseButton(int button) => Input.GetMouseButton(button);
|
||||
|
||||
public bool GetMouseButtonDown(int button) => Input.GetMouseButtonDown(button);
|
||||
|
||||
public bool GetMouseButtonUp(int button) => Input.GetMouseButtonUp(button);
|
||||
|
||||
public void ResetInputAxes() => Input.ResetInputAxes();
|
||||
|
||||
public Vector3 mousePosition => Input.mousePosition;
|
||||
public Vector2 mouseScrollDelta => Input.mouseScrollDelta;
|
||||
public bool mousePresent => Input.mousePresent;
|
||||
public bool anyKey => Input.anyKey;
|
||||
public bool anyKeyDown => Input.anyKeyDown;
|
||||
|
||||
public IEnumerable<KeyCode> SupportedKeyCodes { get; } = (KeyCode[])Enum.GetValues(typeof(KeyCode));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BepInEx.Configuration;
|
||||
using UnityEngine;
|
||||
@@ -36,5 +37,47 @@ internal static class UnityTomlTypeConverters
|
||||
TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
|
||||
TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
|
||||
TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
|
||||
TomlTypeConverter.AddConverter(typeof(Rect), new TypeConverter
|
||||
{
|
||||
ConvertToObject = StringToRect,
|
||||
ConvertToString = RectToString
|
||||
});
|
||||
}
|
||||
|
||||
private static object StringToRect(string s, Type type)
|
||||
{
|
||||
// JsonUtility doesn't work with Rect on all Unity versions, so parse it manually
|
||||
Rect result = default(Rect);
|
||||
|
||||
if (s == null)
|
||||
return result;
|
||||
|
||||
string cleaned = s.Trim('{', '}').Replace(" ", "");
|
||||
foreach (string part in cleaned.Split(','))
|
||||
{
|
||||
string[] parts = part.Split(':');
|
||||
if (parts.Length != 2 || !float.TryParse(parts[1], out float value))
|
||||
continue;
|
||||
|
||||
string id = parts[0].Trim('"');
|
||||
if (id == "x")
|
||||
result.x = value;
|
||||
else if (id == "y")
|
||||
result.y = value;
|
||||
else if (id == "width" || id == "z")
|
||||
result.width = value;
|
||||
else if (id == "height" || id == "w")
|
||||
result.height = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string RectToString(object o, Type type)
|
||||
{
|
||||
Rect rect = (Rect)o;
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
"{{ \"x\":{0}, \"y\":{1}, \"width\":{2}, \"height\":{3} }}",
|
||||
rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ target_assembly = BepInEx\core\BepInEx.Unity.IL2CPP.dll
|
||||
# If true, Unity's output log is redirected to <current folder>\output_log.txt
|
||||
redirect_output_log = false
|
||||
|
||||
# Overrides the default boot.config file path
|
||||
boot_config_override =
|
||||
|
||||
# If enabled, DOORSTOP_DISABLE env var value is ignored
|
||||
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
|
||||
ignore_disable_switch = false
|
||||
@@ -28,10 +31,6 @@ dll_search_path_override =
|
||||
# If true, Mono debugger server will be enabled
|
||||
debug_enabled = false
|
||||
|
||||
# When debug_enabled is true, this option specifies whether Doorstop should initialize the debugger server
|
||||
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to false
|
||||
debug_start_server = true
|
||||
|
||||
# When debug_enabled is true, specifies the address to use for the debugger server
|
||||
debug_address = 127.0.0.1:10000
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ target_assembly = BepInEx\core\BepInEx.Unity.Mono.Preloader.dll
|
||||
# If true, Unity's output log is redirected to <current folder>\output_log.txt
|
||||
redirect_output_log = false
|
||||
|
||||
# Overrides the default boot.config file path
|
||||
boot_config_override =
|
||||
|
||||
# If enabled, DOORSTOP_DISABLE env var value is ignored
|
||||
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
|
||||
ignore_disable_switch = false
|
||||
@@ -23,15 +26,12 @@ ignore_disable_switch = false
|
||||
# (e.g. mscorlib is stripped in original game)
|
||||
# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed
|
||||
# Original Managed folder is added as a secondary folder in the search path
|
||||
dll_search_path_override =
|
||||
# To specify multiple paths, separate them with semicolons (;)
|
||||
dll_search_path_override = "BepInEx\core"
|
||||
|
||||
# If true, Mono debugger server will be enabled
|
||||
debug_enabled = false
|
||||
|
||||
# When debug_enabled is true, this option specifies whether Doorstop should initialize the debugger server
|
||||
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to false
|
||||
debug_start_server = true
|
||||
|
||||
# When debug_enabled is true, specifies the address to use for the debugger server
|
||||
debug_address = 127.0.0.1:10000
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
# 1. Via CLI: Run ./run_bepinex.sh <path to game> [doorstop arguments] [game arguments]
|
||||
# 2. Via config: edit the options below and run ./run.sh without any arguments
|
||||
|
||||
# 0 is false, 1 is true
|
||||
|
||||
# LINUX: name of Unity executable
|
||||
# MACOS: name of the .app directory
|
||||
executable_name=""
|
||||
@@ -19,12 +17,16 @@ executable_name=""
|
||||
# General Config Options
|
||||
|
||||
# Enable Doorstop?
|
||||
# 0 is false, 1 is true
|
||||
enabled="1"
|
||||
|
||||
# Path to the assembly to load and execute
|
||||
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
|
||||
target_assembly="BepInEx/core/BepInEx.Unity.IL2CPP.dll"
|
||||
|
||||
# Overrides the default boot.config file path
|
||||
boot_config_override=
|
||||
|
||||
# If enabled, DOORSTOP_DISABLE env var value is ignored
|
||||
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
|
||||
ignore_disable_switch="0"
|
||||
@@ -36,15 +38,12 @@ ignore_disable_switch="0"
|
||||
# (e.g. mscorlib is stripped in original game)
|
||||
# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed
|
||||
# Original Managed folder is added as a secondary folder in the search path
|
||||
# To specify multiple paths, separate them with colons (:)
|
||||
dll_search_path_override=""
|
||||
|
||||
# If 1, Mono debugger server will be enabled
|
||||
debug_enable="0"
|
||||
|
||||
# When debug_enabled is 1, this option specifies whether Doorstop should initialize the debugger server
|
||||
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to 0
|
||||
debug_start_server="0"
|
||||
|
||||
# When debug_enabled is 1, specifies the address to use for the debugger server
|
||||
debug_address="127.0.0.1:10000"
|
||||
|
||||
@@ -61,43 +60,43 @@ corlib_dir="dotnet"
|
||||
|
||||
################################################################################
|
||||
# Everything past this point is the actual script
|
||||
set -e
|
||||
|
||||
# Special case: program is launched via Steam
|
||||
# In that case rerun the script via their bootstrapper to ensure Steam overlay works
|
||||
if [ "$2" = "SteamLaunch" ]; then
|
||||
# Conceptually: exec "$1" "$2" "$3" "$4" "$0" "rest of $@"
|
||||
# But newer versions of Steam interleave the $1..$4 with some "--" arguments, so preserve them as well
|
||||
# Bash has array subscripting, but POSIX sh doesn't, so avoid it
|
||||
to_rotate=4
|
||||
rotated=0
|
||||
while [ $((to_rotate-=1)) -ge 0 ]; do
|
||||
while [ "z$1" = "z--" ]; do
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
rotated=$((rotated+1))
|
||||
# Special case: program is launched via Steam on Linux
|
||||
# In that case rerun the script via their bootstrapper to delay adding Doorstop to LD_PRELOAD
|
||||
# This is required until https://github.com/NeighTools/UnityDoorstop/issues/88 is resolved
|
||||
for a in "$@"; do
|
||||
if [ "$a" = "SteamLaunch" ]; then
|
||||
rotated=0; max=$#
|
||||
while [ $rotated -lt $max ]; do
|
||||
# Test if argument is prefixed with the value of $PWD
|
||||
if [ "$1" != "${1#"${PWD%/}/"}" ]; then
|
||||
to_rotate=$(($# - rotated))
|
||||
set -- "$@" "$0"
|
||||
while [ $((to_rotate-=1)) -ge 0 ]; do
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
done
|
||||
exec "$@"
|
||||
else
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
rotated=$((rotated+1))
|
||||
fi
|
||||
done
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
rotated=$((rotated+1))
|
||||
done
|
||||
to_rotate=$(($# - rotated))
|
||||
set -- "$@" "$0"
|
||||
while [ $((to_rotate-=1)) -ge 0 ]; do
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
done
|
||||
exec "$@"
|
||||
fi
|
||||
echo "Could not determine game executable launched by Steam" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle first param being executable name
|
||||
if [ -x "$1" ] ; then
|
||||
executable_name="$1"
|
||||
echo "Target executable: $1"
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ -z "${executable_name}" ] || [ ! -x "${executable_name}" ]; then
|
||||
echo "Please set executable_name to a valid name in a text editor or as the first command line parameter"
|
||||
echo "Please set executable_name to a valid name in a text editor or as the first command line parameter" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -108,50 +107,56 @@ arch=""
|
||||
executable_path=""
|
||||
lib_extension=""
|
||||
|
||||
# Set executable path and the extension to use for the libdoorstop shared object
|
||||
abs_path() {
|
||||
# Resolve relative path to absolute from BASEDIR
|
||||
if [ "$1" = "${1#/}" ]; then
|
||||
set -- "${BASEDIR}/${1}"
|
||||
fi
|
||||
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
||||
}
|
||||
|
||||
# Set executable path and the extension to use for the libdoorstop shared object as well as check whether we're running on Apple Silicon
|
||||
os_type="$(uname -s)"
|
||||
case ${os_type} in
|
||||
Linux*)
|
||||
executable_path="${executable_name}"
|
||||
# Handle relative paths
|
||||
if ! echo "$executable_path" | grep "^/.*$"; then
|
||||
executable_path="${BASEDIR}/${executable_path}"
|
||||
fi
|
||||
executable_path="$(abs_path "$executable_name")"
|
||||
lib_extension="so"
|
||||
;;
|
||||
Darwin*)
|
||||
real_executable_name="${executable_name}"
|
||||
|
||||
# Handle relative directories
|
||||
if ! echo "$real_executable_name" | grep "^/.*$"; then
|
||||
real_executable_name="${BASEDIR}/${real_executable_name}"
|
||||
fi
|
||||
real_executable_name="$(abs_path "$executable_name")"
|
||||
|
||||
# If we're not even an actual executable, check .app Info for actual executable
|
||||
if ! echo "$real_executable_name" | grep "^.*\.app/Contents/MacOS/.*"; then
|
||||
# Add .app to the end if not given
|
||||
if ! echo "$real_executable_name" | grep "^.*\.app$"; then
|
||||
real_executable_name="${real_executable_name}.app"
|
||||
fi
|
||||
inner_executable_name=$(defaults read "${real_executable_name}/Contents/Info" CFBundleExecutable)
|
||||
executable_path="${real_executable_name}/Contents/MacOS/${inner_executable_name}"
|
||||
else
|
||||
executable_path="${executable_name}"
|
||||
fi
|
||||
case $real_executable_name in
|
||||
*.app/Contents/MacOS/*)
|
||||
executable_path="${executable_name}"
|
||||
;;
|
||||
*)
|
||||
# Add .app to the end if not given
|
||||
if [ "$real_executable_name" = "${real_executable_name%.app}" ]; then
|
||||
real_executable_name="${real_executable_name}.app"
|
||||
fi
|
||||
inner_executable_name=$(defaults read "${real_executable_name}/Contents/Info" CFBundleExecutable)
|
||||
executable_path="${real_executable_name}/Contents/MacOS/${inner_executable_name}"
|
||||
;;
|
||||
esac
|
||||
lib_extension="dylib"
|
||||
|
||||
# CPUs for Apple Silicon are in the format "Apple M.."
|
||||
cpu_type="$(sysctl -n machdep.cpu.brand_string)"
|
||||
case "${cpu_type}" in
|
||||
Apple*)
|
||||
is_apple_silicon=1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# alright whos running games on freebsd
|
||||
echo "Unknown operating system ($(uname -s))"
|
||||
echo "Make an issue at https://github.com/NeighTools/UnityDoorstop"
|
||||
echo "Unknown operating system ($(uname -s))" 1>&2
|
||||
echo "Make an issue at https://github.com/NeighTools/UnityDoorstop" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
abs_path() {
|
||||
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
||||
}
|
||||
|
||||
_readlink() {
|
||||
# relative links with readlink (without -f) do not preserve the path info
|
||||
ab_path="$(abs_path "$1")"
|
||||
@@ -163,7 +168,6 @@ _readlink() {
|
||||
echo "$link"
|
||||
}
|
||||
|
||||
|
||||
resolve_executable_path () {
|
||||
e_path="$(abs_path "$1")"
|
||||
|
||||
@@ -173,13 +177,23 @@ resolve_executable_path () {
|
||||
echo "${e_path}"
|
||||
}
|
||||
|
||||
# Get absolute path of executable and show to user
|
||||
# Get absolute path of executable
|
||||
executable_path=$(resolve_executable_path "${executable_path}")
|
||||
echo "${executable_path}"
|
||||
|
||||
# Figure out the arch of the executable with file
|
||||
file_out="$(LD_PRELOAD="" file -b "${executable_path}")"
|
||||
case "${file_out}" in
|
||||
*PE32*)
|
||||
echo "The executable is a Windows executable file. You must use Wine/Proton and BepInEx for Windows with this executable." 1>&2
|
||||
echo "Uninstall BepInEx for *nix and install BepInEx for Windows instead." 1>&2
|
||||
echo "More info: https://docs.bepinex.dev/articles/advanced/steam_interop.html#protonwine" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
*shell\ script*)
|
||||
# Fallback for games that launch a shell script from Steam
|
||||
# default to x64, change as needed
|
||||
arch="x64"
|
||||
;;
|
||||
*64-bit*)
|
||||
arch="x64"
|
||||
;;
|
||||
@@ -187,10 +201,10 @@ case "${file_out}" in
|
||||
arch="x86"
|
||||
;;
|
||||
*)
|
||||
echo "The executable \"${executable_path}\" is not compiled for x86 or x64 (might be ARM?)"
|
||||
echo "If you think this is a mistake (or would like to encourage support for other architectures)"
|
||||
echo "Please make an issue at https://github.com/NeighTools/UnityDoorstop"
|
||||
echo "Got: ${file_out}"
|
||||
echo "The executable \"${executable_path}\" is not compiled for x86 or x64 (might be ARM?)" 1>&2
|
||||
echo "If you think this is a mistake (or would like to encourage support for other architectures)" 1>&2
|
||||
echo "Please make an issue at https://github.com/NeighTools/UnityDoorstop" 1>&2
|
||||
echo "Got: ${file_out}" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -208,61 +222,81 @@ doorstop_bool() {
|
||||
}
|
||||
|
||||
# Read from command line
|
||||
while :; do
|
||||
i=0; max=$#
|
||||
while [ $i -lt $max ]; do
|
||||
case "$1" in
|
||||
--doorstop_enabled)
|
||||
--doorstop_enabled) # For backwards compatibility. Renamed to --doorstop-enabled
|
||||
enabled="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop_target_assembly)
|
||||
--doorstop_target_assembly) # For backwards compatibility. Renamed to --doorstop-target-assembly
|
||||
target_assembly="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-enabled)
|
||||
enabled="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-target-assembly)
|
||||
target_assembly="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-boot-config-override)
|
||||
boot_config_override="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-dll-search-path-override)
|
||||
dll_search_path_override="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-debug-enabled)
|
||||
debug_enable="$(doorstop_bool "$2")"
|
||||
shift
|
||||
;;
|
||||
--doorstop-mono-debug-start-server)
|
||||
debug_start_server="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-debug-suspend)
|
||||
debug_suspend="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-debug-address)
|
||||
debug_address="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-clr-runtime-coreclr-path)
|
||||
coreclr_path="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-clr-corlib-dir)
|
||||
corlib_dir="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
*)
|
||||
if [ -z "$1" ]; then
|
||||
break
|
||||
fi
|
||||
rest_args="$rest_args $1"
|
||||
set -- "$@" "$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
i=$((i+1))
|
||||
done
|
||||
|
||||
target_assembly="$(abs_path "$target_assembly")"
|
||||
|
||||
# Move variables to environment
|
||||
export DOORSTOP_ENABLED="$enabled"
|
||||
export DOORSTOP_TARGET_ASSEMBLY="$target_assembly"
|
||||
export DOORSTOP_BOOT_CONFIG_OVERRIDE="$boot_config_override"
|
||||
export DOORSTOP_IGNORE_DISABLED_ENV="$ignore_disable_switch"
|
||||
export DOORSTOP_MONO_DLL_SEARCH_PATH_OVERRIDE="$dll_search_path_override"
|
||||
export DOORSTOP_MONO_DEBUG_ENABLED="$debug_enable"
|
||||
export DOORSTOP_MONO_DEBUG_START_SERVER="$debug_start_server"
|
||||
export DOORSTOP_MONO_DEBUG_ADDRESS="$debug_address"
|
||||
export DOORSTOP_MONO_DEBUG_SUSPEND="$debug_suspend"
|
||||
export DOORSTOP_CLR_RUNTIME_CORECLR_PATH="$coreclr_path.$lib_extension"
|
||||
@@ -286,5 +320,14 @@ else
|
||||
export DYLD_INSERT_LIBRARIES="${doorstop_name}:${DYLD_INSERT_LIBRARIES}"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
exec "$executable_path" $rest_args
|
||||
if [ -n "${is_apple_silicon}" ]; then
|
||||
export ARCHPREFERENCE="arm64,x86_64"
|
||||
|
||||
# We need to use arch for Apple Silicon to allow the executable to be run natively as otherwise if
|
||||
# the executable is universal, supporting both x86_64 and arm64, MacOs will still run it as x86_64
|
||||
# if the parent process is running as x86.
|
||||
# arch also strips the DYLD_INSERT_LIBRARIES env var so we have to pass that in manually
|
||||
exec arch -e DYLD_INSERT_LIBRARIES="${DYLD_INSERT_LIBRARIES}" "$executable_path" "$@"
|
||||
else
|
||||
exec "$executable_path" "$@"
|
||||
fi
|
||||
@@ -8,8 +8,6 @@
|
||||
# 1. Via CLI: Run ./run_bepinex.sh <path to game> [doorstop arguments] [game arguments]
|
||||
# 2. Via config: edit the options below and run ./run.sh without any arguments
|
||||
|
||||
# 0 is false, 1 is true
|
||||
|
||||
# LINUX: name of Unity executable
|
||||
# MACOS: name of the .app directory
|
||||
executable_name=""
|
||||
@@ -19,12 +17,16 @@ executable_name=""
|
||||
# General Config Options
|
||||
|
||||
# Enable Doorstop?
|
||||
# 0 is false, 1 is true
|
||||
enabled="1"
|
||||
|
||||
# Path to the assembly to load and execute
|
||||
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
|
||||
target_assembly="BepInEx/core/BepInEx.Unity.Mono.Preloader.dll"
|
||||
|
||||
# Overrides the default boot.config file path
|
||||
boot_config_override=
|
||||
|
||||
# If enabled, DOORSTOP_DISABLE env var value is ignored
|
||||
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
|
||||
ignore_disable_switch="0"
|
||||
@@ -36,15 +38,12 @@ ignore_disable_switch="0"
|
||||
# (e.g. mscorlib is stripped in original game)
|
||||
# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed
|
||||
# Original Managed folder is added as a secondary folder in the search path
|
||||
dll_search_path_override=""
|
||||
# To specify multiple paths, separate them with colons (:)
|
||||
dll_search_path_override="BepInEx/core"
|
||||
|
||||
# If 1, Mono debugger server will be enabled
|
||||
debug_enable="0"
|
||||
|
||||
# When debug_enabled is 1, this option specifies whether Doorstop should initialize the debugger server
|
||||
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to 0
|
||||
debug_start_server="0"
|
||||
|
||||
# When debug_enabled is 1, specifies the address to use for the debugger server
|
||||
debug_address="127.0.0.1:10000"
|
||||
|
||||
@@ -61,43 +60,43 @@ corlib_dir=""
|
||||
|
||||
################################################################################
|
||||
# Everything past this point is the actual script
|
||||
set -e
|
||||
|
||||
# Special case: program is launched via Steam
|
||||
# In that case rerun the script via their bootstrapper to ensure Steam overlay works
|
||||
if [ "$2" = "SteamLaunch" ]; then
|
||||
# Conceptually: exec "$1" "$2" "$3" "$4" "$0" "rest of $@"
|
||||
# But newer versions of Steam interleave the $1..$4 with some "--" arguments, so preserve them as well
|
||||
# Bash has array subscripting, but POSIX sh doesn't, so avoid it
|
||||
to_rotate=4
|
||||
rotated=0
|
||||
while [ $((to_rotate-=1)) -ge 0 ]; do
|
||||
while [ "z$1" = "z--" ]; do
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
rotated=$((rotated+1))
|
||||
# Special case: program is launched via Steam on Linux
|
||||
# In that case rerun the script via their bootstrapper to delay adding Doorstop to LD_PRELOAD
|
||||
# This is required until https://github.com/NeighTools/UnityDoorstop/issues/88 is resolved
|
||||
for a in "$@"; do
|
||||
if [ "$a" = "SteamLaunch" ]; then
|
||||
rotated=0; max=$#
|
||||
while [ $rotated -lt $max ]; do
|
||||
# Test if argument is prefixed with the value of $PWD
|
||||
if [ "$1" != "${1#"${PWD%/}/"}" ]; then
|
||||
to_rotate=$(($# - rotated))
|
||||
set -- "$@" "$0"
|
||||
while [ $((to_rotate-=1)) -ge 0 ]; do
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
done
|
||||
exec "$@"
|
||||
else
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
rotated=$((rotated+1))
|
||||
fi
|
||||
done
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
rotated=$((rotated+1))
|
||||
done
|
||||
to_rotate=$(($# - rotated))
|
||||
set -- "$@" "$0"
|
||||
while [ $((to_rotate-=1)) -ge 0 ]; do
|
||||
set -- "$@" "$1"
|
||||
shift
|
||||
done
|
||||
exec "$@"
|
||||
fi
|
||||
echo "Could not determine game executable launched by Steam" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle first param being executable name
|
||||
if [ -x "$1" ] ; then
|
||||
executable_name="$1"
|
||||
echo "Target executable: $1"
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ -z "${executable_name}" ] || [ ! -x "${executable_name}" ]; then
|
||||
echo "Please set executable_name to a valid name in a text editor or as the first command line parameter"
|
||||
echo "Please set executable_name to a valid name in a text editor or as the first command line parameter" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -108,50 +107,56 @@ arch=""
|
||||
executable_path=""
|
||||
lib_extension=""
|
||||
|
||||
# Set executable path and the extension to use for the libdoorstop shared object
|
||||
abs_path() {
|
||||
# Resolve relative path to absolute from BASEDIR
|
||||
if [ "$1" = "${1#/}" ]; then
|
||||
set -- "${BASEDIR}/${1}"
|
||||
fi
|
||||
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
||||
}
|
||||
|
||||
# Set executable path and the extension to use for the libdoorstop shared object as well as check whether we're running on Apple Silicon
|
||||
os_type="$(uname -s)"
|
||||
case ${os_type} in
|
||||
Linux*)
|
||||
executable_path="${executable_name}"
|
||||
# Handle relative paths
|
||||
if ! echo "$executable_path" | grep "^/.*$"; then
|
||||
executable_path="${BASEDIR}/${executable_path}"
|
||||
fi
|
||||
executable_path="$(abs_path "$executable_name")"
|
||||
lib_extension="so"
|
||||
;;
|
||||
Darwin*)
|
||||
real_executable_name="${executable_name}"
|
||||
|
||||
# Handle relative directories
|
||||
if ! echo "$real_executable_name" | grep "^/.*$"; then
|
||||
real_executable_name="${BASEDIR}/${real_executable_name}"
|
||||
fi
|
||||
real_executable_name="$(abs_path "$executable_name")"
|
||||
|
||||
# If we're not even an actual executable, check .app Info for actual executable
|
||||
if ! echo "$real_executable_name" | grep "^.*\.app/Contents/MacOS/.*"; then
|
||||
# Add .app to the end if not given
|
||||
if ! echo "$real_executable_name" | grep "^.*\.app$"; then
|
||||
real_executable_name="${real_executable_name}.app"
|
||||
fi
|
||||
inner_executable_name=$(defaults read "${real_executable_name}/Contents/Info" CFBundleExecutable)
|
||||
executable_path="${real_executable_name}/Contents/MacOS/${inner_executable_name}"
|
||||
else
|
||||
executable_path="${executable_name}"
|
||||
fi
|
||||
case $real_executable_name in
|
||||
*.app/Contents/MacOS/*)
|
||||
executable_path="${executable_name}"
|
||||
;;
|
||||
*)
|
||||
# Add .app to the end if not given
|
||||
if [ "$real_executable_name" = "${real_executable_name%.app}" ]; then
|
||||
real_executable_name="${real_executable_name}.app"
|
||||
fi
|
||||
inner_executable_name=$(defaults read "${real_executable_name}/Contents/Info" CFBundleExecutable)
|
||||
executable_path="${real_executable_name}/Contents/MacOS/${inner_executable_name}"
|
||||
;;
|
||||
esac
|
||||
lib_extension="dylib"
|
||||
|
||||
# CPUs for Apple Silicon are in the format "Apple M.."
|
||||
cpu_type="$(sysctl -n machdep.cpu.brand_string)"
|
||||
case "${cpu_type}" in
|
||||
Apple*)
|
||||
is_apple_silicon=1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# alright whos running games on freebsd
|
||||
echo "Unknown operating system ($(uname -s))"
|
||||
echo "Make an issue at https://github.com/NeighTools/UnityDoorstop"
|
||||
echo "Unknown operating system ($(uname -s))" 1>&2
|
||||
echo "Make an issue at https://github.com/NeighTools/UnityDoorstop" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
abs_path() {
|
||||
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
||||
}
|
||||
|
||||
_readlink() {
|
||||
# relative links with readlink (without -f) do not preserve the path info
|
||||
ab_path="$(abs_path "$1")"
|
||||
@@ -163,7 +168,6 @@ _readlink() {
|
||||
echo "$link"
|
||||
}
|
||||
|
||||
|
||||
resolve_executable_path () {
|
||||
e_path="$(abs_path "$1")"
|
||||
|
||||
@@ -173,13 +177,23 @@ resolve_executable_path () {
|
||||
echo "${e_path}"
|
||||
}
|
||||
|
||||
# Get absolute path of executable and show to user
|
||||
# Get absolute path of executable
|
||||
executable_path=$(resolve_executable_path "${executable_path}")
|
||||
echo "${executable_path}"
|
||||
|
||||
# Figure out the arch of the executable with file
|
||||
file_out="$(LD_PRELOAD="" file -b "${executable_path}")"
|
||||
case "${file_out}" in
|
||||
*PE32*)
|
||||
echo "The executable is a Windows executable file. You must use Wine/Proton and BepInEx for Windows with this executable." 1>&2
|
||||
echo "Uninstall BepInEx for *nix and install BepInEx for Windows instead." 1>&2
|
||||
echo "More info: https://docs.bepinex.dev/articles/advanced/steam_interop.html#protonwine" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
*shell\ script*)
|
||||
# Fallback for games that launch a shell script from Steam
|
||||
# default to x64, change as needed
|
||||
arch="x64"
|
||||
;;
|
||||
*64-bit*)
|
||||
arch="x64"
|
||||
;;
|
||||
@@ -187,10 +201,10 @@ case "${file_out}" in
|
||||
arch="x86"
|
||||
;;
|
||||
*)
|
||||
echo "The executable \"${executable_path}\" is not compiled for x86 or x64 (might be ARM?)"
|
||||
echo "If you think this is a mistake (or would like to encourage support for other architectures)"
|
||||
echo "Please make an issue at https://github.com/NeighTools/UnityDoorstop"
|
||||
echo "Got: ${file_out}"
|
||||
echo "The executable \"${executable_path}\" is not compiled for x86 or x64 (might be ARM?)" 1>&2
|
||||
echo "If you think this is a mistake (or would like to encourage support for other architectures)" 1>&2
|
||||
echo "Please make an issue at https://github.com/NeighTools/UnityDoorstop" 1>&2
|
||||
echo "Got: ${file_out}" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -208,61 +222,81 @@ doorstop_bool() {
|
||||
}
|
||||
|
||||
# Read from command line
|
||||
while :; do
|
||||
i=0; max=$#
|
||||
while [ $i -lt $max ]; do
|
||||
case "$1" in
|
||||
--doorstop_enabled)
|
||||
--doorstop_enabled) # For backwards compatibility. Renamed to --doorstop-enabled
|
||||
enabled="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop_target_assembly)
|
||||
--doorstop_target_assembly) # For backwards compatibility. Renamed to --doorstop-target-assembly
|
||||
target_assembly="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-enabled)
|
||||
enabled="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-target-assembly)
|
||||
target_assembly="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-boot-config-override)
|
||||
boot_config_override="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-dll-search-path-override)
|
||||
dll_search_path_override="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-debug-enabled)
|
||||
debug_enable="$(doorstop_bool "$2")"
|
||||
shift
|
||||
;;
|
||||
--doorstop-mono-debug-start-server)
|
||||
debug_start_server="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-debug-suspend)
|
||||
debug_suspend="$(doorstop_bool "$2")"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-mono-debug-address)
|
||||
debug_address="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-clr-runtime-coreclr-path)
|
||||
coreclr_path="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
--doorstop-clr-corlib-dir)
|
||||
corlib_dir="$2"
|
||||
shift
|
||||
i=$((i+1))
|
||||
;;
|
||||
*)
|
||||
if [ -z "$1" ]; then
|
||||
break
|
||||
fi
|
||||
rest_args="$rest_args $1"
|
||||
set -- "$@" "$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
i=$((i+1))
|
||||
done
|
||||
|
||||
target_assembly="$(abs_path "$target_assembly")"
|
||||
|
||||
# Move variables to environment
|
||||
export DOORSTOP_ENABLED="$enabled"
|
||||
export DOORSTOP_TARGET_ASSEMBLY="$target_assembly"
|
||||
export DOORSTOP_BOOT_CONFIG_OVERRIDE="$boot_config_override"
|
||||
export DOORSTOP_IGNORE_DISABLED_ENV="$ignore_disable_switch"
|
||||
export DOORSTOP_MONO_DLL_SEARCH_PATH_OVERRIDE="$dll_search_path_override"
|
||||
export DOORSTOP_MONO_DEBUG_ENABLED="$debug_enable"
|
||||
export DOORSTOP_MONO_DEBUG_START_SERVER="$debug_start_server"
|
||||
export DOORSTOP_MONO_DEBUG_ADDRESS="$debug_address"
|
||||
export DOORSTOP_MONO_DEBUG_SUSPEND="$debug_suspend"
|
||||
export DOORSTOP_CLR_RUNTIME_CORECLR_PATH="$coreclr_path.$lib_extension"
|
||||
@@ -286,5 +320,14 @@ else
|
||||
export DYLD_INSERT_LIBRARIES="${doorstop_name}:${DYLD_INSERT_LIBRARIES}"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
exec "$executable_path" $rest_args
|
||||
if [ -n "${is_apple_silicon}" ]; then
|
||||
export ARCHPREFERENCE="arm64,x86_64"
|
||||
|
||||
# We need to use arch for Apple Silicon to allow the executable to be run natively as otherwise if
|
||||
# the executable is universal, supporting both x86_64 and arm64, MacOs will still run it as x86_64
|
||||
# if the parent process is running as x86.
|
||||
# arch also strips the DYLD_INSERT_LIBRARIES env var so we have to pass that in manually
|
||||
exec arch -e DYLD_INSERT_LIBRARIES="${DYLD_INSERT_LIBRARIES}" "$executable_path" "$@"
|
||||
else
|
||||
exec "$executable_path" "$@"
|
||||
fi
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
+4
-3
@@ -32,7 +32,7 @@ public class BuildContext : FrostingContext
|
||||
BleedingEdge
|
||||
}
|
||||
|
||||
public const string DoorstopVersion = "4.0.0";
|
||||
public const string DoorstopVersion = "4.5.0";
|
||||
public const string DotnetRuntimeVersion = "6.0.7";
|
||||
public const string DobbyVersion = "1.0.5";
|
||||
|
||||
@@ -253,8 +253,9 @@ public sealed class MakeDistTask : FrostingTask<BuildContext>
|
||||
|
||||
if (dist.Engine == "Unity")
|
||||
{
|
||||
var doorstopPath =
|
||||
ctx.CacheDirectory.Combine("doorstop").Combine($"doorstop_{dist.Os}").Combine(dist.Arch);
|
||||
var doorstopPath = dist.Os == "macos"
|
||||
? ctx.CacheDirectory.Combine("doorstop").Combine("doorstop_macos").Combine("universal")
|
||||
: ctx.CacheDirectory.Combine("doorstop").Combine($"doorstop_{dist.Os}").Combine(dist.Arch);
|
||||
foreach (var filePath in ctx.GetFiles(doorstopPath.Combine($"*.{dist.DllExtension}").FullPath))
|
||||
ctx.CopyFileToDirectory(filePath, targetDir);
|
||||
ctx.CopyFileToDirectory(doorstopPath.CombineWithFilePath(".doorstop_version"), targetDir);
|
||||
|
||||
Reference in new issue
Block a user