Add xmldocs

This commit is contained in:
js6pak committed 2022-10-09 21:55:53 +02:00
1 parent ed05e563f0
commit 2d148db013
56 files changed
+1036 -148

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.660" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.$(GamePlatform)" Version="$(GameVersion)" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.0.1" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>
@@ -1,3 +1,5 @@
#pragma warning disable SA1611, SA1615
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Toolchains;
+17 -3
View File
@@ -30,9 +30,9 @@
},
"BepInEx.AutoPlugin": {
"type": "Direct",
"requested": "[1.0.1, )",
"resolved": "1.0.1",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA=="
"requested": "[1.1.0, )",
"resolved": "1.1.0",
"contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
},
"BepInEx.IL2CPP.MSBuild": {
"type": "Direct",
@@ -57,6 +57,15 @@
"Samboy063.Cpp2IL.Core": "2022.0.7.2"
}
},
"StyleCop.Analyzers": {
"type": "Direct",
"requested": "[1.2.0-beta.435, )",
"resolved": "1.2.0-beta.435",
"contentHash": "TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
"dependencies": {
"StyleCop.Analyzers.Unstable": "1.2.0.435"
}
},
"AssetRipper.VersionUtilities": {
"type": "Transitive",
"resolved": "1.2.1",
@@ -463,6 +472,11 @@
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"StyleCop.Analyzers.Unstable": {
"type": "Transitive",
"resolved": "1.2.0.435",
"contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
+17 -3
View File
@@ -10,9 +10,9 @@
},
"BepInEx.AutoPlugin": {
"type": "Direct",
"requested": "[1.0.1, )",
"resolved": "1.0.1",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA=="
"requested": "[1.1.0, )",
"resolved": "1.1.0",
"contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
},
"BepInEx.IL2CPP.MSBuild": {
"type": "Direct",
@@ -37,6 +37,15 @@
"Samboy063.Cpp2IL.Core": "2022.0.7.2"
}
},
"StyleCop.Analyzers": {
"type": "Direct",
"requested": "[1.2.0-beta.435, )",
"resolved": "1.2.0-beta.435",
"contentHash": "TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
"dependencies": {
"StyleCop.Analyzers.Unstable": "1.2.0.435"
}
},
"AssetRipper.VersionUtilities": {
"type": "Transitive",
"resolved": "1.2.1",
@@ -303,6 +312,11 @@
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"StyleCop.Analyzers.Unstable": {
"type": "Transitive",
"resolved": "1.2.0.435",
"contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
},
"System.Collections": {
"type": "Transitive",
"resolved": "4.3.0",
+17 -3
View File
@@ -10,9 +10,9 @@
},
"BepInEx.AutoPlugin": {
"type": "Direct",
"requested": "[1.0.1, )",
"resolved": "1.0.1",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA=="
"requested": "[1.1.0, )",
"resolved": "1.1.0",
"contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
},
"BepInEx.IL2CPP.MSBuild": {
"type": "Direct",
@@ -37,6 +37,15 @@
"Samboy063.Cpp2IL.Core": "2022.0.7.2"
}
},
"StyleCop.Analyzers": {
"type": "Direct",
"requested": "[1.2.0-beta.435, )",
"resolved": "1.2.0-beta.435",
"contentHash": "TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
"dependencies": {
"StyleCop.Analyzers.Unstable": "1.2.0.435"
}
},
"AssetRipper.VersionUtilities": {
"type": "Transitive",
"resolved": "1.2.1",
@@ -303,6 +312,11 @@
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"StyleCop.Analyzers.Unstable": {
"type": "Transitive",
"resolved": "1.2.0.435",
"contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
},
"System.Collections": {
"type": "Transitive",
"resolved": "4.3.0",
+34
View File
@@ -8,8 +8,18 @@ using System.Text;
namespace Reactor.Networking;
/// <summary>
/// Represents a Reactor.Protocol mod.
/// </summary>
public readonly struct Mod : IEquatable<Mod>
{
/// <summary>
/// Initializes a new instance of the <see cref="Mod"/> struct.
/// </summary>
/// <param name="id">The id of the mod.</param>
/// <param name="version">The version of the mod.</param>
/// <param name="flags">The flags of the mod.</param>
/// <param name="name">The name of the mod.</param>
public Mod(string id, string version, ModFlags flags, string? name = null)
{
Id = id;
@@ -18,38 +28,62 @@ public readonly struct Mod : IEquatable<Mod>
Name = name;
}
/// <summary>
/// Gets the id of the mod.
/// </summary>
public string Id { get; }
/// <summary>
/// Gets the version of the mod.
/// </summary>
public string Version { get; }
/// <summary>
/// Gets the flags of the mod.
/// </summary>
public ModFlags Flags { get; }
/// <summary>
/// Gets the name of the mod.
/// </summary>
public string? Name { get; }
/// <summary>
/// Gets a value indicating whether the mod is required on all clients.
/// </summary>
public bool IsRequiredOnAllClients => (Flags & ModFlags.RequireOnAllClients) != 0;
/// <inheritdoc />
public bool Equals(Mod other)
{
return Id == other.Id && Version == other.Version;
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Mod other && Equals(other);
}
/// <inheritdoc />
public static bool operator ==(Mod left, Mod right)
{
return left.Equals(right);
}
/// <inheritdoc />
public static bool operator !=(Mod left, Mod right)
{
return !(left == right);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(Id, Version);
}
/// <inheritdoc />
public override string ToString()
{
return $"{Name ?? Id} ({Version})";
+6
View File
@@ -2,9 +2,15 @@ using System;
namespace Reactor.Networking;
/// <summary>
/// Represents flags of the mod.
/// </summary>
[Flags]
public enum ModFlags : ushort
{
/// <summary>
/// No flags.
/// </summary>
None = 0,
/// <summary>
@@ -15,6 +15,7 @@
<NoBuild>true</NoBuild>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
@@ -22,6 +23,10 @@
<Pack>true</Pack>
<PackagePath>$(ContentTargetFolders)\cs\any\$(PackageId)\%(RecursiveDir)\</PackagePath>
</Compile>
<Compile Update="Mod.cs">
<Pack>true</Pack>
<PackagePath>contentFiles\cs\any\Reactor.Networking.Shared\\</PackagePath>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -1,6 +1,12 @@
namespace Reactor.Networking;
/// <summary>
/// Represents a GameData flag for Reactor messages.
/// </summary>
public enum ReactorGameDataFlag : byte
{
/// <summary>
/// KickWithReason GameData message.
/// </summary>
KickWithReason,
}
@@ -1,6 +1,12 @@
namespace Reactor.Networking;
/// <summary>
/// Represents a message flag for Reactor messages.
/// </summary>
public enum ReactorMessageFlags : byte
{
/// <summary>
/// Handshake message.
/// </summary>
Handshake,
}
+28
View File
@@ -6,14 +6,25 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays;
namespace Reactor.Localization;
/// <summary>
/// Provides a way to use custom static StringNames.
/// </summary>
public class CustomStringName
{
private static int _lastId = -1;
private static readonly List<CustomStringName> _list = new();
/// <summary>
/// Gets a list of all <see cref="CustomStringName"/>s.
/// </summary>
public static IReadOnlyList<CustomStringName> List => _list.AsReadOnly();
/// <summary>
/// Registers a <see cref="CustomStringName"/> with specified <paramref name="value"/>.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>A <see cref="CustomStringName"/>.</returns>
public static CustomStringName Register(string value)
{
var customStringName = new CustomStringName(_lastId--, value);
@@ -22,8 +33,14 @@ public class CustomStringName
return customStringName;
}
/// <summary>
/// Gets the id.
/// </summary>
public int Id { get; }
/// <summary>
/// Gets the value.
/// </summary>
public string Value { get; }
private CustomStringName(int id, string value)
@@ -32,7 +49,18 @@ public class CustomStringName
Value = value;
}
/// <summary>
/// Defines an implicit conversion of a <see cref="CustomStringName"/> to a <see cref="StringNames"/>.
/// </summary>
/// <param name="name">The <see cref="CustomStringName"/>.</param>
/// <returns>A <see cref="StringNames"/>.</returns>
public static implicit operator StringNames(CustomStringName name) => (StringNames) name.Id;
/// <summary>
/// Defines an explicit conversion of a <see cref="StringNames"/> to a <see cref="CustomStringName"/>.
/// </summary>
/// <param name="name">The <see cref="StringNames"/>.</param>
/// <returns>A <see cref="CustomStringName"/>.</returns>
public static explicit operator CustomStringName?(StringNames name) => List.SingleOrDefault(x => x.Id == (int) name);
[HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), typeof(StringNames), typeof(Il2CppReferenceArray<Il2CppSystem.Object>))]
@@ -7,11 +7,19 @@ using Reactor.Networking.Serialization;
namespace Reactor.Networking.Attributes;
/// <summary>
/// Automatically registers a <see cref="MessageConverter{T}"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class MessageConverterAttribute : Attribute
{
private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Registers all <see cref="MessageConverter{T}"/>s annotated with <see cref="MessageConverterAttribute"/> in the specified <paramref name="assembly"/>.
/// </summary>
/// <remarks>This is called automatically on plugin assemblies so you probably don't need to call this.</remarks>
/// <param name="assembly">The assembly to search.</param>
public static void Register(Assembly assembly)
{
if (_registeredAssemblies.Contains(assembly)) return;
@@ -10,21 +10,49 @@ using Reactor.Utilities;
namespace Reactor.Networking.Attributes;
/// <summary>
/// Automatically registers a method rpc.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class MethodRpcAttribute : Attribute
{
private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Gets the id of the rpc.
/// </summary>
public uint Id { get; }
public SendOption Option { get; set; } = SendOption.Reliable;
/// <summary>
/// Gets or sets the send option of the rpc.
/// </summary>
public SendOption SendOption { get; set; } = SendOption.Reliable;
/// <summary>
/// Gets or sets the local handling of the rpc.
/// </summary>
public RpcLocalHandling LocalHandling { get; set; } = RpcLocalHandling.Before;
/// <summary>
/// Gets or sets a value indicating whether the rpc should be sent immediately.
/// </summary>
public bool SendImmediately { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MethodRpcAttribute"/> class.
/// </summary>
/// <param name="id">The id of the rpc.</param>
public MethodRpcAttribute(uint id)
{
Id = id;
}
/// <summary>
/// Registers all method rpc's annotated with <see cref="MethodRpcAttribute"/> in the specified <paramref name="assembly"/>.
/// </summary>
/// <remarks>This is called automatically on plugin assemblies so you probably don't need to call this.</remarks>
/// <param name="assembly">The assembly to search.</param>
/// <param name="plugin">The plugin to register the rpc to.</param>
public static void Register(Assembly assembly, BasePlugin plugin)
{
if (_registeredAssemblies.Contains(assembly)) return;
@@ -43,7 +71,7 @@ public sealed class MethodRpcAttribute : Attribute
try
{
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.Option, attribute.LocalHandling, attribute.SendImmediately);
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.SendOption, attribute.LocalHandling, attribute.SendImmediately);
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
}
catch (Exception e)
@@ -4,17 +4,27 @@ using System.Reflection;
namespace Reactor.Networking.Attributes;
/// <summary>
/// Describes the <see cref="ModFlags"/> of the annotated plugin class.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReactorModFlagsAttribute : Attribute
{
/// <summary>
/// Gets flags of the mod.
/// </summary>
public ModFlags Flags { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ReactorModFlagsAttribute"/> class.
/// </summary>
/// <param name="flags">Flags of the mod.</param>
public ReactorModFlagsAttribute(ModFlags flags)
{
Flags = flags;
}
public static ModFlags GetModFlags(Type type)
internal static ModFlags GetModFlags(Type type)
{
var attribute = type.GetCustomAttribute<ReactorModFlagsAttribute>();
if (attribute != null)
@@ -9,25 +9,33 @@ using Reactor.Utilities;
namespace Reactor.Networking.Attributes;
/// <summary>
/// Utility attribute for automatically registering CustomRpc
/// Automatically registers a rpc.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class RegisterCustomRpcAttribute : Attribute
{
private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Gets the id of the rpc.
/// </summary>
public uint Id { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterCustomRpcAttribute"/> class.
/// </summary>
/// <param name="id">The id of the rpc.</param>
public RegisterCustomRpcAttribute(uint id)
{
Id = id;
}
/// <summary>
/// Registers all types marked with <see cref="RegisterCustomRpcAttribute"/> from the specified assembly as custom rpcs
/// Registers all rpc's annotated with <see cref="RegisterCustomRpcAttribute"/> in the specified <paramref name="assembly"/>.
/// </summary>
/// <param name="assembly"></param>
/// <param name="plugin"></param>
/// <remarks>This is called automatically on plugin assemblies so you probably don't need to call this.</remarks>
/// <param name="assembly">The assembly to search.</param>
/// <param name="plugin">The plugin to register the rpc to.</param>
public static void Register(Assembly assembly, BasePlugin plugin)
{
if (_registeredAssemblies.Contains(assembly)) return;
@@ -3,8 +3,17 @@ using InnerNet;
namespace Reactor.Networking.Extensions;
/// <summary>
/// Provides extension methods for <see cref="InnerNetClient"/>.
/// </summary>
public static class ClientExtensions
{
/// <summary>
/// Kicks a client with id equal to <paramref name="targetClientId"/> with a <paramref name="reason"/>.
/// </summary>
/// <param name="innerNetClient">The <see cref="InnerNetClient"/> to send the message from.</param>
/// <param name="targetClientId">The target client's id.</param>
/// <param name="reason">The kick reason.</param>
public static void KickWithReason(this InnerNetClient innerNetClient, int targetClientId, string reason)
{
var writer = MessageWriter.Get(SendOption.Reliable);
@@ -22,6 +31,11 @@ public static class ClientExtensions
writer.Recycle();
}
/// <summary>
/// Disconnects a local <see cref="InnerNetClient"/> with a custom reason.
/// </summary>
/// <param name="innerNetClient">The <see cref="InnerNetClient"/> to disconnect.</param>
/// <param name="reason">The custom reason to disconnect with.</param>
public static void DisconnectWithReason(this InnerNetClient innerNetClient, string reason)
{
innerNetClient.LastCustomDisconnect = reason;
@@ -5,6 +5,9 @@ using UnityEngine;
namespace Reactor.Networking.Extensions;
/// <summary>
/// Provides extension methods for <see cref="MessageWriter"/> and <see cref="MessageReader"/>.
/// </summary>
public static class ExtraMessageExtensions
{
private const float MIN = -50f;
@@ -15,6 +18,11 @@ public static class ExtraMessageExtensions
return Mathf.Clamp((t - MIN) / (MAX - MIN), 0f, 1f);
}
/// <summary>
/// Writes a <see cref="Vector2"/> to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="MessageWriter"/> to write to.</param>
/// <param name="value">The <see cref="Vector2"/> to write.</param>
public static void Write(this MessageWriter writer, Vector2 value)
{
var x = (ushort) (ReverseLerp(value.x) * ushort.MaxValue);
@@ -24,6 +32,11 @@ public static class ExtraMessageExtensions
writer.Write(y);
}
/// <summary>
/// Reads a <see cref="Vector2"/> from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="MessageReader"/> to read from.</param>
/// <returns>A <see cref="Vector2"/> from the <paramref name="reader"/>.</returns>
public static Vector2 ReadVector2(this MessageReader reader)
{
var x = reader.ReadUInt16() / (float) ushort.MaxValue;
@@ -32,6 +45,12 @@ public static class ExtraMessageExtensions
return new Vector2(Mathf.Lerp(MIN, MAX, x), Mathf.Lerp(MIN, MAX, y));
}
/// <summary>
/// Sends a message on the <paramref name="connection"/> with an <paramref name="ackCallback"/>.
/// </summary>
/// <param name="connection">The connection to send the message on.</param>
/// <param name="msg">The message to send.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public static void Send(this UdpConnection connection, MessageWriter msg, Action ackCallback)
{
if (msg.SendOption != SendOption.Reliable)
+27 -4
View File
@@ -11,13 +11,24 @@ using Reactor.Networking.Patches;
namespace Reactor.Networking;
/// <summary>
/// Represents currently loaded mods.
/// </summary>
public static class ModList
{
/// <summary>
/// Gets current mods.
/// </summary>
public static IReadOnlyCollection<Mod> Current { get; private set; } = null!;
private static readonly Dictionary<string, Mod> _modById = new();
private static readonly Dictionary<Type, Mod> _modByPluginType = new();
/// <summary>
/// Gets a mod by it's id.
/// </summary>
/// <param name="id">The id of the mod.</param>
/// <returns>The mod with the specified <paramref name="id"/>.</returns>
public static Mod GetById(string id)
{
return _modById[id];
@@ -42,6 +53,12 @@ public static class ModList
return _netIdByMod[mod];
}
/// <summary>
/// Reads a <see cref="Mod"/> reference from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="MessageReader"/> to read from.</param>
/// <returns>A <see cref="Mod"/> from the <paramref name="reader"/>.</returns>
/// <remarks>This will try to read by net id and fallback to a regular id otherwise.</remarks>
public static Mod ReadMod(this MessageReader reader)
{
var netId = reader.ReadPackedUInt32();
@@ -54,16 +71,22 @@ public static class ModList
return GetById(id);
}
public static void Write(this MessageWriter writer, Mod mod)
/// <summary>
/// Writes a <see cref="Mod"/> reference to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="MessageWriter"/> to write to.</param>
/// <param name="value">The <see cref="Mod"/> to write.</param>
/// <remarks>This will write the net id if <see cref="Mod.IsRequiredOnAllClients"/> is true, regular id otherwise.</remarks>
public static void Write(this MessageWriter writer, Mod value)
{
if (mod.IsRequiredOnAllClients)
if (value.IsRequiredOnAllClients)
{
writer.WritePacked(mod.GetNetId());
writer.WritePacked(value.GetNetId());
}
else
{
writer.WritePacked(0);
writer.Write(mod.Id);
writer.Write(value.Id);
}
}
@@ -3,10 +3,19 @@ using InnerNet;
namespace Reactor.Networking.Patches;
/// <summary>
/// Provides information about the reactor protocol state of the current connection.
/// </summary>
public class ReactorConnection
{
/// <summary>
/// Gets the syncer.
/// </summary>
public Syncer? Syncer { get; internal set; }
/// <summary>
/// Gets the current instance of <see cref="ReactorConnection"/>.
/// </summary>
public static ReactorConnection? Instance { get; private set; }
[HarmonyPatch]
@@ -30,8 +39,18 @@ public class ReactorConnection
}
}
/// <summary>
/// Specifies who syncs the mod list and handles compatibility.
/// </summary>
public enum Syncer
{
/// <summary>
/// A custom region server.
/// </summary>
Server,
/// <summary>
/// The host of the game.
/// </summary>
Host,
}
+68 -8
View File
@@ -5,77 +5,137 @@ using InnerNet;
namespace Reactor.Networking.Rpc;
/// <summary>
/// Base type for custom rpc's but typed with generics.
/// </summary>
/// <typeparam name="TPlugin">The type of the plugin the the rpc is attached to.</typeparam>
/// <typeparam name="TInnerNetObject">The type of the <see cref="InnerNetObject"/>.</typeparam>
/// <typeparam name="TData">The type of the rpc data.</typeparam>
public abstract class CustomRpc<TPlugin, TInnerNetObject, TData> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
{
/// <inheritdoc />
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
{
}
/// <inheritdoc cref="UnsafeCustomRpc.UnsafePlugin"/>
public TPlugin Plugin => (TPlugin) UnsafePlugin;
/// <inheritdoc />
public override Type InnerNetObjectType => typeof(TInnerNetObject);
/// <inheritdoc cref="UnsafeCustomRpc.UnsafeWrite" />
public abstract void Write(MessageWriter writer, TData? data);
/// <inheritdoc cref="UnsafeCustomRpc.UnsafeRead" />
public abstract TData Read(MessageReader reader);
/// <inheritdoc cref="UnsafeCustomRpc.UnsafeHandle" />
public abstract void Handle(TInnerNetObject innerNetObject, TData? data);
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object? data)
{
Write(writer, (TData?) data);
}
/// <inheritdoc />
public override object? UnsafeRead(MessageReader reader)
{
return Read(reader);
}
/// <inheritdoc />
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
{
Handle((TInnerNetObject) innerNetObject, (TData?) data);
}
public void Send(InnerNetObject netObject, TData data, bool immediately = false, Action? ackCallback = null)
/// <summary>
/// Sends this custom rpc on the specified <paramref name="innerNetObject"/> with the specified <paramref name="data"/>.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="data">The data to send.</param>
/// <param name="immediately">Whether to send it immediately.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void Send(InnerNetObject innerNetObject, TData data, bool immediately = false, Action? ackCallback = null)
{
UnsafeSend(netObject, data, immediately, ackCallback: ackCallback);
UnsafeSend(innerNetObject, data, immediately, ackCallback: ackCallback);
}
public void SendTo(InnerNetObject netObject, int targetId, TData data, Action? ackCallback = null)
/// <summary>
/// Sends this custom rpc on the specified <paramref name="innerNetObject"/> with the specified <paramref name="data"/> to the specified target.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="targetClientId">Target client id, defaults to broadcast.</param>
/// <param name="data">The data to send.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void SendTo(InnerNetObject innerNetObject, int targetClientId, TData data, Action? ackCallback = null)
{
UnsafeSend(netObject, data, true, targetId, ackCallback);
UnsafeSend(innerNetObject, data, true, targetClientId, ackCallback);
}
}
/// <summary>
/// Base type for custom rpc's but typed with generics and without any data.
/// </summary>
/// <typeparam name="TPlugin">The type of the plugin the the rpc is attached to.</typeparam>
/// <typeparam name="TInnerNetObject">The type of the <see cref="InnerNetObject"/>.</typeparam>
public abstract class CustomRpc<TPlugin, TInnerNetObject> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
{
/// <inheritdoc />
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
{
}
/// <inheritdoc cref="UnsafeCustomRpc.UnsafePlugin"/>
public TPlugin Plugin => (TPlugin) UnsafePlugin;
/// <inheritdoc />
public override Type InnerNetObjectType => typeof(TInnerNetObject);
/// <summary>
/// Handles the rpc.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> the rpc is being handled on.</param>
public abstract void Handle(TInnerNetObject innerNetObject);
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object? data)
{
}
/// <inheritdoc />
public override object? UnsafeRead(MessageReader reader)
{
return null;
}
/// <inheritdoc />
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
{
Handle((TInnerNetObject) innerNetObject);
}
public void Send(InnerNetObject netObject, bool immediately = false, Action? ackCallback = null)
/// <summary>
/// Sends this custom rpc on the specified <paramref name="innerNetObject"/>.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="immediately">Whether to send it immediately.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void Send(InnerNetObject innerNetObject, bool immediately = false, Action? ackCallback = null)
{
UnsafeSend(netObject, immediately, ackCallback: ackCallback);
UnsafeSend(innerNetObject, immediately, ackCallback: ackCallback);
}
public void SendTo(InnerNetObject netObject, int targetId, Action? ackCallback = null)
/// <summary>
/// Sends this custom rpc on the specified <paramref name="innerNetObject"/> to the specified target.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="targetClientId">Target client id, defaults to broadcast.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void SendTo(InnerNetObject innerNetObject, int targetClientId, Action? ackCallback = null)
{
UnsafeSend(netObject, null, true, targetId, ackCallback);
UnsafeSend(innerNetObject, null, true, targetClientId, ackCallback);
}
}
+23 -3
View File
@@ -10,16 +10,29 @@ using Reactor.Utilities;
namespace Reactor.Networking.Rpc;
/// <summary>
/// Manages custom rpc's.
/// </summary>
public class CustomRpcManager
{
/// <summary>
/// Rpc call id for the reactor's custom rpc wrapper.
/// </summary>
public const byte CallId = byte.MaxValue;
private readonly List<UnsafeCustomRpc> _list = new();
private readonly Dictionary<Type, Dictionary<Mod, Dictionary<uint, UnsafeCustomRpc>>> _map = new();
/// <summary>
/// Gets a list of all custom rpc's.
/// </summary>
public IReadOnlyList<UnsafeCustomRpc> List => _list.AsReadOnly();
public UnsafeCustomRpc Register(UnsafeCustomRpc customRpc)
/// <summary>
/// Registers a custom rpc.
/// </summary>
/// <param name="customRpc">The custom rpc to register.</param>
public void Register(UnsafeCustomRpc customRpc)
{
customRpc.Manager = this;
_list.Add(customRpc);
@@ -31,10 +44,17 @@ public class CustomRpcManager
{
typeof(Rpc<>).MakeGenericType(customRpc.GetType()).GetProperty("Instance")!.SetValue(null, customRpc);
}
return customRpc;
}
/// <summary>
/// Implementation of <see cref="InnerNetObject.HandleRpc"/> for reading and handling reactor custom rpc's.
/// You can use this in your custom <see cref="InnerNetObject"/> to use reactor custom rpc's on it.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> the rpc is handled on.</param>
/// <param name="callId">The call id of the rpc.</param>
/// <param name="reader">The <see cref="MessageReader"/> with the data of the rpc.</param>
/// <returns>A value indicating whether the rpc was handled.</returns>
/// <remarks>Reactor patches base game <see cref="InnerNetObject"/>s to use this.</remarks>
public static bool HandleRpc(InnerNetObject innerNetObject, byte callId, MessageReader reader)
{
if (callId == CallId)
+48 -6
View File
@@ -15,10 +15,24 @@ using Reactor.Utilities;
namespace Reactor.Networking.Rpc;
/// <summary>
/// Provides a custom rpc for method rpc.
/// </summary>
public class MethodRpc : UnsafeCustomRpc
{
public delegate object HandleDelegate(InnerNetObject innerNetObject, object[] args);
private delegate object HandleDelegate(InnerNetObject innerNetObject, object[] args);
private readonly HandleDelegate _handle;
/// <summary>
/// Initializes a new instance of the <see cref="MethodRpc"/> class.
/// </summary>
/// <param name="plugin">The plugin that the rpc is attached to.</param>
/// <param name="method">The method of the method rpc.</param>
/// <param name="id">The id of the rpc.</param>
/// <param name="option">The send option of the rpc.</param>
/// <param name="localHandling">The local handling method of the rpc.</param>
/// <param name="sendImmediately">The value indicating whether the rpc should be sent immediately.</param>
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately) : base(plugin, id)
{
Method = method;
@@ -54,35 +68,58 @@ public class MethodRpc : UnsafeCustomRpc
InnerNetObjectType = method.DeclaringType;
}
Handle = Hook(method, parameters, method.IsStatic);
_handle = Hook(method, parameters, method.IsStatic);
}
/// <summary>
/// Gets the method of the method rpc.
/// </summary>
public MethodInfo Method { get; }
public HandleDelegate Handle { get; }
/// <inheritdoc />
protected internal override bool IsSingleton => false;
/// <inheritdoc />
public override Type InnerNetObjectType { get; }
/// <inheritdoc />
public override RpcLocalHandling LocalHandling { get; }
/// <inheritdoc />
public override SendOption SendOption { get; }
/// <summary>
/// Gets a value indicating whether the method rpc should be sent immediately.
/// </summary>
public bool SendImmediately { get; }
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object? data)
{
var args = (object[]) data!;
MessageSerializer.Serialize(writer, args);
}
/// <inheritdoc />
public override object UnsafeRead(MessageReader reader)
{
var args = MessageSerializer.Deserialize(reader, this);
var parameters = Method.GetParameters();
var args = new object[parameters.Length - 1];
for (var i = 1; i < parameters.Length; i++)
{
var parameter = parameters[i];
args[i - 1] = reader.Deserialize(parameter.ParameterType);
}
return args;
}
/// <inheritdoc />
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
{
var args = (object[]) data!;
var result = Handle(innerNetObject, args);
var result = _handle(innerNetObject, args);
if (result is IEnumerator enumerator)
{
@@ -90,6 +127,11 @@ public class MethodRpc : UnsafeCustomRpc
}
}
/// <summary>
/// Sends this method rpc on the specified <paramref name="innerNetObject"/> with the specified <paramref name="args"/>.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="args">The arguments to serialize and send.</param>
public void Send(InnerNetObject innerNetObject, object[] args)
{
UnsafeSend(innerNetObject, args, SendImmediately);
@@ -98,7 +140,7 @@ public class MethodRpc : UnsafeCustomRpc
private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send));
/// <summary>
/// Hooks the <paramref name="method"/> rpc with a dynamic method that sends it
/// Hooks the <paramref name="method"/> rpc with a dynamic method that sends it.
/// </summary>
private HandleDelegate Hook(MethodInfo method, ParameterInfo[] parameters, bool isStatic)
{
+37 -4
View File
@@ -3,36 +3,69 @@ using BepInEx.Unity.IL2CPP;
namespace Reactor.Networking.Rpc;
/// <summary>
/// Shorthand for <see cref="CustomRpc{TPlugin,TInnerNetObject,TData}"/> with <see cref="PlayerControl"/> as the TInnerNetObject.
/// </summary>
/// <typeparam name="TPlugin">The type of the plugin the the rpc is attached to.</typeparam>
/// <typeparam name="TData">The type of the rpc data.</typeparam>
public abstract class PlayerCustomRpc<TPlugin, TData> : CustomRpc<TPlugin, PlayerControl, TData> where TPlugin : BasePlugin
{
/// <inheritdoc />
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id)
{
}
/// <summary>
/// Sends this custom rpc on the local player with the specified <paramref name="data"/>.
/// </summary>
/// <param name="data">The data to send.</param>
/// <param name="immediately">Whether to send it immediately.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void Send(TData data, bool immediately = false, Action? ackCallback = null)
{
Send(PlayerControl.LocalPlayer, data, immediately, ackCallback);
}
public void SendTo(int targetId, TData data, Action? ackCallback = null)
/// <summary>
/// Sends this custom rpc on the local player with the specified <paramref name="data"/> to the specified target.
/// </summary>
/// <param name="targetClientId">Target client id, defaults to broadcast.</param>
/// <param name="data">The data to send.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void SendTo(int targetClientId, TData data, Action? ackCallback = null)
{
SendTo(PlayerControl.LocalPlayer, targetId, data, ackCallback);
SendTo(PlayerControl.LocalPlayer, targetClientId, data, ackCallback);
}
}
/// <summary>
/// Shorthand for <see cref="CustomRpc{TPlugin,TInnerNetObject}"/> with <see cref="PlayerControl"/> as the TInnerNetObject.
/// </summary>
/// <typeparam name="TPlugin">The type of the plugin the the rpc is attached to.</typeparam>
public abstract class PlayerCustomRpc<TPlugin> : CustomRpc<TPlugin, PlayerControl> where TPlugin : BasePlugin
{
/// <inheritdoc />
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id)
{
}
/// <summary>
/// Sends this custom rpc on the local player.
/// </summary>
/// <param name="immediately">Whether to send it immediately.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void Send(bool immediately = false, Action? ackCallback = null)
{
Send(PlayerControl.LocalPlayer, immediately, ackCallback);
}
public void SendTo(int targetId, Action? ackCallback = null)
/// <summary>
/// Sends this custom rpc on the local player to the specified target.
/// </summary>
/// <param name="targetClientId">Target client id, defaults to broadcast.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void SendTo(int targetClientId, Action? ackCallback = null)
{
SendTo(PlayerControl.LocalPlayer, targetId, ackCallback);
SendTo(PlayerControl.LocalPlayer, targetClientId, ackCallback);
}
}
+7
View File
@@ -2,10 +2,17 @@ using System;
namespace Reactor.Networking.Rpc;
/// <summary>
/// Provides access to singleton custom rpc's.
/// </summary>
/// <typeparam name="T">The type of the custom rpc.</typeparam>
public static class Rpc<T> where T : UnsafeCustomRpc
{
private static T? _instance;
/// <summary>
/// Gets an instance of <typeparamref name="T"/> rpc.
/// </summary>
public static T Instance
{
get
@@ -1,8 +1,22 @@
namespace Reactor.Networking.Rpc;
/// <summary>
/// Specifies how the rpc should be handled locally.
/// </summary>
public enum RpcLocalHandling
{
/// <summary>
/// Don't do anything.
/// </summary>
None,
/// <summary>
/// Invoke Handle before sending.
/// </summary>
Before,
/// <summary>
/// Invoke Handle after sending.
/// </summary>
After,
}
+58
View File
@@ -11,20 +11,53 @@ using Buffer = Il2CppSystem.Buffer;
namespace Reactor.Networking.Rpc;
/// <summary>
/// Base type for custom rpc's.
/// </summary>
public abstract class UnsafeCustomRpc
{
internal CustomRpcManager? Manager { get; set; }
/// <summary>
/// Gets a value indicating whether the rpc is a singleton and can be used with <see cref="Rpc{T}"/>.
/// </summary>
protected internal virtual bool IsSingleton => true;
/// <summary>
/// Gets the id of the rpc.
/// </summary>
public uint Id { get; }
/// <summary>
/// Gets the plugin of the rpc.
/// </summary>
public BasePlugin UnsafePlugin { get; }
/// <summary>
/// Gets the mod of the rpc.
/// </summary>
public Mod Mod { get; }
/// <summary>
/// Gets the InnerNetObject type the rpc targets.
/// </summary>
public abstract Type InnerNetObjectType { get; }
/// <summary>
/// Gets the send option of the rpc.
/// </summary>
public virtual SendOption SendOption => SendOption.Reliable;
/// <summary>
/// Gets the local handling method of the rpc.
/// </summary>
public abstract RpcLocalHandling LocalHandling { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UnsafeCustomRpc"/> class.
/// </summary>
/// <param name="plugin">The plugin that the rpc is attached to.</param>
/// <param name="id">The id of the rpc.</param>
protected UnsafeCustomRpc(BasePlugin plugin, uint id)
{
UnsafePlugin = plugin;
@@ -32,10 +65,35 @@ public abstract class UnsafeCustomRpc
Id = id;
}
/// <summary>
/// Writes <paramref name="data"/> to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="data">The data to be written.</param>
public abstract void UnsafeWrite(MessageWriter writer, object? data);
/// <summary>
/// Reads rpc data from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The reader to read from.</param>
/// <returns>The rpc data from the <paramref name="reader"/>.</returns>
public abstract object? UnsafeRead(MessageReader reader);
/// <summary>
/// Handles rpc data.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> the rpc is being handled on.</param>
/// <param name="data">The data associated with the rpc.</param>
public abstract void UnsafeHandle(InnerNetObject innerNetObject, object? data);
/// <summary>
/// Sends this custom rpc on the specified <paramref name="innerNetObject"/> with the specified <paramref name="data"/>.
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="data">The data to send.</param>
/// <param name="immediately">Whether to send it immediately.</param>
/// <param name="targetClientId">Target client id, defaults to broadcast.</param>
/// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
public void UnsafeSend(InnerNetObject innerNetObject, object? data, bool immediately = false, int targetClientId = -1, Action? ackCallback = null)
{
if (innerNetObject == null) throw new ArgumentNullException(nameof(innerNetObject));
@@ -8,30 +8,41 @@ using Reactor.Networking.Attributes;
namespace Reactor.Networking.Serialization;
/// <summary>
/// Provides a MessageConverter for <see cref="InnerNetObject"/>.
/// </summary>
[MessageConverter]
public class InnerNetObjectConverter : MessageConverter<InnerNetObject>
public class InnerNetObjectConverter : MessageConverter<InnerNetObject?>
{
private static readonly MethodInfo _readNetObject = AccessTools.Method(typeof(MessageExtensions), nameof(MessageExtensions.ReadNetObject));
private static readonly Dictionary<Type, Func<MessageReader, InnerNetObject>> _readNetObjectMap = new();
private static readonly Dictionary<Type, Func<MessageReader, InnerNetObject?>> _readNetObjectMap = new();
public static InnerNetObject ReadNetObject(MessageReader reader, Type t)
/// <summary>
/// <see cref="MessageExtensions.ReadNetObject{T}"/> but without generics.
/// </summary>
/// <param name="reader">The <see cref="MessageReader"/> to read from.</param>
/// <param name="innerNetObjectType">The type of the <see cref="InnerNetObject"/>.</param>
/// <returns>The <see cref="InnerNetObject"/> read from the <paramref name="reader"/>.</returns>
public static InnerNetObject? ReadNetObject(MessageReader reader, Type innerNetObjectType)
{
if (_readNetObjectMap.TryGetValue(t, out var value))
if (_readNetObjectMap.TryGetValue(innerNetObjectType, out var value))
{
return value(reader);
}
var @delegate = _readNetObject.MakeGenericMethod(t).CreateDelegate<Func<MessageReader, InnerNetObject>>();
_readNetObjectMap[t] = @delegate;
var @delegate = _readNetObject.MakeGenericMethod(innerNetObjectType).CreateDelegate<Func<MessageReader, InnerNetObject>>();
_readNetObjectMap[innerNetObjectType] = @delegate;
return @delegate(reader);
}
public override void Write(MessageWriter writer, InnerNetObject value)
/// <inheritdoc />
public override void Write(MessageWriter writer, InnerNetObject? value)
{
MessageExtensions.WriteNetObject(writer, value);
writer.WriteNetObject(value);
}
public override InnerNetObject Read(MessageReader reader, Type objectType)
/// <inheritdoc />
public override InnerNetObject? Read(MessageReader reader, Type objectType)
{
return ReadNetObject(reader, objectType);
}
@@ -3,26 +3,60 @@ using Hazel;
namespace Reactor.Networking.Serialization;
/// <summary>
/// Base type for MessageConverter's.
/// </summary>
public abstract class UnsafeMessageConverter
{
/// <summary>
/// Writes the <paramref name="value"/> to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="MessageWriter"/> to write to.</param>
/// <param name="value">The value to write.</param>
public abstract void UnsafeWrite(MessageWriter writer, object value);
/// <summary>
/// Reads an object of <paramref name="objectType"/> from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="MessageReader"/> to read from.</param>
/// <param name="objectType">The type of the object being read.</param>
/// <returns>The object read from the <paramref name="reader"/>.</returns>
public abstract object UnsafeRead(MessageReader reader, Type objectType);
/// <summary>
/// Checks whether this MessageConverter can convert the specified type.
/// </summary>
/// <param name="objectType">The type to check.</param>
/// <returns>A value indicating whether this MessageConverter can convert the specified type.</returns>
public abstract bool CanConvert(Type objectType);
}
/// <summary>
/// Base type for MessageConverter's but typed with generics.
/// </summary>
/// <typeparam name="T">The type of the value being converted.</typeparam>
public abstract class MessageConverter<T> : UnsafeMessageConverter
{
/// <inheritdoc cref="UnsafeMessageConverter.UnsafeWrite"/>
public abstract void Write(MessageWriter writer, T value);
/// <summary>
/// Reads a <typeparamref name="T"/> from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="MessageReader"/> to read from.</param>
/// <param name="objectType">The type of the object being read.</param>
/// <returns>The object read from the <paramref name="reader"/>.</returns>
public abstract T Read(MessageReader reader, Type objectType);
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object value) => Write(writer, (T) value);
/// <inheritdoc />
public override object UnsafeRead(MessageReader reader, Type objectType) => Read(reader, objectType)!;
}
@@ -3,23 +3,34 @@ using System.Collections.Generic;
using System.Linq;
using Hazel;
using Reactor.Networking.Extensions;
using Reactor.Networking.Rpc;
using UnityEngine;
namespace Reactor.Networking.Serialization;
/// <summary>
/// Provides de/serializing of objects from/into <see cref="MessageReader"/>/<see cref="MessageWriter"/>.
/// </summary>
public static class MessageSerializer
{
private static List<UnsafeMessageConverter> MessageConverters { get; } = new();
private static Dictionary<Type, UnsafeMessageConverter?> MessageConverterMap { get; } = new();
/// <summary>
/// Registers a MessageConverter.
/// </summary>
/// <param name="messageConverter">The MessageConverter to be registered.</param>
public static void Register(UnsafeMessageConverter messageConverter)
{
MessageConverters.Add(messageConverter);
MessageConverterMap.Clear();
}
/// <summary>
/// Finds a MessageConverter for the specified <paramref name="type"/>.
/// </summary>
/// <param name="type">The type of an object.</param>
/// <returns>A MessageConverted that can convert the specified <see cref="Type"/>.</returns>
public static UnsafeMessageConverter? FindConverter(Type type)
{
if (MessageConverterMap.TryGetValue(type, out var value))
@@ -33,6 +44,11 @@ public static class MessageSerializer
return converter;
}
/// <summary>
/// Serializes <paramref name="args"/> to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="MessageWriter"/> to write to.</param>
/// <param name="args">The args to be written.</param>
public static void Serialize(MessageWriter writer, object[] args)
{
foreach (var arg in args)
@@ -41,9 +57,14 @@ public static class MessageSerializer
}
}
public static void Serialize(this MessageWriter writer, object o)
/// <summary>
/// Serializes an <paramref name="object"/> to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="MessageWriter"/> to write to.</param>
/// <param name="object">The <see cref="object"/> to be written.</param>
public static void Serialize(this MessageWriter writer, object @object)
{
switch (o)
switch (@object)
{
case int i:
writer.WritePacked(i);
@@ -73,84 +94,76 @@ public static class MessageSerializer
writer.Write(i);
break;
default:
var converter = FindConverter(o.GetType());
var converter = FindConverter(@object.GetType());
if (converter != null)
{
converter.UnsafeWrite(writer, o);
converter.UnsafeWrite(writer, @object);
break;
}
throw new NotSupportedException("Couldn't serialize " + o.GetType());
throw new NotSupportedException("Couldn't serialize " + @object.GetType());
}
}
public static object[] Deserialize(MessageReader reader, MethodRpc methodRpc)
/// <summary>
/// Deserializes an <see cref="object"/> of <paramref name="objectType"/> from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="MessageReader"/> to read from.</param>
/// <param name="objectType">The <see cref="Type"/> of the object.</param>
/// <returns>An <see cref="object"/> from the <paramref name="reader"/>.</returns>
public static object Deserialize(this MessageReader reader, Type objectType)
{
var parameters = methodRpc.Method.GetParameters();
var args = new object[parameters.Length - 1];
for (var i = 1; i < parameters.Length; i++)
{
var parameter = parameters[i];
args[i - 1] = reader.Deserialize(parameter.ParameterType);
}
return args;
}
public static object Deserialize(this MessageReader reader, Type t)
{
if (t == typeof(int))
if (objectType == typeof(int))
{
return reader.ReadPackedInt32();
}
if (t == typeof(uint))
if (objectType == typeof(uint))
{
return reader.ReadPackedUInt32();
}
if (t == typeof(byte))
if (objectType == typeof(byte))
{
return reader.ReadByte();
}
if (t == typeof(float))
if (objectType == typeof(float))
{
return reader.ReadSingle();
}
if (t == typeof(sbyte))
if (objectType == typeof(sbyte))
{
return reader.ReadSByte();
}
if (t == typeof(ushort))
if (objectType == typeof(ushort))
{
return reader.ReadUInt16();
}
if (t == typeof(bool))
if (objectType == typeof(bool))
{
return reader.ReadBoolean();
}
if (t == typeof(Vector2))
if (objectType == typeof(Vector2))
{
return reader.ReadVector2();
}
if (t == typeof(string))
if (objectType == typeof(string))
{
return reader.ReadString();
}
var converter = FindConverter(t);
var converter = FindConverter(objectType);
if (converter != null)
{
return converter.UnsafeRead(reader, t);
return converter.UnsafeRead(reader, objectType);
}
throw new NotSupportedException("Couldn't deserialize " + t);
throw new NotSupportedException("Couldn't deserialize " + objectType);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ using UnityEngine;
namespace Reactor.Patches.Fixes;
/// <summary>
/// "Fixes" an issue where empty TextBoxes have wrong cursor positions
/// "Fixes" an issue where empty TextBoxes have wrong cursor positions.
/// </summary>
[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))]
internal static class CursorPosPatch
@@ -17,9 +17,7 @@ internal static class ServerInfoFixesPatch
Info($"Joining {__instance.networkAddress}:{__instance.networkPort}");
}
/// <summary>
/// Fixes hardcoded port and filters out IPv6 in DnsRegionInfo
/// </summary>
// Fixes hardcoded port and filters out IPv6 in DnsRegionInfo.
[HarmonyPatch(typeof(DnsRegionInfo), nameof(DnsRegionInfo.PopulateServers))]
[HarmonyPrefix]
public static bool FixPopulateServers(DnsRegionInfo __instance)
@@ -6,7 +6,7 @@ using HarmonyLib;
namespace Reactor.Patches.Miscellaneous;
/// <summary>
/// Allows launching steam builds outside of steam
/// Allows launching steam builds outside of steam.
/// </summary>
[HarmonyPatch]
internal static class StandaloneSteamPatch
+13 -3
View File
@@ -10,13 +10,20 @@ using Version = SemanticVersioning.Version;
namespace Reactor.Patches;
/// <summary>
/// Shows the reactor version on the main menu.
/// </summary>
public static class ReactorVersionShower
{
/// <summary>
/// Gets the <see cref="TextMeshPro"/> text.
/// </summary>
public static TextMeshPro? Text { get; private set; }
public static event TextUpdatedHandler? TextUpdated;
public delegate void TextUpdatedHandler(TextMeshPro text);
/// <summary>
/// Occurs when <see cref="Text"/> is updated.
/// </summary>
public static event Action<TextMeshPro>? TextUpdated;
internal static void Initialize()
{
@@ -59,6 +66,9 @@ public static class ReactorVersionShower
return $"{version.Major}.{version.Minor}.{version.Patch}{(version.PreRelease == null ? string.Empty : $"-{version.PreRelease}")}";
}
/// <summary>
/// Updates <see cref="Text"/> with reactor version and fires <see cref="TextUpdated"/>.
/// </summary>
public static void UpdateText()
{
if (Text == null) return;
+1
View File
@@ -5,6 +5,7 @@
<Description>Core mod and API for Among Us</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- SourceLink -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
+14 -1
View File
@@ -18,17 +18,28 @@ using UnityEngine.SceneManagement;
namespace Reactor;
/// <summary>
/// Reactor's main class.
/// </summary>
[BepInAutoPlugin("gg.reactor.api")]
[BepInProcess("Among Us.exe")]
public partial class ReactorPlugin : BasePlugin
{
/// <summary>
/// Gets harmony instance.
/// </summary>
public Harmony Harmony { get; } = new(Id);
/// <summary>
/// Gets custom rpc manager.
/// </summary>
public CustomRpcManager CustomRpcManager { get; } = new();
public ConfigEntry<bool>? AllowVanillaServers { get; private set; }
internal ConfigEntry<bool>? AllowVanillaServers { get; private set; }
internal RegionInfoWatcher RegionInfoWatcher { get; } = new();
/// <inheritdoc />
public ReactorPlugin()
{
PluginSingleton<ReactorPlugin>.Instance = this;
@@ -40,6 +51,7 @@ public partial class ReactorPlugin : BasePlugin
MethodRpcAttribute.Initialize();
}
/// <inheritdoc />
public override void Load()
{
AllowVanillaServers = Config.Bind("Features", "Allow vanilla servers", false, "Whether reactor should ignore servers not responding to modded handshake. This config is ignored if any plugin uses custom rpcs!");
@@ -63,6 +75,7 @@ public partial class ReactorPlugin : BasePlugin
}));
}
/// <inheritdoc />
public override bool Unload()
{
Harmony.UnpatchSelf();
+43
View File
@@ -10,10 +10,19 @@ using UnityEngine;
namespace Reactor.Utilities;
/// <summary>
/// Provides a standard way of loading asset bundles from a file or embedded resource.
/// </summary>
public static class AssetBundleManager
{
/// <summary>
/// Extension of a asset bundle.
/// </summary>
public const string BundleExtension = ".bundle";
/// <summary>
/// Gets the target name of the current system.
/// </summary>
public static string TargetName { get; }
static AssetBundleManager()
@@ -48,6 +57,11 @@ public static class AssetBundleManager
}
}
/// <summary>
/// Gets the file name for an asset bundle named <paramref name="assetBundleName"/>.
/// </summary>
/// <param name="assetBundleName">The name of the asset bundle.</param>
/// <returns>File name of the asset bundle.</returns>
public static string GetFileName(string assetBundleName) => $"{assetBundleName}-{TargetName}{BundleExtension}";
private static bool TryFindFile(Assembly assembly, string fileName, [NotNullWhen(true)] out string? path)
@@ -85,11 +99,24 @@ public static class AssetBundleManager
return false;
}
/// <summary>
/// Synchronously loads an <see cref="AssetBundle"/> named <paramref name="name"/> from the calling assembly.
/// </summary>
/// <param name="name">The name of the asset bundle.</param>
/// <returns>The loaded assetbundle.</returns>
/// <exception cref="AssetBundleNotFoundException">Couldn't find an assetbundle named <paramref name="name"/>.</exception>
public static AssetBundle Load(string name)
{
return Load(Assembly.GetCallingAssembly(), name);
}
/// <summary>
/// Synchronously loads an <see cref="AssetBundle"/> named <paramref name="name"/> from the specified <paramref name="assembly"/>.
/// </summary>
/// <param name="assembly">The specified assembly.</param>
/// <param name="name">The name of the asset bundle.</param>
/// <returns>The loaded assetbundle.</returns>
/// <exception cref="AssetBundleNotFoundException">Couldn't find an assetbundle named <paramref name="name"/>.</exception>
public static AssetBundle Load(Assembly assembly, string name)
{
var fileName = GetFileName(name);
@@ -107,11 +134,24 @@ public static class AssetBundleManager
throw new AssetBundleNotFoundException(name);
}
/// <summary>
/// Asynchronously loads an <see cref="AssetBundle"/> named <paramref name="name"/> from the calling assembly.
/// </summary>
/// <param name="name">The name of the asset bundle.</param>
/// <returns>The loaded assetbundle.</returns>
/// <exception cref="AssetBundleNotFoundException">Couldn't find an assetbundle named <paramref name="name"/>.</exception>
public static AssetBundleCreateRequest LoadAsync(string name)
{
return LoadAsync(Assembly.GetCallingAssembly(), name);
}
/// <summary>
/// Asynchronously loads an <see cref="AssetBundle"/> named <paramref name="name"/> from the specified <paramref name="assembly"/>.
/// </summary>
/// <param name="assembly">The specified assembly.</param>
/// <param name="name">The name of the asset bundle.</param>
/// <returns>The loaded assetbundle.</returns>
/// <exception cref="AssetBundleNotFoundException">Couldn't find an assetbundle named <paramref name="name"/>.</exception>
public static AssetBundleCreateRequest LoadAsync(Assembly assembly, string name)
{
var fileName = GetFileName(name);
@@ -130,6 +170,9 @@ public static class AssetBundleManager
}
}
/// <summary>
/// The exception that is thrown when an assetbundle is not found.
/// </summary>
public class AssetBundleNotFoundException : IOException
{
internal AssetBundleNotFoundException(string name) : base("Couldn't find an assetbundle named " + name)
@@ -8,20 +8,30 @@ using Il2CppInterop.Runtime.Injection;
namespace Reactor.Utilities.Attributes;
/// <summary>
/// Utility attribute for automatically calling <see cref="ClassInjector.RegisterTypeInIl2Cpp{T}()"/>
/// Automatically registers an il2cpp type using <see cref="ClassInjector.RegisterTypeInIl2Cpp{T}()"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class RegisterInIl2CppAttribute : Attribute
{
private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Gets il2cpp interfaces to be injected with this type.
/// </summary>
public Type[] Interfaces { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterInIl2CppAttribute"/> class without any interfaces.
/// </summary>
public RegisterInIl2CppAttribute()
{
Interfaces = Type.EmptyTypes;
}
/// <summary>
/// Initializes a new instance of the <see cref="RegisterInIl2CppAttribute"/> class with interfaces.
/// </summary>
/// <param name="interfaces">Il2Cpp interfaces to be injected with this type.</param>
public RegisterInIl2CppAttribute(params Type[] interfaces)
{
Interfaces = interfaces;
@@ -51,9 +61,10 @@ public sealed class RegisterInIl2CppAttribute : Attribute
}
/// <summary>
/// Registers in Il2Cpp all types marked with <see cref="RegisterInIl2CppAttribute"/> from the specified assembly.<br/>
/// Registers all Il2Cpp types annotated with <see cref="RegisterInIl2CppAttribute"/> in the specified <paramref name="assembly"/>.
/// </summary>
/// <param name="assembly"></param>
/// <remarks>This is called automatically on plugin assemblies so you probably don't need to call this.</remarks>
/// <param name="assembly">The assembly to search.</param>
public static void Register(Assembly assembly)
{
if (_registeredAssemblies.Contains(assembly)) return;
+15 -1
View File
@@ -5,8 +5,15 @@ using UnityEngine.UI;
namespace Reactor.Utilities;
/// <summary>
/// Provides utilities for working with unity canvas ui.
/// </summary>
public static class CanvasUtilities
{
/// <summary>
/// Creates a canvas.
/// </summary>
/// <returns>A canvas <see cref="GameObject"/>.</returns>
public static GameObject CreateCanvas()
{
var gameObject = new GameObject("Canvas");
@@ -23,6 +30,10 @@ public static class CanvasUtilities
return gameObject;
}
/// <summary>
/// Creates an EventSystem.
/// </summary>
/// <returns>An EventSystem <see cref="GameObject"/>.</returns>
public static GameObject CreateEventSystem()
{
var gameObject = new GameObject("EventSystem");
@@ -34,8 +45,11 @@ public static class CanvasUtilities
}
/// <summary>
/// Shortcut for empty texture
/// Creates an empty texture of specified <paramref name="width"/> and <paramref name="height"/>.
/// </summary>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <returns>An empty texture of specified size.</returns>
public static Texture2D CreateEmptyTexture(int width = 0, int height = 0)
{
return new Texture2D(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
+19 -7
View File
@@ -8,6 +8,9 @@ using UnityEngine;
namespace Reactor.Utilities;
/// <summary>
/// Provides utilities for starting coroutines.
/// </summary>
public static class Coroutines
{
[RegisterInIl2Cpp]
@@ -33,20 +36,29 @@ public static class Coroutines
private static readonly ConditionalWeakTable<IEnumerator, Coroutine> _ourCoroutineStore = new();
[return: NotNullIfNotNull("routine")]
public static IEnumerator? Start(IEnumerator? routine)
/// <summary>
/// Starts a coroutine.
/// </summary>
/// <param name="coroutine">The coroutine to start.</param>
/// <returns>Specified <paramref name="coroutine"/>.</returns>
[return: NotNullIfNotNull("coroutine")]
public static IEnumerator? Start(IEnumerator? coroutine)
{
if (routine != null)
if (coroutine != null)
{
_ourCoroutineStore.AddOrUpdate(routine, Component.Instance!.StartCoroutine(routine));
_ourCoroutineStore.AddOrUpdate(coroutine, Component.Instance!.StartCoroutine(coroutine));
}
return routine;
return coroutine;
}
public static void Stop(IEnumerator? enumerator)
/// <summary>
/// Stops a coroutine.
/// </summary>
/// <param name="coroutine">The coroutine to stop.</param>
public static void Stop(IEnumerator? coroutine)
{
if (enumerator != null && _ourCoroutineStore.TryGetValue(enumerator, out var routine))
if (coroutine != null && _ourCoroutineStore.TryGetValue(coroutine, out var routine))
{
Component.Instance!.StopCoroutine(routine);
}
+6
View File
@@ -4,8 +4,14 @@ using UnityEngine.UI;
namespace Reactor.Utilities;
/// <summary>
/// Provides unity things that were stripped.
/// </summary>
public static class DefaultBundle
{
/// <summary>
/// Gets standard resources used for canvas ui.
/// </summary>
public static DefaultControls.Resources StandardResources { get; internal set; } = null!;
internal static void Load()
+10
View File
@@ -6,9 +6,15 @@ using UnityEngine;
namespace Reactor.Utilities;
/// <summary>
/// Dispatches actions on Unity main thread.
/// </summary>
[RegisterInIl2Cpp]
public sealed class Dispatcher : MonoBehaviour
{
/// <summary>
/// Gets the instance.
/// </summary>
public static Dispatcher Instance { get; private set; } = null!;
private readonly Queue<Action> _queue = new();
@@ -29,6 +35,10 @@ public sealed class Dispatcher : MonoBehaviour
}
}
/// <summary>
/// Enqueues an <paramref name="action"/>.
/// </summary>
/// <param name="action">The action to enqueue.</param>
[HideFromIl2Cpp]
public void Enqueue(Action action)
{
@@ -3,10 +3,20 @@ using UnityEngine;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for <see cref="AssetBundle"/>.
/// </summary>
public static class AssetBundleExtensions
{
public static T? LoadAsset<T>(this AssetBundle assetBundle, string name) where T : Object
/// <summary>
/// Loads an asset with <paramref name="name"/> from the <paramref name="bundle"/> with the specified <typeparamref name="T"/> type.
/// </summary>
/// <param name="bundle">The <see cref="AssetBundle"/> to load the asset from.</param>
/// <param name="name">The name of the asset.</param>
/// <typeparam name="T">The type of the asset.</typeparam>
/// <returns>The loaded asset or null if it wasn't found.</returns>
public static T? LoadAsset<T>(this AssetBundle bundle, string name) where T : Object
{
return assetBundle.LoadAsset(name, Il2CppType.Of<T>())?.Cast<T>();
return bundle.LoadAsset(name, Il2CppType.Of<T>())?.Cast<T>();
}
}
+20 -6
View File
@@ -3,13 +3,15 @@ using UnityEngine;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// GUI utilities
/// Provides extension methods for unity imgui.
/// </summary>
public static class GUIExtensions
{
/// <summary>
/// Clamp Rect to screen size
/// Clamps <paramref name="rect"/> to the screen size.
/// </summary>
/// <param name="rect">The <see cref="Rect"/> to clamp.</param>
/// <returns>A clamped rect.</returns>
public static Rect ClampScreen(this Rect rect)
{
rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width);
@@ -19,8 +21,10 @@ public static class GUIExtensions
}
/// <summary>
/// Reset Rect size
/// Set <paramref name="rect"/>'s <see cref="Rect.width"/> and <see cref="Rect.height"/> to 0.
/// </summary>
/// <param name="rect">The <see cref="Rect"/> to reset.</param>
/// <returns>A rect with reset size.</returns>
public static Rect ResetSize(this Rect rect)
{
rect.width = rect.height = 0;
@@ -29,13 +33,23 @@ public static class GUIExtensions
}
/// <summary>
/// Create <see cref="Sprite"/> from <paramref name="tex"/>
/// Creates a <see cref="Sprite"/> from the <paramref name="texture"/>.
/// </summary>
public static Sprite CreateSprite(this Texture2D tex)
/// <param name="texture">Texture from which to obtain the sprite graphic.</param>
/// <param name="pivot">Sprite's pivot point relative to its graphic rectangle.</param>
/// <param name="pixelsPerUnit">The number of pixels in the sprite that correspond to one unit in world space.</param>
/// <returns>A sprite.</returns>
public static Sprite CreateSprite(this Texture2D texture, Vector2? pivot = null, float pixelsPerUnit = 100f)
{
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), pivot ?? new Vector2(0.5f, 0.5f), pixelsPerUnit);
}
/// <summary>
/// Sets <paramref name="rectTransform"/>'s width and height.
/// </summary>
/// <param name="rectTransform">The <see cref="RectTransform"/> to set the size of.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public static void SetSize(this RectTransform rectTransform, float width, float height)
{
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
@@ -3,8 +3,17 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for Il2CppInterop.
/// </summary>
public static class Il2CppInteropExtensions
{
/// <summary>
/// Creates a span over a <see cref="Il2CppStructArray{T}"/>.
/// </summary>
/// <param name="array">The array to create a span over.</param>
/// <typeparam name="T">The type of items in the <see cref="Il2CppStructArray{T}"/>.</typeparam>
/// <returns>A span.</returns>
public static unsafe Span<T> ToSpan<T>(this Il2CppStructArray<T> array) where T : unmanaged
{
return new Span<T>(IntPtr.Add(array.Pointer, IntPtr.Size * 4).ToPointer(), array.Length);
@@ -4,29 +4,45 @@ using System.Linq;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides Random related extension methods.
/// </summary>
public static class RandomExtensions
{
/// <summary>
/// Returns random <typeparamref name="T"/> from <paramref name="input"/> using <paramref name="random"/>
/// Returns a random double that is within a specified range.
/// </summary>
public static T? Random<T>(this IEnumerable<T> input, Random random)
{
var list = input as IList<T> ?? input.ToList();
return list.Count == 0 ? default : list[random.Next(0, list.Count)];
}
/// <summary>Returns a random double that is within a specified range.</summary>
/// <param name="random">The <see cref="System.Random"/> to get the random number from.</param>
/// <param name="minValue">The minimum value of the range.</param>
/// <param name="maxValue">The maximum value of the range.</param>
/// <returns>A random double that is within a specified range.</returns>
public static double NextDouble(this Random random, double minValue, double maxValue)
{
return random.NextDouble() * (maxValue - minValue) + minValue;
}
/// <summary>
/// Returns random <typeparamref name="T"/> from <paramref name="input"/>
/// Returns a random <typeparamref name="T"/> from the <paramref name="input"/>.
/// </summary>
/// <param name="input">The input enumerable to take the item from.</param>
/// <typeparam name="T">The type of <paramref name="input"/> items.</typeparam>
/// <returns>A random item from the <paramref name="input"/> enumerable.</returns>
public static T? Random<T>(this IEnumerable<T> input)
{
var list = input as IList<T> ?? input.ToList();
return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)];
}
/// <summary>
/// Returns a random <typeparamref name="T"/> from the <paramref name="input"/> using <paramref name="random"/>.
/// </summary>
/// <param name="input">The input enumerable to take the item from.</param>
/// <param name="random">The <see cref="System.Random"/> to get the random number from.</param>
/// <typeparam name="T">The type of <paramref name="input"/> items.</typeparam>
/// <returns>A random item from the <paramref name="input"/> enumerable.</returns>
public static T? Random<T>(this IEnumerable<T> input, Random random)
{
var list = input as IList<T> ?? input.ToList();
return list.Count == 0 ? default : list[random.Next(0, list.Count)];
}
}
@@ -14,17 +14,30 @@ using MethodInfo = System.Reflection.MethodInfo;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for reflection.
/// </summary>
public static class ReflectionExtensions
{
/// <summary>
/// Gets a <see cref="Il2CppMethodInfo"/> for the specified <see cref="MethodInfo"/>.
/// </summary>
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
/// <returns>A <see cref="Il2CppMethodInfo"/>.</returns>
public static Il2CppMethodInfo ToIl2CppMethodInfo(this MethodInfo methodInfo)
{
var il2CppMethodField = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(methodInfo);
if (il2CppMethodField == null) throw new ArgumentException($"'{methodInfo.Name}' is not an il2cpp method", nameof(methodInfo));
var il2CppMethod = (IntPtr) il2CppMethodField.GetValue(null)!;
return new Il2CppSystem.Reflection.MethodInfo(IL2CPP.il2cpp_method_get_object(il2CppMethod, IntPtr.Zero));
return new Il2CppMethodInfo(IL2CPP.il2cpp_method_get_object(il2CppMethod, IntPtr.Zero));
}
/// <summary>
/// Gets enumerator's MoveNext type for the specified <see cref="Il2CppMethodInfo"/>.
/// </summary>
/// <param name="methodInfo">The enumerator <see cref="Il2CppMethodInfo"/>.</param>
/// <returns>A <see cref="Il2CppSystemType"/> for enumerator's MoveNext type.</returns>
public static Il2CppSystemType GetEnumeratorMoveNextType(this Il2CppMethodInfo methodInfo)
{
var customAttribute = Il2CppCustomAttributeExtensions.GetCustomAttribute(methodInfo, Il2CppType.Of<IteratorStateMachineAttribute>()).TryCast<IteratorStateMachineAttribute>();
@@ -33,6 +46,11 @@ public static class ReflectionExtensions
return customAttribute._StateMachineType_k__BackingField;
}
/// <summary>
/// Gets a <see cref="Type"/> for the specified <see cref="Il2CppSystemType"/>.
/// </summary>
/// <param name="type">The <see cref="Il2CppSystemType"/>.</param>
/// <returns>A <see cref="Type"/>.</returns>
public static Type ToSystemType(this Il2CppSystemType type)
{
var result = Type.GetType(type.AssemblyQualifiedName);
@@ -51,16 +69,37 @@ public static class ReflectionExtensions
throw new TypeLoadException("Failed to find a system type for " + type.AssemblyQualifiedName);
}
/// <summary>
/// Gets MoveNext <see cref="MethodInfo"/> for specified enumerator method.
/// </summary>
/// <param name="type">The enclosing type of the method.</param>
/// <param name="methodName">The name of the method.</param>
/// <returns>A <see cref="MethodInfo"/>.</returns>
public static MethodInfo EnumeratorMoveNext(Type type, string methodName)
{
return AccessTools.Method(AccessTools.Method(type, methodName).ToIl2CppMethodInfo().GetEnumeratorMoveNextType().ToSystemType(), "MoveNext");
}
public static IEnumerable<MethodBase> GetMethods(this Type type, BindingFlags bindingAttr, Type returnType, params Type[] parameterTypes)
/// <summary>
/// Gets all method from the <paramref name="type"/> with the specified <paramref name="bindingFlags"/>, <paramref name="returnType"/> and <paramref name="parameterTypes"/>.
/// </summary>
/// <param name="type">The type to search the methods in.</param>
/// <param name="bindingFlags">The <see cref="BindingFlags"/>.</param>
/// <param name="returnType">The return type.</param>
/// <param name="parameterTypes">The parameter types.</param>
/// <returns>An array of <see cref="MethodInfo"/> objects representing all methods defined for the current <see cref="Type"/> that match the specified binding constraints.</returns>
public static IEnumerable<MethodBase> GetMethods(this Type type, BindingFlags bindingFlags, Type returnType, params Type[] parameterTypes)
{
return type.GetMethods(bindingAttr).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes));
return type.GetMethods(bindingFlags).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes));
}
/// <summary>
/// Gets all method from the <paramref name="type"/> with the specified <paramref name="returnType"/> and <paramref name="parameterTypes"/>.
/// </summary>
/// <param name="type">The type to search the methods in.</param>
/// <param name="returnType">The return type.</param>
/// <param name="parameterTypes">The parameter types.</param>
/// <returns>An array of <see cref="MethodInfo"/> objects representing all methods defined for the current <see cref="Type"/> that match the specified binding constraints.</returns>
public static IEnumerable<MethodBase> GetMethods(this Type type, Type returnType, params Type[] parameterTypes)
{
return type.GetMethods(AccessTools.all, returnType, parameterTypes);
@@ -3,13 +3,17 @@ using System.Diagnostics.CodeAnalysis;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
namespace Reactor.Utilities;
namespace Reactor.Utilities.Extensions;
public static class SceneManagerUtilities
/// <summary>
/// Provides extension methods for <see cref="SceneManager"/>.
/// </summary>
public static class SceneManagerExtensions
{
/// <summary>
/// Adds a delegate to get one notification when a scene has loaded.
/// </summary>
/// <param name="value">The action.</param>
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "This supplements existing methods like add_sceneLoaded and remove_sceneLoaded")]
public static void once_sceneLoaded(Action<Scene, LoadSceneMode> value)
{
@@ -8,13 +8,20 @@ using Reactor.Utilities.Attributes;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for <see cref="Stream"/>.
/// </summary>
public static class StreamExtensions
{
/// <summary>
/// Provides a <see cref="Il2CppSystem.IO.Stream"/> which uses <see cref="System.IO.Stream"/> under the hood.
/// </summary>
[RegisterInIl2Cpp]
public class StreamWrapper : Il2CppSystem.IO.Stream
{
private readonly Stream _stream;
/// <inheritdoc />
public StreamWrapper(Stream stream) : base(ClassInjector.DerivedConstructorPointer<StreamWrapper>())
{
ClassInjector.DerivedConstructorBody(this);
@@ -29,31 +36,37 @@ public static class StreamExtensions
return new Span<byte>(rawBuffer + offset, count);
}
/// <inheritdoc />
public override int Read(Il2CppStructArray<byte> buffer, int offset, int count)
{
return _stream.Read(GetSpan(buffer, offset, count));
}
/// <inheritdoc />
public override void Write(Il2CppStructArray<byte> buffer, int offset, int count)
{
_stream.Write(GetSpan(buffer, offset, count));
}
/// <inheritdoc />
public override void Flush()
{
_stream.Flush();
}
/// <inheritdoc />
public override void Close()
{
_stream.Close();
}
/// <inheritdoc />
public override void Dispose()
{
_stream.Dispose();
}
/// <inheritdoc />
public override long Seek(long offset, Il2CppSystem.IO.SeekOrigin origin)
{
return _stream.Seek(offset, origin switch
@@ -65,16 +78,25 @@ public static class StreamExtensions
});
}
/// <inheritdoc />
public override void SetLength(long value)
{
_stream.SetLength(value);
}
/// <inheritdoc />
public override bool CanRead => _stream.CanRead;
/// <inheritdoc />
public override bool CanSeek => _stream.CanSeek;
/// <inheritdoc />
public override bool CanWrite => _stream.CanWrite;
/// <inheritdoc />
public override long Length => _stream.Length;
/// <inheritdoc />
public override long Position
{
get => _stream.Position;
@@ -83,13 +105,17 @@ public static class StreamExtensions
}
/// <summary>
/// Wraps a System Stream into an Il2Cpp Stream
/// Wraps a <see cref="System.IO.Stream"/> into a <see cref="Il2CppSystem.IO.Stream"/>.
/// </summary>
/// <param name="stream">The stream to wrap.</param>
/// <returns>A <see cref="StreamWrapper"/> for the specified <paramref name="stream"/>.</returns>
public static StreamWrapper AsIl2Cpp(this Stream stream) => new(stream);
/// <summary>
/// Fully read <paramref name="input"/> stream, can be used as workaround for il2cpp streams.
/// Fully reads the <paramref name="input"/> stream.
/// </summary>
/// <param name="input">The stream to read.</param>
/// <returns>A byte array read from the <see cref="Stream"/>.</returns>
public static byte[] ReadFully(this Stream input)
{
using var ms = new MemoryStream();
@@ -2,6 +2,9 @@ using UnityEngine;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for unity.
/// </summary>
public static class UnityExtensions
{
/// <summary>
@@ -9,7 +12,7 @@ public static class UnityExtensions
/// </summary>
/// <param name="color">The color to be converted.</param>
/// <returns>Hexadecimal string representing the color.</returns>
/// <remarks>https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html</remarks>
/// <remarks>https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html.</remarks>
public static string ToHtmlStringRGBA(this Color32 color)
{
return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}";
@@ -26,8 +29,10 @@ public static class UnityExtensions
private static readonly int _addColor = Shader.PropertyToID("_AddColor");
/// <summary>
/// Sets color outline for renderers using default Among Us shader
/// Sets the outline for renderer using the default Among Us shader.
/// </summary>
/// <param name="renderer">The renderer to set the color on.</param>
/// <param name="color">The color or null to disable the outline.</param>
public static void SetOutline(this Renderer renderer, Color? color)
{
renderer.material.SetFloat(_outline, color.HasValue ? 1 : 0);
@@ -1,42 +1,67 @@
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for <see cref="UnityObject"/>.
/// </summary>
public static class UnityObjectExtensions
{
/// <summary>
/// Stops <paramref name="obj"/> from being destroyed
/// Stops <paramref name="obj"/> from being destroyed.
/// </summary>
/// <param name="obj">Object to stop from being destroyed</param>
/// <returns>Passed <paramref name="obj"/></returns>
public static T DontDestroy<T>(this T obj) where T : Object
/// <param name="obj">The object to stop from being destroyed.</param>
/// <typeparam name="T">The type of the object.</typeparam>
/// <returns>Passed <paramref name="obj"/>.</returns>
public static T DontDestroy<T>(this T obj) where T : UnityObject
{
obj.hideFlags |= HideFlags.HideAndDontSave;
return obj.DontDestroyOnLoad();
}
public static T DontUnload<T>(this T obj) where T : Object
/// <summary>
/// Stops <paramref name="obj"/> from being unloaded.
/// </summary>
/// <param name="obj">The object to stop from being unloaded.</param>
/// <typeparam name="T">The type of the object.</typeparam>
/// <returns>Passed <paramref name="obj"/>.</returns>
public static T DontUnload<T>(this T obj) where T : UnityObject
{
obj.hideFlags |= HideFlags.DontUnloadUnusedAsset;
return obj;
}
public static T DontDestroyOnLoad<T>(this T obj) where T : Object
/// <summary>
/// Stops <paramref name="obj"/> from being destroyed on load.
/// </summary>
/// <param name="obj">The object to stop from being destroyed on load.</param>
/// <typeparam name="T">The type of the object.</typeparam>
/// <returns>Passed <paramref name="obj"/>.</returns>
public static T DontDestroyOnLoad<T>(this T obj) where T : UnityObject
{
Object.DontDestroyOnLoad(obj);
UnityObject.DontDestroyOnLoad(obj);
return obj;
}
public static void Destroy(this Object obj)
/// <summary>
/// Destroys the <paramref name="obj"/>.
/// </summary>
/// <param name="obj">The object to destroy.</param>
public static void Destroy(this UnityObject obj)
{
Object.Destroy(obj);
UnityObject.Destroy(obj);
}
public static void DestroyImmediate(this Object obj)
/// <summary>
/// Destroys the <paramref name="obj"/> immediately.
/// </summary>
/// <param name="obj">The object to destroy immediately.</param>
public static void DestroyImmediate(this UnityObject obj)
{
Object.DestroyImmediate(obj);
UnityObject.DestroyImmediate(obj);
}
}
@@ -3,10 +3,14 @@ using Il2CppSystem.Runtime.CompilerServices;
namespace Reactor.Utilities;
/// <inheritdoc />
public sealed class Il2CppEqualityComparer<T> : IEqualityComparer<T> where T : Il2CppSystem.Object
{
private static Il2CppEqualityComparer<T>? _instance;
/// <summary>
/// Gets the instance.
/// </summary>
public static Il2CppEqualityComparer<T> Instance
{
get
+7 -2
View File
@@ -4,20 +4,25 @@ using UnityEngine;
namespace Reactor.Utilities.ImGui;
/// <summary>
/// Draggable version of <see cref="Window"/>.
/// </summary>
public class DragWindow : Window
{
/// <inheritdoc />
public DragWindow(Rect rect, string title, Action<int> func) : base(rect, title, func)
{
Func = id =>
{
func(id);
UnityEngine.GUI.DragWindow(new Rect(0, 0, 10000, 20));
GUI.DragWindow(new Rect(0, 0, 10000, 20));
Rect = Rect.ClampScreen();
};
}
public DragWindow(Rect rect, string title, Action func) : this(rect, title, id => func())
/// <inheritdoc />
public DragWindow(Rect rect, string title, Action func) : this(rect, title, _ => func())
{
}
}
+37 -2
View File
@@ -4,22 +4,53 @@ using UnityEngine;
namespace Reactor.Utilities.ImGui;
/// <summary>
/// Utility wrapper over <see cref="GUI"/>.<see cref="GUI.Window(int,UnityEngine.Rect,UnityEngine.GUI.WindowFunction,UnityEngine.GUIContent,UnityEngine.GUIStyle)"/>.
/// </summary>
public class Window
{
private static int _lastWindowId;
/// <summary>
/// Gets the next window id.
/// </summary>
/// <returns>A window id.</returns>
public static int NextWindowId()
{
return _lastWindowId++;
}
/// <summary>
/// Gets or sets the id of the window.
/// </summary>
public int Id { get; set; } = NextWindowId();
/// <summary>
/// Gets or sets a value indicating whether the window is enabled and shown.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Gets or sets the rect of the window.
/// </summary>
public Rect Rect { get; set; }
/// <summary>
/// Gets or sets the render function of the window.
/// </summary>
public Action<int> Func { get; set; }
/// <summary>
/// Gets or sets the title of the window.
/// </summary>
public string Title { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="title">The title.</param>
/// <param name="func">The render function.</param>
public Window(Rect rect, string title, Action<int> func)
{
Rect = rect;
@@ -27,11 +58,15 @@ public class Window
Func = func;
}
public Window(Rect rect, string title, Action func) : this(rect, title, id => func())
/// <inheritdoc />
public Window(Rect rect, string title, Action func) : this(rect, title, _ => func())
{
}
public void OnGUI()
/// <summary>
/// Draws the window gui.
/// </summary>
public virtual void OnGUI()
{
if (Enabled)
{
+7
View File
@@ -5,8 +5,15 @@ using BepInEx.Unity.IL2CPP;
namespace Reactor.Utilities;
/// <summary>
/// Provides singleton access to plugins logger.
/// </summary>
/// <typeparam name="T">The type of the plugin.</typeparam>
public static class Logger<T> where T : BasePlugin
{
/// <summary>
/// Gets the instance.
/// </summary>
public static ManualLogSource Instance => PluginSingleton<T>.Instance.Log;
/// <inheritdoc cref="ManualLogSource.Log(BepInEx.Logging.LogLevel,object)"/>
+7
View File
@@ -5,10 +5,17 @@ using BepInEx.Unity.IL2CPP;
namespace Reactor.Utilities;
/// <summary>
/// Provides singleton access to plugins instance.
/// </summary>
/// <typeparam name="T">The type of the plugin.</typeparam>
public static class PluginSingleton<T> where T : BasePlugin
{
private static T? _instance;
/// <summary>
/// Gets or sets an instance of <typeparamref name="T"/> plugin.
/// </summary>
public static T Instance
{
get => _instance ??= IL2CPPChainloader.Instance.Plugins.Values.Select(x => x.Instance).OfType<T>().Single();
+17 -3
View File
@@ -10,9 +10,9 @@
},
"BepInEx.AutoPlugin": {
"type": "Direct",
"requested": "[1.0.1, )",
"resolved": "1.0.1",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA=="
"requested": "[1.1.0, )",
"resolved": "1.1.0",
"contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
},
"BepInEx.IL2CPP.MSBuild": {
"type": "Direct",
@@ -47,6 +47,15 @@
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"StyleCop.Analyzers": {
"type": "Direct",
"requested": "[1.2.0-beta.435, )",
"resolved": "1.2.0-beta.435",
"contentHash": "TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==",
"dependencies": {
"StyleCop.Analyzers.Unstable": "1.2.0.435"
}
},
"AssetRipper.VersionUtilities": {
"type": "Transitive",
"resolved": "1.2.1",
@@ -323,6 +332,11 @@
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"StyleCop.Analyzers.Unstable": {
"type": "Transitive",
"resolved": "1.2.0.435",
"contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
},
"System.Collections": {
"type": "Transitive",
"resolved": "4.3.0",