mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
Merge branch 'custom-rpc-api'
# Conflicts: # Reactor/Extensions/Extensions.cs # Reactor/Patches/SteamPatch.cs
This commit is contained in:
19 files changed
+615
-87
No files matched your search
+12
-3
@@ -1,11 +1,20 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup Condition="'$(GamePlatform)' == 'Steam'">
|
||||||
<GameVersion>2020.12.9s</GameVersion>
|
<GameVersion>2020.12.9s</GameVersion>
|
||||||
<Mappings>NuclearPowered/Mappings:0.1.0-alpha.2</Mappings>
|
<DefineConstants>$(DefineConstants);STEAM</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(GamePlatform)' == 'Itch'">
|
||||||
|
<GameVersion>2020.11.17i</GameVersion>
|
||||||
|
<DefineConstants>$(DefineConstants);ITCH</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Mappings>NuclearPowered/Mappings:0.1.0</Mappings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Reactor.OxygenFilter.MSBuild" Version="0.2.3" />
|
<PackageReference Include="Reactor.OxygenFilter.MSBuild" Version="0.2.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="Copy" AfterTargets="Reobfuscate">
|
<Target Name="Copy" AfterTargets="Reobfuscate">
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
||||||
<Version>0.1.0</Version>
|
<Version>0.1.0</Version>
|
||||||
<Description>Debugging mod for Reactor</Description>
|
<Description>Debugging mod for Reactor</Description>
|
||||||
<Authors>NuclearPowered</Authors>
|
<Authors>NuclearPowered</Authors>
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using BepInEx;
|
||||||
|
using BepInEx.IL2CPP;
|
||||||
|
using Reactor.Extensions;
|
||||||
|
using UnhollowerBaseLib.Attributes;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Reactor.Example
|
||||||
|
{
|
||||||
|
[BepInPlugin(Id)]
|
||||||
|
[BepInProcess("Among Us.exe")]
|
||||||
|
[BepInDependency(ReactorPlugin.Id)]
|
||||||
|
public class ExamplePlugin : BasePlugin
|
||||||
|
{
|
||||||
|
public const string Id = "gg.reactor.Example";
|
||||||
|
|
||||||
|
public override void Load()
|
||||||
|
{
|
||||||
|
RegisterInIl2CppAttribute.Register();
|
||||||
|
RegisterCustomRpcAttribute.Register(this);
|
||||||
|
|
||||||
|
var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy();
|
||||||
|
gameObject.AddComponent<ExampleComponent>().Plugin = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RegisterInIl2Cpp]
|
||||||
|
public class ExampleComponent : MonoBehaviour
|
||||||
|
{
|
||||||
|
[HideFromIl2Cpp]
|
||||||
|
public ExamplePlugin Plugin { get; internal set; }
|
||||||
|
|
||||||
|
public ExampleComponent(IntPtr ptr) : base(ptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(KeyCode.F3))
|
||||||
|
{
|
||||||
|
Plugin.Log.LogWarning("Sending example rpc");
|
||||||
|
PlayerControl.LocalPlayer.Send<ExampleRpc>(new ExampleRpc.Data("Cześć :)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Hazel;
|
||||||
|
|
||||||
|
namespace Reactor.Example
|
||||||
|
{
|
||||||
|
[RegisterCustomRpc]
|
||||||
|
public class ExampleRpc : CustomRpc<ExamplePlugin, PlayerControl, ExampleRpc.Data>
|
||||||
|
{
|
||||||
|
public ExampleRpc(ExamplePlugin plugin) : base(plugin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct Data
|
||||||
|
{
|
||||||
|
public readonly string Message;
|
||||||
|
|
||||||
|
public Data(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;
|
||||||
|
|
||||||
|
public override void Write(MessageWriter writer, Data data)
|
||||||
|
{
|
||||||
|
writer.Write(data.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Data Read(MessageReader reader)
|
||||||
|
{
|
||||||
|
return new Data(reader.ReadString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Handle(PlayerControl innerNetObject, Data data)
|
||||||
|
{
|
||||||
|
Plugin.Log.LogWarning($"{innerNetObject.Data.PlayerId} sent \"{data.Message}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
||||||
|
<Version>0.1.0</Version>
|
||||||
|
<Description>Example mod using Reactor</Description>
|
||||||
|
<Authors>NuclearPowered</Authors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Reactor\Reactor.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="../AmongUs.props" />
|
||||||
|
</Project>
|
||||||
+6
-2
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "build\Build.csproj
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E2F4DAFA-E98C-4A7D-BA47-AAB0271A769D}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E2F4DAFA-E98C-4A7D-BA47-AAB0271A769D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Example", "Reactor.Example\Reactor.Example.csproj", "{26A83466-26FE-4383-A35C-B8629902F909}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -22,9 +24,11 @@ Global
|
|||||||
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.Build.0 = Release|Any CPU
|
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{26A83466-26FE-4383-A35C-B8629902F909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{26A83466-26FE-4383-A35C-B8629902F909}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{26A83466-26FE-4383-A35C-B8629902F909}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{26A83466-26FE-4383-A35C-B8629902F909}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2} = {E2F4DAFA-E98C-4A7D-BA47-AAB0271A769D}
|
{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2} = {E2F4DAFA-E98C-4A7D-BA47-AAB0271A769D}
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using BepInEx;
|
||||||
|
using BepInEx.IL2CPP;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Hazel;
|
||||||
|
using Reactor.Net;
|
||||||
|
|
||||||
|
namespace Reactor
|
||||||
|
{
|
||||||
|
public enum RpcLocalHandling
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Before,
|
||||||
|
After
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class UnsafeCustomRpc
|
||||||
|
{
|
||||||
|
public int? Id { get; internal set; }
|
||||||
|
|
||||||
|
public BasePlugin UnsafePlugin { get; }
|
||||||
|
public string PluginId { get; }
|
||||||
|
|
||||||
|
public abstract Type InnerNetObjectType { get; }
|
||||||
|
|
||||||
|
public abstract RpcLocalHandling LocalHandling { get; }
|
||||||
|
|
||||||
|
protected UnsafeCustomRpc(BasePlugin plugin)
|
||||||
|
{
|
||||||
|
UnsafePlugin = plugin;
|
||||||
|
PluginId = MetadataHelper.GetMetadata(plugin).GUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void UnsafeWrite(MessageWriter writer, object data);
|
||||||
|
public abstract object UnsafeRead(MessageReader reader);
|
||||||
|
public abstract void UnsafeHandle(InnerNetObject innerNetObject, object data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class CustomRpc<TPlugin, TInnerNetObject, TData> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
|
||||||
|
{
|
||||||
|
protected CustomRpc(TPlugin plugin) : base(plugin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TPlugin Plugin => (TPlugin) UnsafePlugin;
|
||||||
|
public override Type InnerNetObjectType => typeof(TInnerNetObject);
|
||||||
|
|
||||||
|
public abstract void Write(MessageWriter writer, TData data);
|
||||||
|
public abstract TData Read(MessageReader reader);
|
||||||
|
public abstract void Handle(TInnerNetObject innerNetObject, TData data);
|
||||||
|
|
||||||
|
public override void UnsafeWrite(MessageWriter writer, object data)
|
||||||
|
{
|
||||||
|
Write(writer, (TData) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object UnsafeRead(MessageReader reader)
|
||||||
|
{
|
||||||
|
return Read(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UnsafeHandle(InnerNetObject innerNetObject, object data)
|
||||||
|
{
|
||||||
|
Handle((TInnerNetObject) innerNetObject, (TData) data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomRpcManager
|
||||||
|
{
|
||||||
|
public const byte CallId = byte.MaxValue;
|
||||||
|
|
||||||
|
private readonly List<UnsafeCustomRpc> _list = new List<UnsafeCustomRpc>();
|
||||||
|
private readonly Dictionary<Type, List<UnsafeCustomRpc>> _map = new Dictionary<Type, List<UnsafeCustomRpc>>();
|
||||||
|
|
||||||
|
public IReadOnlyList<UnsafeCustomRpc> List => _list.AsReadOnly();
|
||||||
|
|
||||||
|
public ILookup<Type, UnsafeCustomRpc> Map =>
|
||||||
|
_map.SelectMany(pair => pair.Value, (pair, value) => new { pair.Key, Value = value })
|
||||||
|
.ToLookup(pair => pair.Key, pair => pair.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, int> PluginIdMap { get; private set; }
|
||||||
|
public Dictionary<int, string> PluginIdMapReversed { get; private set; }
|
||||||
|
|
||||||
|
public CustomRpcManager()
|
||||||
|
{
|
||||||
|
foreach (var type in HandleRpcPatch.InnerNetObjectTypes)
|
||||||
|
{
|
||||||
|
_map[type] = new List<UnsafeCustomRpc>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadPluginIdMap()
|
||||||
|
{
|
||||||
|
PluginIdMap = new Dictionary<string, int>();
|
||||||
|
PluginIdMapReversed = new Dictionary<int, string>();
|
||||||
|
|
||||||
|
var i = -1;
|
||||||
|
|
||||||
|
foreach (var mod in ModList.GetCurrent().OrderBy(x => x.Id))
|
||||||
|
{
|
||||||
|
if (mod.Side == PluginSide.Both)
|
||||||
|
{
|
||||||
|
PluginIdMap[mod.Id] = i;
|
||||||
|
PluginIdMapReversed[i] = mod.Id;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsafeCustomRpc Register(UnsafeCustomRpc customRpc)
|
||||||
|
{
|
||||||
|
customRpc.Id = List.Count(x => x.UnsafePlugin == customRpc.UnsafePlugin);
|
||||||
|
_list.Add(customRpc);
|
||||||
|
_map[customRpc.InnerNetObjectType].Add(customRpc);
|
||||||
|
|
||||||
|
return customRpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send<TCustomRpc>(InnerNetObject netObject, object data, bool immediately = false)
|
||||||
|
{
|
||||||
|
UnsafeSend(netObject, List.Single(x => x.GetType() == typeof(TCustomRpc)), data, immediately);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsafeSend(InnerNetObject netObject, UnsafeCustomRpc customRpc, object data, bool immediately = false)
|
||||||
|
{
|
||||||
|
if (netObject == null) throw new ArgumentNullException(nameof(netObject));
|
||||||
|
if (customRpc == null) throw new ArgumentNullException(nameof(customRpc));
|
||||||
|
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||||
|
|
||||||
|
if (customRpc.Id == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Can't send unregistered CustomRpc");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customRpc.LocalHandling == RpcLocalHandling.Before)
|
||||||
|
{
|
||||||
|
customRpc.UnsafeHandle(netObject, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer = immediately switch
|
||||||
|
{
|
||||||
|
false => AmongUsClient.Instance.StartRpc(netObject.NetId, CallId, SendOption.Reliable),
|
||||||
|
true => AmongUsClient.Instance.StartRpcImmediately(netObject.NetId, CallId, SendOption.Reliable, -1)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (PluginIdMap.TryGetValue(customRpc.PluginId, out var pluginId))
|
||||||
|
{
|
||||||
|
writer.WritePacked(pluginId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(customRpc.PluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WritePacked(customRpc.Id!.Value);
|
||||||
|
customRpc.UnsafeWrite(writer, data);
|
||||||
|
|
||||||
|
if (immediately)
|
||||||
|
{
|
||||||
|
AmongUsClient.Instance.FinishRpcImmediately(writer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.EndMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customRpc.LocalHandling == RpcLocalHandling.After)
|
||||||
|
{
|
||||||
|
customRpc.UnsafeHandle(netObject, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch]
|
||||||
|
private static class HandleRpcPatch
|
||||||
|
{
|
||||||
|
internal static List<Type> InnerNetObjectTypes { get; } = typeof(InnerNetObject).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(InnerNetObject)) && x != typeof(LobbyBehaviour)).ToList();
|
||||||
|
|
||||||
|
public static IEnumerable<MethodBase> TargetMethods()
|
||||||
|
{
|
||||||
|
return InnerNetObjectTypes.Select(x => x.GetMethod(nameof(InnerNetObject.HandleRpc), AccessTools.all));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadString(MessageReader reader, int len)
|
||||||
|
{
|
||||||
|
if (reader.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {reader.BytesRemaining}");
|
||||||
|
|
||||||
|
var output = Encoding.UTF8.GetString(reader.Buffer, reader.readHead, len);
|
||||||
|
reader.Position += len;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(InnerNetObject __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader)
|
||||||
|
{
|
||||||
|
if (callId == CallId)
|
||||||
|
{
|
||||||
|
var manager = PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager;
|
||||||
|
var customRpcs = manager.Map[__instance.GetType()];
|
||||||
|
|
||||||
|
var lengthOrShortId = reader.ReadPackedInt32();
|
||||||
|
|
||||||
|
var pluginId = lengthOrShortId < 0
|
||||||
|
? manager.PluginIdMapReversed[lengthOrShortId]
|
||||||
|
: ReadString(reader, lengthOrShortId);
|
||||||
|
|
||||||
|
var id = reader.ReadPackedInt32();
|
||||||
|
|
||||||
|
var customRpc = customRpcs.Single(x => x.PluginId == pluginId && x.Id == id);
|
||||||
|
|
||||||
|
customRpc.UnsafeHandle(__instance, customRpc.UnsafeRead(reader));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -129,5 +129,10 @@ namespace Reactor.Extensions
|
|||||||
{
|
{
|
||||||
return type.GetMethods(AccessTools.all).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes));
|
return type.GetMethods(AccessTools.all).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Send<TCustomRpc>(this InnerNetObject netObject, object data, bool immediately = false)
|
||||||
|
{
|
||||||
|
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Send<TCustomRpc>(netObject, data, immediately);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace Reactor.Net
|
||||||
|
{
|
||||||
|
public readonly struct Mod
|
||||||
|
{
|
||||||
|
public readonly string Id;
|
||||||
|
public readonly string Version;
|
||||||
|
public readonly PluginSide Side;
|
||||||
|
|
||||||
|
public Mod(string id, string version, PluginSide side)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Version = version;
|
||||||
|
Side = side;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Id} ({Version})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using BepInEx.IL2CPP;
|
||||||
|
using Hazel;
|
||||||
|
|
||||||
|
namespace Reactor.Net
|
||||||
|
{
|
||||||
|
public static class ModList
|
||||||
|
{
|
||||||
|
public static void Serialize(MessageWriter writer, ISet<Mod> mods)
|
||||||
|
{
|
||||||
|
writer.WritePacked(mods.Count);
|
||||||
|
|
||||||
|
foreach (var mod in mods)
|
||||||
|
{
|
||||||
|
writer.Write(mod.Id);
|
||||||
|
writer.Write(mod.Version);
|
||||||
|
writer.Write((byte) mod.Side);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ISet<Mod> GetCurrent()
|
||||||
|
{
|
||||||
|
return IL2CPPChainloader.Instance.Plugins
|
||||||
|
.Select(plugin => new Mod(
|
||||||
|
plugin.Key,
|
||||||
|
plugin.Value.Metadata.Version.ToString(),
|
||||||
|
plugin.Value.Instance.GetType().GetCustomAttribute<ReactorPluginSideAttribute>()?.Side ?? PluginSide.Both
|
||||||
|
))
|
||||||
|
.ToHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Hazel;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
|
||||||
|
namespace Reactor.Net
|
||||||
|
{
|
||||||
|
public static class ModdedHandshakeC2S
|
||||||
|
{
|
||||||
|
public static void Serialize(MessageWriter writer, Il2CppStructArray<byte> clientVersion, string name, ISet<Mod> mods = null)
|
||||||
|
{
|
||||||
|
writer.Write(clientVersion);
|
||||||
|
writer.Write(name);
|
||||||
|
|
||||||
|
if (mods != null)
|
||||||
|
{
|
||||||
|
ModList.Serialize(writer, mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Hazel;
|
||||||
|
|
||||||
|
namespace Reactor.Net
|
||||||
|
{
|
||||||
|
public static class ModdedHandshakeS2C
|
||||||
|
{
|
||||||
|
public static void Serialize(MessageWriter writer, string serverBrand)
|
||||||
|
{
|
||||||
|
writer.StartMessage(byte.MaxValue);
|
||||||
|
writer.Write(serverBrand);
|
||||||
|
writer.EndMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using BepInEx.IL2CPP;
|
|
||||||
using HarmonyLib;
|
|
||||||
using Hazel;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
|
|
||||||
namespace Reactor.Patches
|
|
||||||
{
|
|
||||||
internal static class ModdedHandshake
|
|
||||||
{
|
|
||||||
// TODO add back when vent issue is fixed
|
|
||||||
// [HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))]
|
|
||||||
// public static class FreeplayPatch
|
|
||||||
// {
|
|
||||||
// public static void Prefix([HarmonyArgument(0)] NewConnectionEventArgs evt)
|
|
||||||
// {
|
|
||||||
// var handshakeData = evt.HandshakeData;
|
|
||||||
//
|
|
||||||
// var protocolVersion = handshakeData.ReadInt32();
|
|
||||||
// if (protocolVersion != -1)
|
|
||||||
// {
|
|
||||||
// // reset position if vanilla
|
|
||||||
// handshakeData._position -= sizeof(int);
|
|
||||||
// handshakeData.readHead -= sizeof(int);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// workaround for ^
|
|
||||||
[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.IsCompatibleVersion))]
|
|
||||||
public static class FreeplayPatch
|
|
||||||
{
|
|
||||||
public static bool Prefix([HarmonyArgument(0)] int version, ref bool __result)
|
|
||||||
{
|
|
||||||
if (version == -1)
|
|
||||||
{
|
|
||||||
__result = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.GetConnectionData))]
|
|
||||||
public static class HandshakePatch
|
|
||||||
{
|
|
||||||
public static bool Prefix(out Il2CppStructArray<byte> __result)
|
|
||||||
{
|
|
||||||
var handshake = MessageWriter.Get(SendOption.Reliable);
|
|
||||||
|
|
||||||
var plugins = IL2CPPChainloader.Instance.Plugins;
|
|
||||||
|
|
||||||
if (plugins.Values
|
|
||||||
.Select(pluginInfo => pluginInfo.Instance.GetType().GetCustomAttribute<ReactorPluginSideAttribute>())
|
|
||||||
.Any(attribute => attribute == null || attribute.Side == PluginSide.Both))
|
|
||||||
{
|
|
||||||
handshake.Write(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
handshake.Write(Constants.GetBroadcastVersion());
|
|
||||||
handshake.Write(SaveManager.PlayerName);
|
|
||||||
|
|
||||||
handshake.WritePacked(plugins.Count);
|
|
||||||
|
|
||||||
foreach (var plugin in plugins)
|
|
||||||
{
|
|
||||||
handshake.Write(plugin.Key);
|
|
||||||
handshake.Write(plugin.Value.Metadata.Version.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
__result = handshake.ToByteArray(false);
|
|
||||||
handshake.Recycle();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Hazel;
|
||||||
|
using Hazel.Udp;
|
||||||
|
using Reactor.Extensions;
|
||||||
|
using Reactor.Net;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Reactor.Patches
|
||||||
|
{
|
||||||
|
internal static class ModdedHandshakePatches
|
||||||
|
{
|
||||||
|
// [HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))]
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class ServerPatch
|
||||||
|
{
|
||||||
|
public static IEnumerable<MethodBase> TargetMethods()
|
||||||
|
{
|
||||||
|
return typeof(InnerNetServer).GetMethods(typeof(void), typeof(NewConnectionEventArgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string ServerBrand = $"Among Us {Application.version}";
|
||||||
|
|
||||||
|
public static void Postfix([HarmonyArgument(0)] NewConnectionEventArgs evt)
|
||||||
|
{
|
||||||
|
var writer = MessageWriter.Get(SendOption.Reliable);
|
||||||
|
ModdedHandshakeS2C.Serialize(writer, ServerBrand);
|
||||||
|
evt.Connection.Send(writer);
|
||||||
|
writer.Recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleMessage))]
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class HandleMessagePatch
|
||||||
|
{
|
||||||
|
public static IEnumerable<MethodBase> TargetMethods()
|
||||||
|
{
|
||||||
|
return typeof(InnerNetClient).GetMethods(typeof(void), typeof(MessageReader), typeof(SendOption));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader reader, [HarmonyArgument(1)] SendOption sendOption)
|
||||||
|
{
|
||||||
|
if (reader.Tag == byte.MaxValue)
|
||||||
|
{
|
||||||
|
var serverBrand = reader.ReadString();
|
||||||
|
|
||||||
|
__instance.connection.State = ConnectionState.Connected;
|
||||||
|
|
||||||
|
PluginSingleton<ReactorPlugin>.Instance.Log.LogDebug($"Connected to modded server ({serverBrand})");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(UdpConnection), nameof(UdpConnection.HandleSend))]
|
||||||
|
public static class SendHelloPatch
|
||||||
|
{
|
||||||
|
public static void Prefix(UdpConnection __instance, byte sendOption, ref Il2CppSystem.Action ackCallback)
|
||||||
|
{
|
||||||
|
if (sendOption != 8 || AmongUsClient.Instance == null || AmongUsClient.Instance.connection == null || !AmongUsClient.Instance.connection.Equals(__instance))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PluginSingleton<ReactorPlugin>.Instance.ModdedHandshake.Value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ackCallback = (Action) (() =>
|
||||||
|
{
|
||||||
|
PluginSingleton<ReactorPlugin>.Instance.Log.LogDebug("Hello was acked, waiting for modded handshake response");
|
||||||
|
|
||||||
|
Coroutines.Start(Coroutine());
|
||||||
|
|
||||||
|
__instance.add_Disconnected((Action<Il2CppSystem.Object, DisconnectedEventArgs>) ((_, _) =>
|
||||||
|
{
|
||||||
|
Coroutines.Stop(Coroutine());
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerator Coroutine()
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(3);
|
||||||
|
|
||||||
|
var client = AmongUsClient.Instance;
|
||||||
|
|
||||||
|
if (client != null && client.connection != null && client.connection.State == ConnectionState.Connecting)
|
||||||
|
{
|
||||||
|
client.LastDisconnectReason = DisconnectReasons.Custom;
|
||||||
|
client.LastCustomDisconnect = "Server didn't respond to modded handshake";
|
||||||
|
client.HandleDisconnect(client.LastDisconnectReason, client.LastCustomDisconnect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.GetConnectionData))]
|
||||||
|
public static class HandshakePatch
|
||||||
|
{
|
||||||
|
public static bool Prefix(out Il2CppStructArray<byte> __result)
|
||||||
|
{
|
||||||
|
var handshake = MessageWriter.Get(SendOption.Reliable);
|
||||||
|
|
||||||
|
ModdedHandshakeC2S.Serialize(
|
||||||
|
handshake,
|
||||||
|
Constants.GetBroadcastVersionBytes(),
|
||||||
|
SaveManager.PlayerName,
|
||||||
|
ModList.GetCurrent()
|
||||||
|
);
|
||||||
|
|
||||||
|
__result = handshake.ToByteArray(false);
|
||||||
|
handshake.Recycle();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#if STEAM
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using Steamworks;
|
using Steamworks;
|
||||||
@@ -23,3 +24,4 @@ namespace Reactor.Patches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
|
using BepInEx.Configuration;
|
||||||
using BepInEx.IL2CPP;
|
using BepInEx.IL2CPP;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using Reactor.Extensions;
|
using Reactor.Extensions;
|
||||||
@@ -19,12 +20,18 @@ namespace Reactor
|
|||||||
public const string Id = "gg.reactor.api";
|
public const string Id = "gg.reactor.api";
|
||||||
|
|
||||||
public Harmony Harmony { get; } = new Harmony(Id);
|
public Harmony Harmony { get; } = new Harmony(Id);
|
||||||
|
public CustomRpcManager CustomRpcManager { get; } = new CustomRpcManager();
|
||||||
|
|
||||||
|
public ConfigEntry<bool> ModdedHandshake;
|
||||||
|
|
||||||
private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher();
|
private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher();
|
||||||
|
|
||||||
public override void Load()
|
public override void Load()
|
||||||
{
|
{
|
||||||
RegisterInIl2CppAttribute.Register();
|
RegisterInIl2CppAttribute.Register();
|
||||||
|
|
||||||
|
ModdedHandshake = Config.Bind("Features", "Modded handshake", true);
|
||||||
|
|
||||||
var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy();
|
var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy();
|
||||||
gameObject.AddComponent<ReactorComponent>().Plugin = this;
|
gameObject.AddComponent<ReactorComponent>().Plugin = this;
|
||||||
|
|
||||||
@@ -52,6 +59,8 @@ namespace Reactor
|
|||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
Plugin.CustomRpcManager.ReloadPluginIdMap();
|
||||||
|
|
||||||
Camera.onPostRender = Camera.onPostRender == null
|
Camera.onPostRender = Camera.onPostRender == null
|
||||||
? new Action<Camera>(OnPostRenderM)
|
? new Action<Camera>(OnPostRenderM)
|
||||||
: Il2CppSystem.Delegate.Combine(Camera.onPostRender, Il2CppSystem.Delegate.CreateDelegate(GetIl2CppType(), GetIl2CppType().GetMethod(nameof(OnPostRenderM), Il2CppSystem.Reflection.BindingFlags.Static | Il2CppSystem.Reflection.BindingFlags.Public))).Cast<Camera.CameraCallback>();
|
: Il2CppSystem.Delegate.Combine(Camera.onPostRender, Il2CppSystem.Delegate.CreateDelegate(GetIl2CppType(), GetIl2CppType().GetMethod(nameof(OnPostRenderM), Il2CppSystem.Reflection.BindingFlags.Static | Il2CppSystem.Reflection.BindingFlags.Public))).Cast<Camera.CameraCallback>();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Reactor
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plugin side used in modded handshake
|
/// Plugin side used in modded handshake
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum PluginSide
|
public enum PluginSide : byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required by both sides, reject connection if missing on the other side
|
/// Required by both sides, reject connection if missing on the other side
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using BepInEx.IL2CPP;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Reactor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility attribute for automatically registering CustomRpc
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class RegisterCustomRpcAttribute : Attribute
|
||||||
|
{
|
||||||
|
public static void Register(BasePlugin plugin)
|
||||||
|
{
|
||||||
|
Register(Assembly.GetCallingAssembly(), plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Register(Assembly assembly, BasePlugin plugin)
|
||||||
|
{
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
var attribute = type.GetCustomAttribute<RegisterCustomRpcAttribute>();
|
||||||
|
|
||||||
|
if (attribute != null)
|
||||||
|
{
|
||||||
|
if (!type.IsSubclassOf(typeof(UnsafeCustomRpc)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(RegisterCustomRpcAttribute)} but doesn't extend {nameof(UnsafeCustomRpc)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var customRpc = (UnsafeCustomRpc) Activator.CreateInstance(type, plugin);
|
||||||
|
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,6 +122,7 @@ public sealed class BuildTask : FrostingTask<BuildContext>
|
|||||||
Configuration = "Release",
|
Configuration = "Release",
|
||||||
EnvironmentVariables =
|
EnvironmentVariables =
|
||||||
{
|
{
|
||||||
|
["GamePlatform"] = "Steam",
|
||||||
["AmongUs"] = Path.GetFullPath(context.AmongUsPath)
|
["AmongUs"] = Path.GetFullPath(context.AmongUsPath)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user