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="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="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" /> <PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup> </ItemGroup>
@@ -1,3 +1,5 @@
#pragma warning disable SA1611, SA1615
using BenchmarkDotNet.Engines; using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Toolchains; using BenchmarkDotNet.Toolchains;
+17 -3
View File
@@ -30,9 +30,9 @@
}, },
"BepInEx.AutoPlugin": { "BepInEx.AutoPlugin": {
"type": "Direct", "type": "Direct",
"requested": "[1.0.1, )", "requested": "[1.1.0, )",
"resolved": "1.0.1", "resolved": "1.1.0",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA==" "contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
}, },
"BepInEx.IL2CPP.MSBuild": { "BepInEx.IL2CPP.MSBuild": {
"type": "Direct", "type": "Direct",
@@ -57,6 +57,15 @@
"Samboy063.Cpp2IL.Core": "2022.0.7.2" "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": { "AssetRipper.VersionUtilities": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.2.1", "resolved": "1.2.1",
@@ -463,6 +472,11 @@
"resolved": "2.0.2", "resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA==" "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": { "System.CodeDom": {
"type": "Transitive", "type": "Transitive",
"resolved": "6.0.0", "resolved": "6.0.0",
+17 -3
View File
@@ -10,9 +10,9 @@
}, },
"BepInEx.AutoPlugin": { "BepInEx.AutoPlugin": {
"type": "Direct", "type": "Direct",
"requested": "[1.0.1, )", "requested": "[1.1.0, )",
"resolved": "1.0.1", "resolved": "1.1.0",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA==" "contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
}, },
"BepInEx.IL2CPP.MSBuild": { "BepInEx.IL2CPP.MSBuild": {
"type": "Direct", "type": "Direct",
@@ -37,6 +37,15 @@
"Samboy063.Cpp2IL.Core": "2022.0.7.2" "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": { "AssetRipper.VersionUtilities": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.2.1", "resolved": "1.2.1",
@@ -303,6 +312,11 @@
"resolved": "2.0.2", "resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA==" "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": { "System.Collections": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.3.0", "resolved": "4.3.0",
+17 -3
View File
@@ -10,9 +10,9 @@
}, },
"BepInEx.AutoPlugin": { "BepInEx.AutoPlugin": {
"type": "Direct", "type": "Direct",
"requested": "[1.0.1, )", "requested": "[1.1.0, )",
"resolved": "1.0.1", "resolved": "1.1.0",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA==" "contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
}, },
"BepInEx.IL2CPP.MSBuild": { "BepInEx.IL2CPP.MSBuild": {
"type": "Direct", "type": "Direct",
@@ -37,6 +37,15 @@
"Samboy063.Cpp2IL.Core": "2022.0.7.2" "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": { "AssetRipper.VersionUtilities": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.2.1", "resolved": "1.2.1",
@@ -303,6 +312,11 @@
"resolved": "2.0.2", "resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA==" "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": { "System.Collections": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.3.0", "resolved": "4.3.0",
+34
View File
@@ -8,8 +8,18 @@ using System.Text;
namespace Reactor.Networking; namespace Reactor.Networking;
/// <summary>
/// Represents a Reactor.Protocol mod.
/// </summary>
public readonly struct Mod : IEquatable<Mod> 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) public Mod(string id, string version, ModFlags flags, string? name = null)
{ {
Id = id; Id = id;
@@ -18,38 +28,62 @@ public readonly struct Mod : IEquatable<Mod>
Name = name; Name = name;
} }
/// <summary>
/// Gets the id of the mod.
/// </summary>
public string Id { get; } public string Id { get; }
/// <summary>
/// Gets the version of the mod.
/// </summary>
public string Version { get; } public string Version { get; }
/// <summary>
/// Gets the flags of the mod.
/// </summary>
public ModFlags Flags { get; } public ModFlags Flags { get; }
/// <summary>
/// Gets the name of the mod.
/// </summary>
public string? Name { get; } 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; public bool IsRequiredOnAllClients => (Flags & ModFlags.RequireOnAllClients) != 0;
/// <inheritdoc />
public bool Equals(Mod other) public bool Equals(Mod other)
{ {
return Id == other.Id && Version == other.Version; return Id == other.Id && Version == other.Version;
} }
/// <inheritdoc />
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
return obj is Mod other && Equals(other); return obj is Mod other && Equals(other);
} }
/// <inheritdoc />
public static bool operator ==(Mod left, Mod right) public static bool operator ==(Mod left, Mod right)
{ {
return left.Equals(right); return left.Equals(right);
} }
/// <inheritdoc />
public static bool operator !=(Mod left, Mod right) public static bool operator !=(Mod left, Mod right)
{ {
return !(left == right); return !(left == right);
} }
/// <inheritdoc />
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(Id, Version); return HashCode.Combine(Id, Version);
} }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"{Name ?? Id} ({Version})"; return $"{Name ?? Id} ({Version})";
+6
View File
@@ -2,9 +2,15 @@ using System;
namespace Reactor.Networking; namespace Reactor.Networking;
/// <summary>
/// Represents flags of the mod.
/// </summary>
[Flags] [Flags]
public enum ModFlags : ushort public enum ModFlags : ushort
{ {
/// <summary>
/// No flags.
/// </summary>
None = 0, None = 0,
/// <summary> /// <summary>
@@ -15,6 +15,7 @@
<NoBuild>true</NoBuild> <NoBuild>true</NoBuild>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -22,6 +23,10 @@
<Pack>true</Pack> <Pack>true</Pack>
<PackagePath>$(ContentTargetFolders)\cs\any\$(PackageId)\%(RecursiveDir)\</PackagePath> <PackagePath>$(ContentTargetFolders)\cs\any\$(PackageId)\%(RecursiveDir)\</PackagePath>
</Compile> </Compile>
<Compile Update="Mod.cs">
<Pack>true</Pack>
<PackagePath>contentFiles\cs\any\Reactor.Networking.Shared\\</PackagePath>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,6 +1,12 @@
namespace Reactor.Networking; namespace Reactor.Networking;
/// <summary>
/// Represents a GameData flag for Reactor messages.
/// </summary>
public enum ReactorGameDataFlag : byte public enum ReactorGameDataFlag : byte
{ {
/// <summary>
/// KickWithReason GameData message.
/// </summary>
KickWithReason, KickWithReason,
} }
@@ -1,6 +1,12 @@
namespace Reactor.Networking; namespace Reactor.Networking;
/// <summary>
/// Represents a message flag for Reactor messages.
/// </summary>
public enum ReactorMessageFlags : byte public enum ReactorMessageFlags : byte
{ {
/// <summary>
/// Handshake message.
/// </summary>
Handshake, Handshake,
} }
+28
View File
@@ -6,14 +6,25 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays;
namespace Reactor.Localization; namespace Reactor.Localization;
/// <summary>
/// Provides a way to use custom static StringNames.
/// </summary>
public class CustomStringName public class CustomStringName
{ {
private static int _lastId = -1; private static int _lastId = -1;
private static readonly List<CustomStringName> _list = new(); 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(); 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) public static CustomStringName Register(string value)
{ {
var customStringName = new CustomStringName(_lastId--, value); var customStringName = new CustomStringName(_lastId--, value);
@@ -22,8 +33,14 @@ public class CustomStringName
return customStringName; return customStringName;
} }
/// <summary>
/// Gets the id.
/// </summary>
public int Id { get; } public int Id { get; }
/// <summary>
/// Gets the value.
/// </summary>
public string Value { get; } public string Value { get; }
private CustomStringName(int id, string value) private CustomStringName(int id, string value)
@@ -32,7 +49,18 @@ public class CustomStringName
Value = value; 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; 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); 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>))] [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), typeof(StringNames), typeof(Il2CppReferenceArray<Il2CppSystem.Object>))]
@@ -7,11 +7,19 @@ using Reactor.Networking.Serialization;
namespace Reactor.Networking.Attributes; namespace Reactor.Networking.Attributes;
/// <summary>
/// Automatically registers a <see cref="MessageConverter{T}"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class MessageConverterAttribute : Attribute public sealed class MessageConverterAttribute : Attribute
{ {
private static readonly HashSet<Assembly> _registeredAssemblies = new(); 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) public static void Register(Assembly assembly)
{ {
if (_registeredAssemblies.Contains(assembly)) return; if (_registeredAssemblies.Contains(assembly)) return;
@@ -10,21 +10,49 @@ using Reactor.Utilities;
namespace Reactor.Networking.Attributes; namespace Reactor.Networking.Attributes;
/// <summary>
/// Automatically registers a method rpc.
/// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class MethodRpcAttribute : Attribute public sealed class MethodRpcAttribute : Attribute
{ {
private static readonly HashSet<Assembly> _registeredAssemblies = new(); private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Gets the id of the rpc.
/// </summary>
public uint Id { get; } 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; 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; } 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) public MethodRpcAttribute(uint id)
{ {
Id = 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) public static void Register(Assembly assembly, BasePlugin plugin)
{ {
if (_registeredAssemblies.Contains(assembly)) return; if (_registeredAssemblies.Contains(assembly)) return;
@@ -43,7 +71,7 @@ public sealed class MethodRpcAttribute : Attribute
try 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); PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
} }
catch (Exception e) catch (Exception e)
@@ -4,17 +4,27 @@ using System.Reflection;
namespace Reactor.Networking.Attributes; namespace Reactor.Networking.Attributes;
/// <summary>
/// Describes the <see cref="ModFlags"/> of the annotated plugin class.
/// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class ReactorModFlagsAttribute : Attribute public sealed class ReactorModFlagsAttribute : Attribute
{ {
/// <summary>
/// Gets flags of the mod.
/// </summary>
public ModFlags Flags { get; } 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) public ReactorModFlagsAttribute(ModFlags flags)
{ {
Flags = flags; Flags = flags;
} }
public static ModFlags GetModFlags(Type type) internal static ModFlags GetModFlags(Type type)
{ {
var attribute = type.GetCustomAttribute<ReactorModFlagsAttribute>(); var attribute = type.GetCustomAttribute<ReactorModFlagsAttribute>();
if (attribute != null) if (attribute != null)
@@ -9,25 +9,33 @@ using Reactor.Utilities;
namespace Reactor.Networking.Attributes; namespace Reactor.Networking.Attributes;
/// <summary> /// <summary>
/// Utility attribute for automatically registering CustomRpc /// Automatically registers a rpc.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class RegisterCustomRpcAttribute : Attribute public sealed class RegisterCustomRpcAttribute : Attribute
{ {
private static readonly HashSet<Assembly> _registeredAssemblies = new(); private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Gets the id of the rpc.
/// </summary>
public uint Id { get; } 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) public RegisterCustomRpcAttribute(uint id)
{ {
Id = id; Id = id;
} }
/// <summary> /// <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> /// </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="plugin"></param> /// <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) public static void Register(Assembly assembly, BasePlugin plugin)
{ {
if (_registeredAssemblies.Contains(assembly)) return; if (_registeredAssemblies.Contains(assembly)) return;
@@ -3,8 +3,17 @@ using InnerNet;
namespace Reactor.Networking.Extensions; namespace Reactor.Networking.Extensions;
/// <summary>
/// Provides extension methods for <see cref="InnerNetClient"/>.
/// </summary>
public static class ClientExtensions 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) public static void KickWithReason(this InnerNetClient innerNetClient, int targetClientId, string reason)
{ {
var writer = MessageWriter.Get(SendOption.Reliable); var writer = MessageWriter.Get(SendOption.Reliable);
@@ -22,6 +31,11 @@ public static class ClientExtensions
writer.Recycle(); 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) public static void DisconnectWithReason(this InnerNetClient innerNetClient, string reason)
{ {
innerNetClient.LastCustomDisconnect = reason; innerNetClient.LastCustomDisconnect = reason;
@@ -5,6 +5,9 @@ using UnityEngine;
namespace Reactor.Networking.Extensions; namespace Reactor.Networking.Extensions;
/// <summary>
/// Provides extension methods for <see cref="MessageWriter"/> and <see cref="MessageReader"/>.
/// </summary>
public static class ExtraMessageExtensions public static class ExtraMessageExtensions
{ {
private const float MIN = -50f; private const float MIN = -50f;
@@ -15,6 +18,11 @@ public static class ExtraMessageExtensions
return Mathf.Clamp((t - MIN) / (MAX - MIN), 0f, 1f); 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) public static void Write(this MessageWriter writer, Vector2 value)
{ {
var x = (ushort) (ReverseLerp(value.x) * ushort.MaxValue); var x = (ushort) (ReverseLerp(value.x) * ushort.MaxValue);
@@ -24,6 +32,11 @@ public static class ExtraMessageExtensions
writer.Write(y); 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) public static Vector2 ReadVector2(this MessageReader reader)
{ {
var x = reader.ReadUInt16() / (float) ushort.MaxValue; 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)); 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) public static void Send(this UdpConnection connection, MessageWriter msg, Action ackCallback)
{ {
if (msg.SendOption != SendOption.Reliable) if (msg.SendOption != SendOption.Reliable)
+27 -4
View File
@@ -11,13 +11,24 @@ using Reactor.Networking.Patches;
namespace Reactor.Networking; namespace Reactor.Networking;
/// <summary>
/// Represents currently loaded mods.
/// </summary>
public static class ModList public static class ModList
{ {
/// <summary>
/// Gets current mods.
/// </summary>
public static IReadOnlyCollection<Mod> Current { get; private set; } = null!; public static IReadOnlyCollection<Mod> Current { get; private set; } = null!;
private static readonly Dictionary<string, Mod> _modById = new(); private static readonly Dictionary<string, Mod> _modById = new();
private static readonly Dictionary<Type, Mod> _modByPluginType = 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) public static Mod GetById(string id)
{ {
return _modById[id]; return _modById[id];
@@ -42,6 +53,12 @@ public static class ModList
return _netIdByMod[mod]; 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) public static Mod ReadMod(this MessageReader reader)
{ {
var netId = reader.ReadPackedUInt32(); var netId = reader.ReadPackedUInt32();
@@ -54,16 +71,22 @@ public static class ModList
return GetById(id); 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 else
{ {
writer.WritePacked(0); writer.WritePacked(0);
writer.Write(mod.Id); writer.Write(value.Id);
} }
} }
@@ -3,10 +3,19 @@ using InnerNet;
namespace Reactor.Networking.Patches; namespace Reactor.Networking.Patches;
/// <summary>
/// Provides information about the reactor protocol state of the current connection.
/// </summary>
public class ReactorConnection public class ReactorConnection
{ {
/// <summary>
/// Gets the syncer.
/// </summary>
public Syncer? Syncer { get; internal set; } public Syncer? Syncer { get; internal set; }
/// <summary>
/// Gets the current instance of <see cref="ReactorConnection"/>.
/// </summary>
public static ReactorConnection? Instance { get; private set; } public static ReactorConnection? Instance { get; private set; }
[HarmonyPatch] [HarmonyPatch]
@@ -30,8 +39,18 @@ public class ReactorConnection
} }
} }
/// <summary>
/// Specifies who syncs the mod list and handles compatibility.
/// </summary>
public enum Syncer public enum Syncer
{ {
/// <summary>
/// A custom region server.
/// </summary>
Server, Server,
/// <summary>
/// The host of the game.
/// </summary>
Host, Host,
} }
+68 -8
View File
@@ -5,77 +5,137 @@ using InnerNet;
namespace Reactor.Networking.Rpc; 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 public abstract class CustomRpc<TPlugin, TInnerNetObject, TData> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
{ {
/// <inheritdoc />
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id) protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
{ {
} }
/// <inheritdoc cref="UnsafeCustomRpc.UnsafePlugin"/>
public TPlugin Plugin => (TPlugin) UnsafePlugin; public TPlugin Plugin => (TPlugin) UnsafePlugin;
/// <inheritdoc />
public override Type InnerNetObjectType => typeof(TInnerNetObject); public override Type InnerNetObjectType => typeof(TInnerNetObject);
/// <inheritdoc cref="UnsafeCustomRpc.UnsafeWrite" />
public abstract void Write(MessageWriter writer, TData? data); public abstract void Write(MessageWriter writer, TData? data);
/// <inheritdoc cref="UnsafeCustomRpc.UnsafeRead" />
public abstract TData Read(MessageReader reader); public abstract TData Read(MessageReader reader);
/// <inheritdoc cref="UnsafeCustomRpc.UnsafeHandle" />
public abstract void Handle(TInnerNetObject innerNetObject, TData? data); public abstract void Handle(TInnerNetObject innerNetObject, TData? data);
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object? data) public override void UnsafeWrite(MessageWriter writer, object? data)
{ {
Write(writer, (TData?) data); Write(writer, (TData?) data);
} }
/// <inheritdoc />
public override object? UnsafeRead(MessageReader reader) public override object? UnsafeRead(MessageReader reader)
{ {
return Read(reader); return Read(reader);
} }
/// <inheritdoc />
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data) public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
{ {
Handle((TInnerNetObject) innerNetObject, (TData?) 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 public abstract class CustomRpc<TPlugin, TInnerNetObject> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
{ {
/// <inheritdoc />
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id) protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
{ {
} }
/// <inheritdoc cref="UnsafeCustomRpc.UnsafePlugin"/>
public TPlugin Plugin => (TPlugin) UnsafePlugin; public TPlugin Plugin => (TPlugin) UnsafePlugin;
/// <inheritdoc />
public override Type InnerNetObjectType => typeof(TInnerNetObject); 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); public abstract void Handle(TInnerNetObject innerNetObject);
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object? data) public override void UnsafeWrite(MessageWriter writer, object? data)
{ {
} }
/// <inheritdoc />
public override object? UnsafeRead(MessageReader reader) public override object? UnsafeRead(MessageReader reader)
{ {
return null; return null;
} }
/// <inheritdoc />
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data) public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
{ {
Handle((TInnerNetObject) innerNetObject); 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; namespace Reactor.Networking.Rpc;
/// <summary>
/// Manages custom rpc's.
/// </summary>
public class CustomRpcManager public class CustomRpcManager
{ {
/// <summary>
/// Rpc call id for the reactor's custom rpc wrapper.
/// </summary>
public const byte CallId = byte.MaxValue; public const byte CallId = byte.MaxValue;
private readonly List<UnsafeCustomRpc> _list = new(); private readonly List<UnsafeCustomRpc> _list = new();
private readonly Dictionary<Type, Dictionary<Mod, Dictionary<uint, UnsafeCustomRpc>>> _map = 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 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; customRpc.Manager = this;
_list.Add(customRpc); _list.Add(customRpc);
@@ -31,10 +44,17 @@ public class CustomRpcManager
{ {
typeof(Rpc<>).MakeGenericType(customRpc.GetType()).GetProperty("Instance")!.SetValue(null, customRpc); 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) public static bool HandleRpc(InnerNetObject innerNetObject, byte callId, MessageReader reader)
{ {
if (callId == CallId) if (callId == CallId)
+48 -6
View File
@@ -15,10 +15,24 @@ using Reactor.Utilities;
namespace Reactor.Networking.Rpc; namespace Reactor.Networking.Rpc;
/// <summary>
/// Provides a custom rpc for method rpc.
/// </summary>
public class MethodRpc : UnsafeCustomRpc 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) public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately) : base(plugin, id)
{ {
Method = method; Method = method;
@@ -54,35 +68,58 @@ public class MethodRpc : UnsafeCustomRpc
InnerNetObjectType = method.DeclaringType; 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 MethodInfo Method { get; }
public HandleDelegate Handle { get; }
/// <inheritdoc />
protected internal override bool IsSingleton => false; protected internal override bool IsSingleton => false;
/// <inheritdoc />
public override Type InnerNetObjectType { get; } public override Type InnerNetObjectType { get; }
/// <inheritdoc />
public override RpcLocalHandling LocalHandling { get; } public override RpcLocalHandling LocalHandling { get; }
/// <inheritdoc />
public override SendOption SendOption { get; } public override SendOption SendOption { get; }
/// <summary>
/// Gets a value indicating whether the method rpc should be sent immediately.
/// </summary>
public bool SendImmediately { get; } public bool SendImmediately { get; }
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object? data) public override void UnsafeWrite(MessageWriter writer, object? data)
{ {
var args = (object[]) data!; var args = (object[]) data!;
MessageSerializer.Serialize(writer, args); MessageSerializer.Serialize(writer, args);
} }
/// <inheritdoc />
public override object UnsafeRead(MessageReader reader) 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; return args;
} }
/// <inheritdoc />
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data) public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
{ {
var args = (object[]) data!; var args = (object[]) data!;
var result = Handle(innerNetObject, args); var result = _handle(innerNetObject, args);
if (result is IEnumerator enumerator) 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) public void Send(InnerNetObject innerNetObject, object[] args)
{ {
UnsafeSend(innerNetObject, args, SendImmediately); UnsafeSend(innerNetObject, args, SendImmediately);
@@ -98,7 +140,7 @@ public class MethodRpc : UnsafeCustomRpc
private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send)); private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send));
/// <summary> /// <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> /// </summary>
private HandleDelegate Hook(MethodInfo method, ParameterInfo[] parameters, bool isStatic) 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; 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 public abstract class PlayerCustomRpc<TPlugin, TData> : CustomRpc<TPlugin, PlayerControl, TData> where TPlugin : BasePlugin
{ {
/// <inheritdoc />
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id) 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) public void Send(TData data, bool immediately = false, Action? ackCallback = null)
{ {
Send(PlayerControl.LocalPlayer, data, immediately, ackCallback); 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 public abstract class PlayerCustomRpc<TPlugin> : CustomRpc<TPlugin, PlayerControl> where TPlugin : BasePlugin
{ {
/// <inheritdoc />
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id) 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) public void Send(bool immediately = false, Action? ackCallback = null)
{ {
Send(PlayerControl.LocalPlayer, immediately, ackCallback); 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; 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 public static class Rpc<T> where T : UnsafeCustomRpc
{ {
private static T? _instance; private static T? _instance;
/// <summary>
/// Gets an instance of <typeparamref name="T"/> rpc.
/// </summary>
public static T Instance public static T Instance
{ {
get get
@@ -1,8 +1,22 @@
namespace Reactor.Networking.Rpc; namespace Reactor.Networking.Rpc;
/// <summary>
/// Specifies how the rpc should be handled locally.
/// </summary>
public enum RpcLocalHandling public enum RpcLocalHandling
{ {
/// <summary>
/// Don't do anything.
/// </summary>
None, None,
/// <summary>
/// Invoke Handle before sending.
/// </summary>
Before, Before,
/// <summary>
/// Invoke Handle after sending.
/// </summary>
After, After,
} }
+58
View File
@@ -11,20 +11,53 @@ using Buffer = Il2CppSystem.Buffer;
namespace Reactor.Networking.Rpc; namespace Reactor.Networking.Rpc;
/// <summary>
/// Base type for custom rpc's.
/// </summary>
public abstract class UnsafeCustomRpc public abstract class UnsafeCustomRpc
{ {
internal CustomRpcManager? Manager { get; set; } 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; protected internal virtual bool IsSingleton => true;
/// <summary>
/// Gets the id of the rpc.
/// </summary>
public uint Id { get; } public uint Id { get; }
/// <summary>
/// Gets the plugin of the rpc.
/// </summary>
public BasePlugin UnsafePlugin { get; } public BasePlugin UnsafePlugin { get; }
/// <summary>
/// Gets the mod of the rpc.
/// </summary>
public Mod Mod { get; } public Mod Mod { get; }
/// <summary>
/// Gets the InnerNetObject type the rpc targets.
/// </summary>
public abstract Type InnerNetObjectType { get; } public abstract Type InnerNetObjectType { get; }
/// <summary>
/// Gets the send option of the rpc.
/// </summary>
public virtual SendOption SendOption => SendOption.Reliable; public virtual SendOption SendOption => SendOption.Reliable;
/// <summary>
/// Gets the local handling method of the rpc.
/// </summary>
public abstract RpcLocalHandling LocalHandling { get; } 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) protected UnsafeCustomRpc(BasePlugin plugin, uint id)
{ {
UnsafePlugin = plugin; UnsafePlugin = plugin;
@@ -32,10 +65,35 @@ public abstract class UnsafeCustomRpc
Id = id; 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); 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); 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); 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) public void UnsafeSend(InnerNetObject innerNetObject, object? data, bool immediately = false, int targetClientId = -1, Action? ackCallback = null)
{ {
if (innerNetObject == null) throw new ArgumentNullException(nameof(innerNetObject)); if (innerNetObject == null) throw new ArgumentNullException(nameof(innerNetObject));
@@ -8,30 +8,41 @@ using Reactor.Networking.Attributes;
namespace Reactor.Networking.Serialization; namespace Reactor.Networking.Serialization;
/// <summary>
/// Provides a MessageConverter for <see cref="InnerNetObject"/>.
/// </summary>
[MessageConverter] [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 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); return value(reader);
} }
var @delegate = _readNetObject.MakeGenericMethod(t).CreateDelegate<Func<MessageReader, InnerNetObject>>(); var @delegate = _readNetObject.MakeGenericMethod(innerNetObjectType).CreateDelegate<Func<MessageReader, InnerNetObject>>();
_readNetObjectMap[t] = @delegate; _readNetObjectMap[innerNetObjectType] = @delegate;
return @delegate(reader); 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); return ReadNetObject(reader, objectType);
} }
@@ -3,26 +3,60 @@ using Hazel;
namespace Reactor.Networking.Serialization; namespace Reactor.Networking.Serialization;
/// <summary>
/// Base type for MessageConverter's.
/// </summary>
public abstract class UnsafeMessageConverter 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); 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); 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); 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 public abstract class MessageConverter<T> : UnsafeMessageConverter
{ {
/// <inheritdoc cref="UnsafeMessageConverter.UnsafeWrite"/>
public abstract void Write(MessageWriter writer, T value); 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); public abstract T Read(MessageReader reader, Type objectType);
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return typeof(T).IsAssignableFrom(objectType); return typeof(T).IsAssignableFrom(objectType);
} }
/// <inheritdoc />
public override void UnsafeWrite(MessageWriter writer, object value) => Write(writer, (T) value); public override void UnsafeWrite(MessageWriter writer, object value) => Write(writer, (T) value);
/// <inheritdoc />
public override object UnsafeRead(MessageReader reader, Type objectType) => Read(reader, objectType)!; public override object UnsafeRead(MessageReader reader, Type objectType) => Read(reader, objectType)!;
} }
@@ -3,23 +3,34 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Hazel; using Hazel;
using Reactor.Networking.Extensions; using Reactor.Networking.Extensions;
using Reactor.Networking.Rpc;
using UnityEngine; using UnityEngine;
namespace Reactor.Networking.Serialization; namespace Reactor.Networking.Serialization;
/// <summary>
/// Provides de/serializing of objects from/into <see cref="MessageReader"/>/<see cref="MessageWriter"/>.
/// </summary>
public static class MessageSerializer public static class MessageSerializer
{ {
private static List<UnsafeMessageConverter> MessageConverters { get; } = new(); private static List<UnsafeMessageConverter> MessageConverters { get; } = new();
private static Dictionary<Type, UnsafeMessageConverter?> MessageConverterMap { 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) public static void Register(UnsafeMessageConverter messageConverter)
{ {
MessageConverters.Add(messageConverter); MessageConverters.Add(messageConverter);
MessageConverterMap.Clear(); 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) public static UnsafeMessageConverter? FindConverter(Type type)
{ {
if (MessageConverterMap.TryGetValue(type, out var value)) if (MessageConverterMap.TryGetValue(type, out var value))
@@ -33,6 +44,11 @@ public static class MessageSerializer
return converter; 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) public static void Serialize(MessageWriter writer, object[] args)
{ {
foreach (var arg in 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: case int i:
writer.WritePacked(i); writer.WritePacked(i);
@@ -73,84 +94,76 @@ public static class MessageSerializer
writer.Write(i); writer.Write(i);
break; break;
default: default:
var converter = FindConverter(o.GetType()); var converter = FindConverter(@object.GetType());
if (converter != null) if (converter != null)
{ {
converter.UnsafeWrite(writer, o); converter.UnsafeWrite(writer, @object);
break; 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(); if (objectType == typeof(int))
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))
{ {
return reader.ReadPackedInt32(); return reader.ReadPackedInt32();
} }
if (t == typeof(uint)) if (objectType == typeof(uint))
{ {
return reader.ReadPackedUInt32(); return reader.ReadPackedUInt32();
} }
if (t == typeof(byte)) if (objectType == typeof(byte))
{ {
return reader.ReadByte(); return reader.ReadByte();
} }
if (t == typeof(float)) if (objectType == typeof(float))
{ {
return reader.ReadSingle(); return reader.ReadSingle();
} }
if (t == typeof(sbyte)) if (objectType == typeof(sbyte))
{ {
return reader.ReadSByte(); return reader.ReadSByte();
} }
if (t == typeof(ushort)) if (objectType == typeof(ushort))
{ {
return reader.ReadUInt16(); return reader.ReadUInt16();
} }
if (t == typeof(bool)) if (objectType == typeof(bool))
{ {
return reader.ReadBoolean(); return reader.ReadBoolean();
} }
if (t == typeof(Vector2)) if (objectType == typeof(Vector2))
{ {
return reader.ReadVector2(); return reader.ReadVector2();
} }
if (t == typeof(string)) if (objectType == typeof(string))
{ {
return reader.ReadString(); return reader.ReadString();
} }
var converter = FindConverter(t); var converter = FindConverter(objectType);
if (converter != null) 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; namespace Reactor.Patches.Fixes;
/// <summary> /// <summary>
/// "Fixes" an issue where empty TextBoxes have wrong cursor positions /// "Fixes" an issue where empty TextBoxes have wrong cursor positions.
/// </summary> /// </summary>
[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))] [HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))]
internal static class CursorPosPatch internal static class CursorPosPatch
@@ -17,9 +17,7 @@ internal static class ServerInfoFixesPatch
Info($"Joining {__instance.networkAddress}:{__instance.networkPort}"); Info($"Joining {__instance.networkAddress}:{__instance.networkPort}");
} }
/// <summary> // Fixes hardcoded port and filters out IPv6 in DnsRegionInfo.
/// Fixes hardcoded port and filters out IPv6 in DnsRegionInfo
/// </summary>
[HarmonyPatch(typeof(DnsRegionInfo), nameof(DnsRegionInfo.PopulateServers))] [HarmonyPatch(typeof(DnsRegionInfo), nameof(DnsRegionInfo.PopulateServers))]
[HarmonyPrefix] [HarmonyPrefix]
public static bool FixPopulateServers(DnsRegionInfo __instance) public static bool FixPopulateServers(DnsRegionInfo __instance)
@@ -6,7 +6,7 @@ using HarmonyLib;
namespace Reactor.Patches.Miscellaneous; namespace Reactor.Patches.Miscellaneous;
/// <summary> /// <summary>
/// Allows launching steam builds outside of steam /// Allows launching steam builds outside of steam.
/// </summary> /// </summary>
[HarmonyPatch] [HarmonyPatch]
internal static class StandaloneSteamPatch internal static class StandaloneSteamPatch
+13 -3
View File
@@ -10,13 +10,20 @@ using Version = SemanticVersioning.Version;
namespace Reactor.Patches; namespace Reactor.Patches;
/// <summary>
/// Shows the reactor version on the main menu.
/// </summary>
public static class ReactorVersionShower public static class ReactorVersionShower
{ {
/// <summary>
/// Gets the <see cref="TextMeshPro"/> text.
/// </summary>
public static TextMeshPro? Text { get; private set; } public static TextMeshPro? Text { get; private set; }
public static event TextUpdatedHandler? TextUpdated; /// <summary>
/// Occurs when <see cref="Text"/> is updated.
public delegate void TextUpdatedHandler(TextMeshPro text); /// </summary>
public static event Action<TextMeshPro>? TextUpdated;
internal static void Initialize() 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}")}"; 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() public static void UpdateText()
{ {
if (Text == null) return; if (Text == null) return;
+1
View File
@@ -5,6 +5,7 @@
<Description>Core mod and API for Among Us</Description> <Description>Core mod and API for Among Us</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- SourceLink --> <!-- SourceLink -->
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
+14 -1
View File
@@ -18,17 +18,28 @@ using UnityEngine.SceneManagement;
namespace Reactor; namespace Reactor;
/// <summary>
/// Reactor's main class.
/// </summary>
[BepInAutoPlugin("gg.reactor.api")] [BepInAutoPlugin("gg.reactor.api")]
[BepInProcess("Among Us.exe")] [BepInProcess("Among Us.exe")]
public partial class ReactorPlugin : BasePlugin public partial class ReactorPlugin : BasePlugin
{ {
/// <summary>
/// Gets harmony instance.
/// </summary>
public Harmony Harmony { get; } = new(Id); public Harmony Harmony { get; } = new(Id);
/// <summary>
/// Gets custom rpc manager.
/// </summary>
public CustomRpcManager CustomRpcManager { get; } = new(); public CustomRpcManager CustomRpcManager { get; } = new();
public ConfigEntry<bool>? AllowVanillaServers { get; private set; } internal ConfigEntry<bool>? AllowVanillaServers { get; private set; }
internal RegionInfoWatcher RegionInfoWatcher { get; } = new(); internal RegionInfoWatcher RegionInfoWatcher { get; } = new();
/// <inheritdoc />
public ReactorPlugin() public ReactorPlugin()
{ {
PluginSingleton<ReactorPlugin>.Instance = this; PluginSingleton<ReactorPlugin>.Instance = this;
@@ -40,6 +51,7 @@ public partial class ReactorPlugin : BasePlugin
MethodRpcAttribute.Initialize(); MethodRpcAttribute.Initialize();
} }
/// <inheritdoc />
public override void Load() 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!"); 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() public override bool Unload()
{ {
Harmony.UnpatchSelf(); Harmony.UnpatchSelf();
+43
View File
@@ -10,10 +10,19 @@ using UnityEngine;
namespace Reactor.Utilities; namespace Reactor.Utilities;
/// <summary>
/// Provides a standard way of loading asset bundles from a file or embedded resource.
/// </summary>
public static class AssetBundleManager public static class AssetBundleManager
{ {
/// <summary>
/// Extension of a asset bundle.
/// </summary>
public const string BundleExtension = ".bundle"; public const string BundleExtension = ".bundle";
/// <summary>
/// Gets the target name of the current system.
/// </summary>
public static string TargetName { get; } public static string TargetName { get; }
static AssetBundleManager() 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}"; public static string GetFileName(string assetBundleName) => $"{assetBundleName}-{TargetName}{BundleExtension}";
private static bool TryFindFile(Assembly assembly, string fileName, [NotNullWhen(true)] out string? path) private static bool TryFindFile(Assembly assembly, string fileName, [NotNullWhen(true)] out string? path)
@@ -85,11 +99,24 @@ public static class AssetBundleManager
return false; 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) public static AssetBundle Load(string name)
{ {
return Load(Assembly.GetCallingAssembly(), 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) public static AssetBundle Load(Assembly assembly, string name)
{ {
var fileName = GetFileName(name); var fileName = GetFileName(name);
@@ -107,11 +134,24 @@ public static class AssetBundleManager
throw new AssetBundleNotFoundException(name); 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) public static AssetBundleCreateRequest LoadAsync(string name)
{ {
return LoadAsync(Assembly.GetCallingAssembly(), 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) public static AssetBundleCreateRequest LoadAsync(Assembly assembly, string name)
{ {
var fileName = GetFileName(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 public class AssetBundleNotFoundException : IOException
{ {
internal AssetBundleNotFoundException(string name) : base("Couldn't find an assetbundle named " + name) internal AssetBundleNotFoundException(string name) : base("Couldn't find an assetbundle named " + name)
@@ -8,20 +8,30 @@ using Il2CppInterop.Runtime.Injection;
namespace Reactor.Utilities.Attributes; namespace Reactor.Utilities.Attributes;
/// <summary> /// <summary>
/// Utility attribute for automatically calling <see cref="ClassInjector.RegisterTypeInIl2Cpp{T}()"/> /// Automatically registers an il2cpp type using <see cref="ClassInjector.RegisterTypeInIl2Cpp{T}()"/>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class RegisterInIl2CppAttribute : Attribute public sealed class RegisterInIl2CppAttribute : Attribute
{ {
private static readonly HashSet<Assembly> _registeredAssemblies = new(); private static readonly HashSet<Assembly> _registeredAssemblies = new();
/// <summary>
/// Gets il2cpp interfaces to be injected with this type.
/// </summary>
public Type[] Interfaces { get; } public Type[] Interfaces { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterInIl2CppAttribute"/> class without any interfaces.
/// </summary>
public RegisterInIl2CppAttribute() public RegisterInIl2CppAttribute()
{ {
Interfaces = Type.EmptyTypes; 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) public RegisterInIl2CppAttribute(params Type[] interfaces)
{ {
Interfaces = interfaces; Interfaces = interfaces;
@@ -51,9 +61,10 @@ public sealed class RegisterInIl2CppAttribute : Attribute
} }
/// <summary> /// <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> /// </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) public static void Register(Assembly assembly)
{ {
if (_registeredAssemblies.Contains(assembly)) return; if (_registeredAssemblies.Contains(assembly)) return;
+15 -1
View File
@@ -5,8 +5,15 @@ using UnityEngine.UI;
namespace Reactor.Utilities; namespace Reactor.Utilities;
/// <summary>
/// Provides utilities for working with unity canvas ui.
/// </summary>
public static class CanvasUtilities public static class CanvasUtilities
{ {
/// <summary>
/// Creates a canvas.
/// </summary>
/// <returns>A canvas <see cref="GameObject"/>.</returns>
public static GameObject CreateCanvas() public static GameObject CreateCanvas()
{ {
var gameObject = new GameObject("Canvas"); var gameObject = new GameObject("Canvas");
@@ -23,6 +30,10 @@ public static class CanvasUtilities
return gameObject; return gameObject;
} }
/// <summary>
/// Creates an EventSystem.
/// </summary>
/// <returns>An EventSystem <see cref="GameObject"/>.</returns>
public static GameObject CreateEventSystem() public static GameObject CreateEventSystem()
{ {
var gameObject = new GameObject("EventSystem"); var gameObject = new GameObject("EventSystem");
@@ -34,8 +45,11 @@ public static class CanvasUtilities
} }
/// <summary> /// <summary>
/// Shortcut for empty texture /// Creates an empty texture of specified <paramref name="width"/> and <paramref name="height"/>.
/// </summary> /// </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) public static Texture2D CreateEmptyTexture(int width = 0, int height = 0)
{ {
return new Texture2D(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero); 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; namespace Reactor.Utilities;
/// <summary>
/// Provides utilities for starting coroutines.
/// </summary>
public static class Coroutines public static class Coroutines
{ {
[RegisterInIl2Cpp] [RegisterInIl2Cpp]
@@ -33,20 +36,29 @@ public static class Coroutines
private static readonly ConditionalWeakTable<IEnumerator, Coroutine> _ourCoroutineStore = new(); private static readonly ConditionalWeakTable<IEnumerator, Coroutine> _ourCoroutineStore = new();
[return: NotNullIfNotNull("routine")] /// <summary>
public static IEnumerator? Start(IEnumerator? routine) /// 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); Component.Instance!.StopCoroutine(routine);
} }
+6
View File
@@ -4,8 +4,14 @@ using UnityEngine.UI;
namespace Reactor.Utilities; namespace Reactor.Utilities;
/// <summary>
/// Provides unity things that were stripped.
/// </summary>
public static class DefaultBundle public static class DefaultBundle
{ {
/// <summary>
/// Gets standard resources used for canvas ui.
/// </summary>
public static DefaultControls.Resources StandardResources { get; internal set; } = null!; public static DefaultControls.Resources StandardResources { get; internal set; } = null!;
internal static void Load() internal static void Load()
+10
View File
@@ -6,9 +6,15 @@ using UnityEngine;
namespace Reactor.Utilities; namespace Reactor.Utilities;
/// <summary>
/// Dispatches actions on Unity main thread.
/// </summary>
[RegisterInIl2Cpp] [RegisterInIl2Cpp]
public sealed class Dispatcher : MonoBehaviour public sealed class Dispatcher : MonoBehaviour
{ {
/// <summary>
/// Gets the instance.
/// </summary>
public static Dispatcher Instance { get; private set; } = null!; public static Dispatcher Instance { get; private set; } = null!;
private readonly Queue<Action> _queue = new(); 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] [HideFromIl2Cpp]
public void Enqueue(Action action) public void Enqueue(Action action)
{ {
@@ -3,10 +3,20 @@ using UnityEngine;
namespace Reactor.Utilities.Extensions; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for <see cref="AssetBundle"/>.
/// </summary>
public static class AssetBundleExtensions 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; namespace Reactor.Utilities.Extensions;
/// <summary> /// <summary>
/// GUI utilities /// Provides extension methods for unity imgui.
/// </summary> /// </summary>
public static class GUIExtensions public static class GUIExtensions
{ {
/// <summary> /// <summary>
/// Clamp Rect to screen size /// Clamps <paramref name="rect"/> to the screen size.
/// </summary> /// </summary>
/// <param name="rect">The <see cref="Rect"/> to clamp.</param>
/// <returns>A clamped rect.</returns>
public static Rect ClampScreen(this Rect rect) public static Rect ClampScreen(this Rect rect)
{ {
rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width); rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width);
@@ -19,8 +21,10 @@ public static class GUIExtensions
} }
/// <summary> /// <summary>
/// Reset Rect size /// Set <paramref name="rect"/>'s <see cref="Rect.width"/> and <see cref="Rect.height"/> to 0.
/// </summary> /// </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) public static Rect ResetSize(this Rect rect)
{ {
rect.width = rect.height = 0; rect.width = rect.height = 0;
@@ -29,13 +33,23 @@ public static class GUIExtensions
} }
/// <summary> /// <summary>
/// Create <see cref="Sprite"/> from <paramref name="tex"/> /// Creates a <see cref="Sprite"/> from the <paramref name="texture"/>.
/// </summary> /// </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) public static void SetSize(this RectTransform rectTransform, float width, float height)
{ {
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width); rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
@@ -3,8 +3,17 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays;
namespace Reactor.Utilities.Extensions; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for Il2CppInterop.
/// </summary>
public static class Il2CppInteropExtensions 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 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); 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; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides Random related extension methods.
/// </summary>
public static class RandomExtensions public static class RandomExtensions
{ {
/// <summary> /// <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> /// </summary>
public static T? Random<T>(this IEnumerable<T> input, Random random) /// <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>
var list = input as IList<T> ?? input.ToList(); /// <param name="maxValue">The maximum value of the range.</param>
return list.Count == 0 ? default : list[random.Next(0, list.Count)]; /// <returns>A random double that is within a specified range.</returns>
}
/// <summary>Returns a random double that is within a specified range.</summary>
public static double NextDouble(this Random random, double minValue, double maxValue) public static double NextDouble(this Random random, double minValue, double maxValue)
{ {
return random.NextDouble() * (maxValue - minValue) + minValue; return random.NextDouble() * (maxValue - minValue) + minValue;
} }
/// <summary> /// <summary>
/// Returns random <typeparamref name="T"/> from <paramref name="input"/> /// Returns a random <typeparamref name="T"/> from the <paramref name="input"/>.
/// </summary> /// </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) public static T? Random<T>(this IEnumerable<T> input)
{ {
var list = input as IList<T> ?? input.ToList(); var list = input as IList<T> ?? input.ToList();
return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)]; 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; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for reflection.
/// </summary>
public static class ReflectionExtensions 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) public static Il2CppMethodInfo ToIl2CppMethodInfo(this MethodInfo methodInfo)
{ {
var il2CppMethodField = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(methodInfo); var il2CppMethodField = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(methodInfo);
if (il2CppMethodField == null) throw new ArgumentException($"'{methodInfo.Name}' is not an il2cpp method", nameof(methodInfo)); if (il2CppMethodField == null) throw new ArgumentException($"'{methodInfo.Name}' is not an il2cpp method", nameof(methodInfo));
var il2CppMethod = (IntPtr) il2CppMethodField.GetValue(null)!; 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) public static Il2CppSystemType GetEnumeratorMoveNextType(this Il2CppMethodInfo methodInfo)
{ {
var customAttribute = Il2CppCustomAttributeExtensions.GetCustomAttribute(methodInfo, Il2CppType.Of<IteratorStateMachineAttribute>()).TryCast<IteratorStateMachineAttribute>(); var customAttribute = Il2CppCustomAttributeExtensions.GetCustomAttribute(methodInfo, Il2CppType.Of<IteratorStateMachineAttribute>()).TryCast<IteratorStateMachineAttribute>();
@@ -33,6 +46,11 @@ public static class ReflectionExtensions
return customAttribute._StateMachineType_k__BackingField; 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) public static Type ToSystemType(this Il2CppSystemType type)
{ {
var result = Type.GetType(type.AssemblyQualifiedName); 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); 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) public static MethodInfo EnumeratorMoveNext(Type type, string methodName)
{ {
return AccessTools.Method(AccessTools.Method(type, methodName).ToIl2CppMethodInfo().GetEnumeratorMoveNextType().ToSystemType(), "MoveNext"); 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) public static IEnumerable<MethodBase> GetMethods(this Type type, Type returnType, params Type[] parameterTypes)
{ {
return type.GetMethods(AccessTools.all, returnType, parameterTypes); return type.GetMethods(AccessTools.all, returnType, parameterTypes);
@@ -3,13 +3,17 @@ using System.Diagnostics.CodeAnalysis;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.SceneManagement; 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> /// <summary>
/// Adds a delegate to get one notification when a scene has loaded. /// Adds a delegate to get one notification when a scene has loaded.
/// </summary> /// </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")] [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) public static void once_sceneLoaded(Action<Scene, LoadSceneMode> value)
{ {
@@ -8,13 +8,20 @@ using Reactor.Utilities.Attributes;
namespace Reactor.Utilities.Extensions; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for <see cref="Stream"/>.
/// </summary>
public static class StreamExtensions public static class StreamExtensions
{ {
/// <summary>
/// Provides a <see cref="Il2CppSystem.IO.Stream"/> which uses <see cref="System.IO.Stream"/> under the hood.
/// </summary>
[RegisterInIl2Cpp] [RegisterInIl2Cpp]
public class StreamWrapper : Il2CppSystem.IO.Stream public class StreamWrapper : Il2CppSystem.IO.Stream
{ {
private readonly Stream _stream; private readonly Stream _stream;
/// <inheritdoc />
public StreamWrapper(Stream stream) : base(ClassInjector.DerivedConstructorPointer<StreamWrapper>()) public StreamWrapper(Stream stream) : base(ClassInjector.DerivedConstructorPointer<StreamWrapper>())
{ {
ClassInjector.DerivedConstructorBody(this); ClassInjector.DerivedConstructorBody(this);
@@ -29,31 +36,37 @@ public static class StreamExtensions
return new Span<byte>(rawBuffer + offset, count); return new Span<byte>(rawBuffer + offset, count);
} }
/// <inheritdoc />
public override int Read(Il2CppStructArray<byte> buffer, int offset, int count) public override int Read(Il2CppStructArray<byte> buffer, int offset, int count)
{ {
return _stream.Read(GetSpan(buffer, offset, count)); return _stream.Read(GetSpan(buffer, offset, count));
} }
/// <inheritdoc />
public override void Write(Il2CppStructArray<byte> buffer, int offset, int count) public override void Write(Il2CppStructArray<byte> buffer, int offset, int count)
{ {
_stream.Write(GetSpan(buffer, offset, count)); _stream.Write(GetSpan(buffer, offset, count));
} }
/// <inheritdoc />
public override void Flush() public override void Flush()
{ {
_stream.Flush(); _stream.Flush();
} }
/// <inheritdoc />
public override void Close() public override void Close()
{ {
_stream.Close(); _stream.Close();
} }
/// <inheritdoc />
public override void Dispose() public override void Dispose()
{ {
_stream.Dispose(); _stream.Dispose();
} }
/// <inheritdoc />
public override long Seek(long offset, Il2CppSystem.IO.SeekOrigin origin) public override long Seek(long offset, Il2CppSystem.IO.SeekOrigin origin)
{ {
return _stream.Seek(offset, origin switch return _stream.Seek(offset, origin switch
@@ -65,16 +78,25 @@ public static class StreamExtensions
}); });
} }
/// <inheritdoc />
public override void SetLength(long value) public override void SetLength(long value)
{ {
_stream.SetLength(value); _stream.SetLength(value);
} }
/// <inheritdoc />
public override bool CanRead => _stream.CanRead; public override bool CanRead => _stream.CanRead;
/// <inheritdoc />
public override bool CanSeek => _stream.CanSeek; public override bool CanSeek => _stream.CanSeek;
/// <inheritdoc />
public override bool CanWrite => _stream.CanWrite; public override bool CanWrite => _stream.CanWrite;
/// <inheritdoc />
public override long Length => _stream.Length; public override long Length => _stream.Length;
/// <inheritdoc />
public override long Position public override long Position
{ {
get => _stream.Position; get => _stream.Position;
@@ -83,13 +105,17 @@ public static class StreamExtensions
} }
/// <summary> /// <summary>
/// Wraps a System Stream into an Il2Cpp Stream /// Wraps a <see cref="System.IO.Stream"/> into a <see cref="Il2CppSystem.IO.Stream"/>.
/// </summary> /// </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); public static StreamWrapper AsIl2Cpp(this Stream stream) => new(stream);
/// <summary> /// <summary>
/// Fully read <paramref name="input"/> stream, can be used as workaround for il2cpp streams. /// Fully reads the <paramref name="input"/> stream.
/// </summary> /// </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) public static byte[] ReadFully(this Stream input)
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
@@ -2,6 +2,9 @@ using UnityEngine;
namespace Reactor.Utilities.Extensions; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for unity.
/// </summary>
public static class UnityExtensions public static class UnityExtensions
{ {
/// <summary> /// <summary>
@@ -9,7 +12,7 @@ public static class UnityExtensions
/// </summary> /// </summary>
/// <param name="color">The color to be converted.</param> /// <param name="color">The color to be converted.</param>
/// <returns>Hexadecimal string representing the color.</returns> /// <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) public static string ToHtmlStringRGBA(this Color32 color)
{ {
return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}"; 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"); private static readonly int _addColor = Shader.PropertyToID("_AddColor");
/// <summary> /// <summary>
/// Sets color outline for renderers using default Among Us shader /// Sets the outline for renderer using the default Among Us shader.
/// </summary> /// </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) public static void SetOutline(this Renderer renderer, Color? color)
{ {
renderer.material.SetFloat(_outline, color.HasValue ? 1 : 0); renderer.material.SetFloat(_outline, color.HasValue ? 1 : 0);
@@ -1,42 +1,67 @@
using UnityEngine; using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Reactor.Utilities.Extensions; namespace Reactor.Utilities.Extensions;
/// <summary>
/// Provides extension methods for <see cref="UnityObject"/>.
/// </summary>
public static class UnityObjectExtensions public static class UnityObjectExtensions
{ {
/// <summary> /// <summary>
/// Stops <paramref name="obj"/> from being destroyed /// Stops <paramref name="obj"/> from being destroyed.
/// </summary> /// </summary>
/// <param name="obj">Object to stop from being destroyed</param> /// <param name="obj">The object to stop from being destroyed.</param>
/// <returns>Passed <paramref name="obj"/></returns> /// <typeparam name="T">The type of the object.</typeparam>
public static T DontDestroy<T>(this T obj) where T : Object /// <returns>Passed <paramref name="obj"/>.</returns>
public static T DontDestroy<T>(this T obj) where T : UnityObject
{ {
obj.hideFlags |= HideFlags.HideAndDontSave; obj.hideFlags |= HideFlags.HideAndDontSave;
return obj.DontDestroyOnLoad(); 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; obj.hideFlags |= HideFlags.DontUnloadUnusedAsset;
return obj; 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; 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; namespace Reactor.Utilities;
/// <inheritdoc />
public sealed class Il2CppEqualityComparer<T> : IEqualityComparer<T> where T : Il2CppSystem.Object public sealed class Il2CppEqualityComparer<T> : IEqualityComparer<T> where T : Il2CppSystem.Object
{ {
private static Il2CppEqualityComparer<T>? _instance; private static Il2CppEqualityComparer<T>? _instance;
/// <summary>
/// Gets the instance.
/// </summary>
public static Il2CppEqualityComparer<T> Instance public static Il2CppEqualityComparer<T> Instance
{ {
get get
+7 -2
View File
@@ -4,20 +4,25 @@ using UnityEngine;
namespace Reactor.Utilities.ImGui; namespace Reactor.Utilities.ImGui;
/// <summary>
/// Draggable version of <see cref="Window"/>.
/// </summary>
public class DragWindow : Window public class DragWindow : Window
{ {
/// <inheritdoc />
public DragWindow(Rect rect, string title, Action<int> func) : base(rect, title, func) public DragWindow(Rect rect, string title, Action<int> func) : base(rect, title, func)
{ {
Func = id => Func = id =>
{ {
func(id); func(id);
UnityEngine.GUI.DragWindow(new Rect(0, 0, 10000, 20)); GUI.DragWindow(new Rect(0, 0, 10000, 20));
Rect = Rect.ClampScreen(); 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; 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 public class Window
{ {
private static int _lastWindowId; private static int _lastWindowId;
/// <summary>
/// Gets the next window id.
/// </summary>
/// <returns>A window id.</returns>
public static int NextWindowId() public static int NextWindowId()
{ {
return _lastWindowId++; return _lastWindowId++;
} }
/// <summary>
/// Gets or sets the id of the window.
/// </summary>
public int Id { get; set; } = NextWindowId(); 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; public bool Enabled { get; set; } = true;
/// <summary>
/// Gets or sets the rect of the window.
/// </summary>
public Rect Rect { get; set; } public Rect Rect { get; set; }
/// <summary>
/// Gets or sets the render function of the window.
/// </summary>
public Action<int> Func { get; set; } public Action<int> Func { get; set; }
/// <summary>
/// Gets or sets the title of the window.
/// </summary>
public string Title { get; set; } 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) public Window(Rect rect, string title, Action<int> func)
{ {
Rect = rect; Rect = rect;
@@ -27,11 +58,15 @@ public class Window
Func = func; 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) if (Enabled)
{ {
+7
View File
@@ -5,8 +5,15 @@ using BepInEx.Unity.IL2CPP;
namespace Reactor.Utilities; 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 public static class Logger<T> where T : BasePlugin
{ {
/// <summary>
/// Gets the instance.
/// </summary>
public static ManualLogSource Instance => PluginSingleton<T>.Instance.Log; public static ManualLogSource Instance => PluginSingleton<T>.Instance.Log;
/// <inheritdoc cref="ManualLogSource.Log(BepInEx.Logging.LogLevel,object)"/> /// <inheritdoc cref="ManualLogSource.Log(BepInEx.Logging.LogLevel,object)"/>
+7
View File
@@ -5,10 +5,17 @@ using BepInEx.Unity.IL2CPP;
namespace Reactor.Utilities; 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 public static class PluginSingleton<T> where T : BasePlugin
{ {
private static T? _instance; private static T? _instance;
/// <summary>
/// Gets or sets an instance of <typeparamref name="T"/> plugin.
/// </summary>
public static T Instance public static T Instance
{ {
get => _instance ??= IL2CPPChainloader.Instance.Plugins.Values.Select(x => x.Instance).OfType<T>().Single(); get => _instance ??= IL2CPPChainloader.Instance.Plugins.Values.Select(x => x.Instance).OfType<T>().Single();
+17 -3
View File
@@ -10,9 +10,9 @@
}, },
"BepInEx.AutoPlugin": { "BepInEx.AutoPlugin": {
"type": "Direct", "type": "Direct",
"requested": "[1.0.1, )", "requested": "[1.1.0, )",
"resolved": "1.0.1", "resolved": "1.1.0",
"contentHash": "G4YpTHG9rtkDJof+ld7Er4E+sCOlgsjG4bJ1itx1ub7dgff6qqHLy9ksX1ehgO0AdBOPVyVl/GKJg9o7iNl8HA==" "contentHash": "/7Nmc1EA9lYlGBiyz6qPAaHvlcJHEjFZ1RFpGX+8EBj3GrgoX5kbpnZusuSHoOlLx0l+CnzKuQNE54B8UaGPyw=="
}, },
"BepInEx.IL2CPP.MSBuild": { "BepInEx.IL2CPP.MSBuild": {
"type": "Direct", "type": "Direct",
@@ -47,6 +47,15 @@
"Microsoft.SourceLink.Common": "1.1.1" "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": { "AssetRipper.VersionUtilities": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.2.1", "resolved": "1.2.1",
@@ -323,6 +332,11 @@
"resolved": "2.0.2", "resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA==" "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": { "System.Collections": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.3.0", "resolved": "4.3.0",