Skip to content

Commit

Permalink
Add IL2CPP version for BepInEx6 nightly builds (#78)
Browse files Browse the repository at this point in the history
Code was refactored to use a shared project. Based on source from HC.BepInEx.ConfigurationManager.Il2Cpp.CoreCLR-18.0_beta2_20230821
  • Loading branch information
ManlyMarco committed Nov 27, 2023
1 parent 77ac031 commit e3b754d
Show file tree
Hide file tree
Showing 21 changed files with 727 additions and 115 deletions.
36 changes: 36 additions & 0 deletions BepInEx.KeyboardShortcut/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("BepInEx.KeyboardShortcut")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BepInEx.KeyboardShortcut")]
[assembly: AssemblyCopyright("Copyright © Bepis 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("29e2f128-9187-41a9-b834-537d3721cd3a")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("6.0.0.0")]
[assembly: AssemblyFileVersion("6.0.0.0")]
[assembly: AssemblyInformationalVersion("6.0.0-be.672")]
20 changes: 20 additions & 0 deletions BepInEx.KeyboardShortcut/BepInEx.KeyboardShortcut.IL2CPP.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<RootNamespace>BepInEx.KeyboardShortcut</RootNamespace>
<AssemblyName>BepInEx.KeyboardShortcut</AssemblyName>
<DebugType>embedded</DebugType>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.664" />
<PackageReference Include="IllusionLibs.HoneyCome.Il2Cppmscorlib" Version="2023.16.10" />
<PackageReference Include="IllusionLibs.HoneyCome.UnityEngine" Version="2021.3.14.2" />
</ItemGroup>

</Project>
179 changes: 179 additions & 0 deletions BepInEx.KeyboardShortcut/KeyboardShortcut.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// This is copied from BepInEx 5 and modified to work with BepInEx 6 nightlies where this type is missing
// This is a temporary fix until this struct is added to BepInEx 6 proper
using BepInEx.Logging;
using System.Reflection;
using UnityEngine;
using Logger = BepInEx.Logging.Logger;

namespace BepInEx.Configuration
{
/// <summary>
/// A keyboard shortcut that can be used in Update method to check if user presses a key combo. The shortcut is only
/// triggered when the user presses the exact combination. For example, <c>F + LeftCtrl</c> will trigger only if user
/// presses and holds only LeftCtrl, and then presses F. If any other keys are pressed, the shortcut will not trigger.
///
/// Can be used as a value of a setting in <see cref="ConfigFile.Bind{T}(ConfigDefinition,T,ConfigDescription)"/>
/// to allow user to change this shortcut and have the changes saved.
///
/// How to use: Use <see cref="IsDown"/> in this class instead of <see cref="Input.GetKeyDown(KeyCode)"/> in the Update loop.
/// </summary>
public struct KeyboardShortcut
{
static KeyboardShortcut()
{
TomlTypeConverter.AddConverter(
typeof(KeyboardShortcut),
new TypeConverter
{
ConvertToString = (o, type) => ((KeyboardShortcut)o).Serialize(),
ConvertToObject = (s, type) => Deserialize(s)
});
}

/// <summary>
/// Shortcut that never triggers.
/// </summary>
public static readonly KeyboardShortcut Empty = new KeyboardShortcut();

/// <summary>
/// All KeyCode values that can be used in a keyboard shortcut.
/// </summary>
public static readonly IEnumerable<KeyCode> AllKeyCodes = Enum.GetValues(typeof(KeyCode)) as KeyCode[];

// Don't block hotkeys if mouse is being pressed, e.g. when shooting and trying to strafe
public static readonly KeyCode[] ModifierBlockKeyCodes = AllKeyCodes.Except(new KeyCode[] {
KeyCode.Mouse0, KeyCode.Mouse1, KeyCode.Mouse2, KeyCode.Mouse3,
KeyCode.Mouse4, KeyCode.Mouse5, KeyCode.Mouse6, KeyCode.None }).ToArray();

private readonly KeyCode[] _allKeys;

#if true
private static MethodInfo logMethod = typeof(Logger).GetMethod(
"Log", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null,
new Type[] { typeof(LogLevel), typeof(object) }, null);

#endif
/// <summary>
/// Create a new keyboard shortcut.
/// </summary>
/// <param name="mainKey">Main key to press</param>
/// <param name="modifiers">Keys that should be held down before main key is registered</param>
public KeyboardShortcut(KeyCode mainKey, params KeyCode[] modifiers) : this(new[] { mainKey }.Concat(modifiers).ToArray())
{
if (mainKey == KeyCode.None && modifiers.Any())
throw new ArgumentException($"Can't set {nameof(mainKey)} to KeyCode.None if there are any {nameof(modifiers)}");
}

private KeyboardShortcut(KeyCode[] keys)
{
_allKeys = SanitizeKeys(keys);
}

private static KeyCode[] SanitizeKeys(params KeyCode[] keys)
{
return keys != null && keys.Length != 0 && keys[0] != KeyCode.None
? new KeyCode[] { keys[0] }.Concat(keys.Skip(1).Distinct().Where(x => x != keys[0]).OrderBy(x => (int)x)).ToArray()
: new KeyCode[] { KeyCode.None };
}

/// <summary>
/// Main key of the key combination. It has to be pressed / let go last for the combination to be triggered.
/// If the combination is empty, <see cref="KeyCode.None"/> is returned.
/// </summary>
public KeyCode MainKey => _allKeys != null && _allKeys.Length > 0 ? _allKeys[0] : KeyCode.None;

/// <summary>
/// Modifiers of the key combination, if any.
/// </summary>
public IEnumerable<KeyCode> Modifiers => _allKeys?.Skip(1) ?? Enumerable.Empty<KeyCode>();

/// <summary>
/// Attempt to deserialize key combination from the string.
/// </summary>
public static KeyboardShortcut Deserialize(string str)
{
try
{
KeyCode[] parts = str.Split(new[] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => (KeyCode)Enum.Parse(typeof(KeyCode), x)).ToArray();
return new KeyboardShortcut(parts);
}
catch (SystemException ex)
{
#if false
Logger.Log(LogLevel.Error, "Failed to read keybind from settings: " + ex.Message);
#else
logMethod?.Invoke(null, new object[] { LogLevel.Error, "Failed to read keybind from settings: " + ex.Message });
#endif
}
return Empty;
}

/// <summary>
/// Serialize the key combination into a user readable string.
/// </summary>
public string Serialize()
{
return _allKeys != null
? string.Join(" + ", _allKeys.Select(x => x.ToString()).ToArray())
: string.Empty;
}

/// <summary>
/// Check if the main key was just pressed (Input.GetKeyDown), and specified modifier keys are all pressed
/// </summary>
public bool IsDown()
{
KeyCode mainKey = MainKey;
return mainKey != KeyCode.None && Input.GetKeyDown(mainKey) && ModifierKeyTest();
}

/// <summary>
/// Check if the main key is currently held down (Input.GetKey), and specified modifier keys are all pressed
/// </summary>
public bool IsPressed()
{
KeyCode mainKey = MainKey;
return mainKey != KeyCode.None && Input.GetKey(mainKey) && ModifierKeyTest();
}

/// <summary>
/// Check if the main key was just lifted (Input.GetKeyUp), and specified modifier keys are all pressed.
/// </summary>
public bool IsUp()
{
KeyCode mainKey = MainKey;
return mainKey != KeyCode.None && Input.GetKeyUp(mainKey) && ModifierKeyTest();
}

private bool ModifierKeyTest()
{
KeyCode mainKey = MainKey;
return
_allKeys.All(key => key == mainKey || Input.GetKey(key)) &&
ModifierBlockKeyCodes.Except(_allKeys).All(key => !Input.GetKey(key));
}

/// <inheritdoc />
public override string ToString()
{
return MainKey != KeyCode.None
? string.Join(" + ", _allKeys.Select(key => key.ToString()).ToArray())
: "Not set";
}

/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is KeyboardShortcut shortcut && MainKey == shortcut.MainKey && Modifiers.SequenceEqual(shortcut.Modifiers);
}

/// <inheritdoc />
public override int GetHashCode()
{
return MainKey != KeyCode.None
? _allKeys.Aggregate(_allKeys.Length, (current, item) => unchecked(current * 31 + (int)item))
: 0;
}
}
}
32 changes: 32 additions & 0 deletions ConfigurationManager.IL2CPP/ConfigurationManager.IL2CPP.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<RootNamespace>ConfigurationManager</RootNamespace>
<AssemblyName>ConfigurationManager</AssemblyName>

<OutputPath>D:\SVN\BepInEx.ConfigurationManager\bin\IL2CPP</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateDocumentationFile>True</GenerateDocumentationFile>

<DebugType>embedded</DebugType>
<DefineConstants>$(DefineConstants);IL2CPP</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.664" />
<PackageReference Include="IllusionLibs.HoneyCome.Il2Cppmscorlib" Version="2023.16.10" />
<PackageReference Include="IllusionLibs.HoneyCome.UnityEngine" Version="2021.3.14.2" />
<PackageReference Include="IllusionLibs.HoneyCome.UnityEngine.UI" Version="2021.3.14.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BepInEx.KeyboardShortcut\BepInEx.KeyboardShortcut.IL2CPP.csproj" />
</ItemGroup>

<Import Project="..\ConfigurationManager.Shared\ConfigurationManager.Shared.projitems" Label="Shared" />

</Project>
112 changes: 112 additions & 0 deletions ConfigurationManager.IL2CPP/SettingSearcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEngine;

namespace ConfigurationManager
{
internal static class SettingSearcher
{
private static readonly ICollection<string> _updateMethodNames = new[]
{
"Update",
"FixedUpdate",
"LateUpdate",
"OnGUI"
};

// Search for instances of BaseUnityPlugin to also find dynamically loaded plugins. Doing this makes checking Chainloader.PluginInfos redundant.
// Have to use FindObjectsOfType(Type) instead of FindObjectsOfType<T> because the latter is not available in some older unity versions.
public static IReadOnlyList<PluginInfo> FindPlugins() => IL2CPPChainloader.Instance.Plugins.Values.Where(x => x.Instance is BasePlugin).ToList();

public static void CollectSettings(out IEnumerable<SettingEntryBase> results, out List<string> modsWithoutSettings, bool showDebug)
{
modsWithoutSettings = new List<string>();
try
{
results = GetBepInExCoreConfig();
}
catch (Exception ex)
{
results = Enumerable.Empty<SettingEntryBase>();
ConfigurationManager.Logger.LogError(ex);
}

foreach (var plugin in FindPlugins())
{
var type = plugin.Instance.GetType();

bool advanced = false;
if (type.GetCustomAttributes(typeof(BrowsableAttribute), false).Cast<BrowsableAttribute>()
.Any(x => !x.Browsable))
{
var metadata = plugin.Metadata;

if (metadata.GUID != ConfigurationManager.GUID)
{
modsWithoutSettings.Add(metadata.Name);
continue;
}
advanced = true;
}

var detected = GetPluginConfig(plugin).Cast<SettingEntryBase>().ToList();

detected.RemoveAll(x => x.Browsable == false);

if (detected.Count == 0 || advanced)
detected.ForEach(x => x.IsAdvanced = true);

// Allow to enable/disable plugin if it uses any update methods ------
if (showDebug)
{
var pluginAssembly = type.Assembly;
var behaviours = UnityEngine.Object.FindObjectsOfType<MonoBehaviour>()
.Where(behaviour => behaviour.GetType().Assembly == pluginAssembly);
foreach (var behaviour in behaviours)
{
var behaviourType = behaviour.GetType();
if (!behaviourType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(m => _updateMethodNames.Contains(m.Name)))
continue;
PropertyInfo property = behaviourType.GetProperty(nameof(MonoBehaviour.enabled));
PropertySettingEntry enabledSetting = new PropertySettingEntry(behaviour, property, plugin);
enabledSetting.DispName = "!Allow plugin to run on every frame";
enabledSetting.Description = "Disabling this will disable some or all of the plugin's functionality.\nHooks and event-based functionality will not be disabled.\nThis setting will be lost after game restart.";
enabledSetting.IsAdvanced = true;
detected.Add(enabledSetting);
break;
}
}

if (detected.Count > 0)
results = results.Concat(detected);
}
}

/// <summary>
/// Get entries for all core BepInEx settings
/// </summary>
private static IEnumerable<SettingEntryBase> GetBepInExCoreConfig()
{
var bepinMeta = new BepInPlugin(nameof(BepInEx), nameof(BepInEx), BepInEx.Paths.BepInExVersion.ToString().Split('+')[0]);

return ConfigFile.CoreConfig.Select(kvp => (SettingEntryBase)new ConfigSettingEntry(kvp.Value, null) { IsAdvanced = true, PluginInfo = bepinMeta });
}

/// <summary>
/// Get entries for all settings of a plugin
/// </summary>
private static IEnumerable<ConfigSettingEntry> GetPluginConfig(PluginInfo plugin)
{
var basePlugin = plugin.Instance as BasePlugin;
return basePlugin != null
? basePlugin.Config.Select(kvp => new ConfigSettingEntry(kvp.Value, plugin))
: Enumerable.Empty<ConfigSettingEntry>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[assembly: AssemblyProduct("BepInEx.ConfigurationManager")]
[assembly: AssemblyTitle("BepInEx.ConfigurationManager")]

[assembly: AssemblyDescription("")]
[assembly: AssemblyDescription("Universal in-game configuration manager for BepInEx plugins")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("https://github.com/BepInEx/BepInEx.ConfigurationManager")]
[assembly: AssemblyCopyright("Copyright © 2019")]
Expand Down
Loading

0 comments on commit e3b754d

Please sign in to comment.