From a0f2dd863c185523c2c4af1cca5f34e9d8893732 Mon Sep 17 00:00:00 2001 From: linmeideli <2558527373@qq.com> Date: Wed, 26 Aug 2026 22:34:41 +0800 Subject: [PATCH 1/7] support amci --- AmongUs.props | 2 +- Reactor/Networking/AmciMods.cs | 130 ++++++++++++++++++ .../Attributes/AmciIgnoreAttribute.cs | 28 ++++ .../Attributes/AmciModGuidAttribute.cs | 40 ++++++ Reactor/Networking/Patches/HttpPatches.cs | 3 +- Reactor/ReactorPlugin.cs | 1 + Reactor/packages.lock.json | 6 +- 7 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 Reactor/Networking/AmciMods.cs create mode 100644 Reactor/Networking/Attributes/AmciIgnoreAttribute.cs create mode 100644 Reactor/Networking/Attributes/AmciModGuidAttribute.cs diff --git a/AmongUs.props b/AmongUs.props index 5c6e8c42..a03ad691 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -1,7 +1,7 @@ - + diff --git a/Reactor/Networking/AmciMods.cs b/Reactor/Networking/AmciMods.cs new file mode 100644 index 00000000..6b98ee58 --- /dev/null +++ b/Reactor/Networking/AmciMods.cs @@ -0,0 +1,130 @@ +#pragma warning disable CA5350 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using BepInEx; +using BepInEx.Unity.IL2CPP; +using Reactor.Networking.Attributes; + +namespace Reactor.Networking; + +/// +/// Tracks the AMCI mod registrations of the loaded plugins and drives the vanilla +/// integration. +/// +public static class AmciMods +{ + private static readonly Dictionary _guidByModId = new(); + + /// + /// Gets the loaded mod ids and their registered AMCI GUIDs. + /// + public static IReadOnlyDictionary Registered => _guidByModId; + + /// + /// Gets the calculated active AMCI GUID (Single mod GUID or deterministic Composite GUID for multiple mods), + /// or null when no loaded mod registered one. + /// + public static Guid? Primary { get; private set; } + + /// + /// Gets a value indicating whether AMCI is currently active: at least one mod GUID is registered. + /// + public static bool IsEnabled => Primary != null; + + /// + /// Applies the current AMCI state to the vanilla client. + /// + public static void Apply() + { + var primary = Primary; + CurrentModRegistration.ModRegistrationGuidString = IsEnabled && primary != null ? primary.Value.ToString() : string.Empty; + } + + internal static void Initialize() + { + foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) + { + if (pluginInfo.Instance != null) + { + Register(pluginInfo); + } + } + + IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, _, _) => Register(pluginInfo); + + IL2CPPChainloader.Instance.Finished += () => + { + RefreshPrimary(); + Apply(); + }; + } + + private static void Register(PluginInfo pluginInfo) + { + if (pluginInfo.Instance == null) + { + return; + } + + var pluginType = pluginInfo.Instance.GetType(); + if (AmciIgnoreAttribute.IsIgnored(pluginType)) + { + return; + } + + var guid = AmciModGuidAttribute.GetGuid(pluginType); + if (guid.HasValue) + { + _guidByModId[pluginInfo.Metadata.GUID] = guid.Value; + } + } + + private static void RefreshPrimary() + { + if (_guidByModId.Count == 0) + { + Primary = null; + } + else if (_guidByModId.Count == 1) + { + Primary = _guidByModId.Values.First(); + } + else + { + Primary = ComputeCompositeGuid(_guidByModId.Values); + } + } + + private static Guid ComputeCompositeGuid(IEnumerable guids) + { + var sortedGuids = guids + .Distinct() + .OrderBy(g => g.ToString(), StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (sortedGuids.Length == 1) + { + return sortedGuids[0]; + } + + var buffer = new byte[sortedGuids.Length * 16]; + for (var i = 0; i < sortedGuids.Length; i++) + { + var bytes = sortedGuids[i].ToByteArray(); + Buffer.BlockCopy(bytes, 0, buffer, i * 16, 16); + } + + var hash = SHA1.HashData(buffer); + var guidBytes = new byte[16]; + Array.Copy(hash, 0, guidBytes, 0, 16); + + // Conform to RFC 4122 version 4 + guidBytes[6] = (byte) ((guidBytes[6] & 0x0F) | 0x40); // version 4 + guidBytes[8] = (byte) ((guidBytes[8] & 0x3F) | 0x80); // variant RFC 4122 + + return new Guid(guidBytes); + } +} diff --git a/Reactor/Networking/Attributes/AmciIgnoreAttribute.cs b/Reactor/Networking/Attributes/AmciIgnoreAttribute.cs new file mode 100644 index 00000000..cfc4486b --- /dev/null +++ b/Reactor/Networking/Attributes/AmciIgnoreAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Reactor.Networking.Attributes; + +/// +/// Marks a plugin to be ignored during AMCI mod GUID registration and composite calculation. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class AmciIgnoreAttribute : Attribute +{ + internal static bool IsIgnored(Type type) + { + if (type.GetCustomAttribute() != null) + { + return true; + } + + var metadata = type.Assembly.GetCustomAttributes().SingleOrDefault(x => x.Key == "Reactor.AmciIgnore"); + if (metadata is { Value: not null } && bool.TryParse(metadata.Value, out var ignored)) + { + return ignored; + } + + return false; + } +} diff --git a/Reactor/Networking/Attributes/AmciModGuidAttribute.cs b/Reactor/Networking/Attributes/AmciModGuidAttribute.cs new file mode 100644 index 00000000..67028796 --- /dev/null +++ b/Reactor/Networking/Attributes/AmciModGuidAttribute.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Reactor.Networking.Attributes; + +/// +/// Registers the annotated plugin with the official AMCI protocol. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class AmciModGuidAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// The self-assigned v4 mod GUID of the mod. + public AmciModGuidAttribute(string guid) + { + Guid = Guid.Parse(guid); + } + + public Guid Guid { get; } + + internal static Guid? GetGuid(Type type) + { + var attribute = type.GetCustomAttribute(); + if (attribute != null) + { + return attribute.Guid; + } + + var metadataAttribute = type.Assembly.GetCustomAttributes().SingleOrDefault(x => x.Key == "Reactor.AmciModGuid"); + if (metadataAttribute is { Value: not null } && Guid.TryParse(metadataAttribute.Value, out var guid)) + { + return guid; + } + + return null; + } +} diff --git a/Reactor/Networking/Patches/HttpPatches.cs b/Reactor/Networking/Patches/HttpPatches.cs index 41769221..727de519 100644 --- a/Reactor/Networking/Patches/HttpPatches.cs +++ b/Reactor/Networking/Patches/HttpPatches.cs @@ -92,7 +92,7 @@ public static void Postfix(UnityWebRequest __instance, UnityWebRequestAsyncOpera if (__instance.GetMethod() == UnityWebRequest.UnityWebRequestMethod.Get) { - if (responseHeader == null && ModList.IsAnyModRequiredOnAllClients) + if (responseHeader == null && !AmciMods.IsEnabled && ModList.IsAnyModRequiredOnAllClients) { HandshakePopup.Show(); } @@ -110,6 +110,7 @@ private static class GameStartManagerPatch public static void Postfix(GameStartManager __instance) { if (AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) return; + if (AmciMods.IsEnabled) return; if (ModList.IsAnyModRequiredOnAllClients && !IsCurrentRegionModded()) { Warning("Vanilla region, locking public toggle"); diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index df518441..ce83ca6a 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -47,6 +47,7 @@ public ReactorPlugin() RegisterInIl2CppAttribute.Initialize(); ModList.Initialize(); + AmciMods.Initialize(); RegisterCustomRpcAttribute.Initialize(); MessageConverterAttribute.Initialize(); diff --git a/Reactor/packages.lock.json b/Reactor/packages.lock.json index bf551dd7..9d9e41e2 100644 --- a/Reactor/packages.lock.json +++ b/Reactor/packages.lock.json @@ -4,9 +4,9 @@ "net6.0": { "AmongUs.GameLibs.Steam": { "type": "Direct", - "requested": "[2025.4.15, )", - "resolved": "2025.4.15", - "contentHash": "R3RxzHGZOGUJ9nMwrP0cjS6g+sh60QUU506Mo47lh17155enUjH6PtErijihSoU7t3aQpzCIyC67W3XrrHhH4g==" + "requested": "[2026.8.18, 2026.8.18]", + "resolved": "2026.8.18", + "contentHash": "sSGu2zGYvLboOc83PzPxQ0yGsK1YrhIO/538Tfzc/5FKfEbGCtCQErcKV0wdiYZCHGpCe561RzzfrBtp46jcgQ==" }, "BepInEx.AutoPlugin": { "type": "Direct", From 3d65f0f02d36e6b394c9e3db7cbbc664a558a0c5 Mon Sep 17 00:00:00 2001 From: linmeideli <2558527373@qq.com> Date: Wed, 26 Aug 2026 22:36:33 +0800 Subject: [PATCH 2/7] fix --- AmongUs.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AmongUs.props b/AmongUs.props index a03ad691..fd95d956 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -1,7 +1,7 @@ - + From 69060d27b2de9f4d7b19c7c454178825c0e97ad8 Mon Sep 17 00:00:00 2001 From: linmeideli <2558527373@qq.com> Date: Thu, 27 Aug 2026 01:15:04 +0800 Subject: [PATCH 3/7] fix lost codes --- Reactor/Networking/AmciMods.cs | 19 ++++++++++++++----- Reactor/packages.lock.json | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Reactor/Networking/AmciMods.cs b/Reactor/Networking/AmciMods.cs index 6b98ee58..48795b3d 100644 --- a/Reactor/Networking/AmciMods.cs +++ b/Reactor/Networking/AmciMods.cs @@ -49,27 +49,36 @@ internal static void Initialize() { if (pluginInfo.Instance != null) { - Register(pluginInfo); + Register(pluginInfo, (BasePlugin) pluginInfo.Instance); } } - IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, _, _) => Register(pluginInfo); + IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, _, plugin) => Register(pluginInfo, plugin); IL2CPPChainloader.Instance.Finished += () => { + foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values) + { + if (pluginInfo.Instance != null) + { + Register(pluginInfo, (BasePlugin) pluginInfo.Instance); + } + } + RefreshPrimary(); Apply(); + Info($"AMCI initialized: registered={_guidByModId.Count} activeGuid={Primary} CurrentModRegistration={CurrentModRegistration.ModRegistrationGuidString}"); }; } - private static void Register(PluginInfo pluginInfo) + private static void Register(PluginInfo pluginInfo, BasePlugin plugin) { - if (pluginInfo.Instance == null) + if (plugin == null) { return; } - var pluginType = pluginInfo.Instance.GetType(); + var pluginType = plugin.GetType(); if (AmciIgnoreAttribute.IsIgnored(pluginType)) { return; diff --git a/Reactor/packages.lock.json b/Reactor/packages.lock.json index 9d9e41e2..005a8779 100644 --- a/Reactor/packages.lock.json +++ b/Reactor/packages.lock.json @@ -4,7 +4,7 @@ "net6.0": { "AmongUs.GameLibs.Steam": { "type": "Direct", - "requested": "[2026.8.18, 2026.8.18]", + "requested": "[2026.8.18, )", "resolved": "2026.8.18", "contentHash": "sSGu2zGYvLboOc83PzPxQ0yGsK1YrhIO/538Tfzc/5FKfEbGCtCQErcKV0wdiYZCHGpCe561RzzfrBtp46jcgQ==" }, From eda83f5146e7d564276025e6970db5e95be9326b Mon Sep 17 00:00:00 2001 From: Fang-Kuai <145177672+FangkuaiYa@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:33:25 +0800 Subject: [PATCH 4/7] Fix filter show all roms (#1) --- .../Patches/ModRegistrationPatch.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Reactor/Networking/Patches/ModRegistrationPatch.cs diff --git a/Reactor/Networking/Patches/ModRegistrationPatch.cs b/Reactor/Networking/Patches/ModRegistrationPatch.cs new file mode 100644 index 00000000..13cda938 --- /dev/null +++ b/Reactor/Networking/Patches/ModRegistrationPatch.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AmongUs.Matchmaking; +using HarmonyLib; + +namespace Reactor.Networking.Patches +{ + internal class ModRegistrationPatch + { + [HarmonyPatch(typeof(CurrentModRegistration), nameof(CurrentModRegistration.UpdateFilterSetWithModRegistrationSettings))] + public static class CurrentModRegistrationPatch + { + public static void Prefix(GameFilterSet filterSet) + { + for (int i = filterSet.Filters.Count - 1; i >= 0; i--) + { + if (filterSet.Filters[i].Key == "mod") + { + filterSet.Filters.RemoveAt(i); + } + } + } + } + } +} From fc495e297de0966d97cb5eed79e1c9d801eb5d55 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Thu, 27 Aug 2026 12:25:10 +0800 Subject: [PATCH 5/7] Fix the issue where LocalGame cannot be used after registering the mod. --- Reactor/Networking/Patches/ModRegistrationPatch.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Reactor/Networking/Patches/ModRegistrationPatch.cs b/Reactor/Networking/Patches/ModRegistrationPatch.cs index 13cda938..e2dddfc9 100644 --- a/Reactor/Networking/Patches/ModRegistrationPatch.cs +++ b/Reactor/Networking/Patches/ModRegistrationPatch.cs @@ -24,5 +24,18 @@ public static void Prefix(GameFilterSet filterSet) } } } + + [HarmonyPatch(typeof(CurrentModRegistration), nameof(CurrentModRegistration.TryGetModRegistrationGuid))] + public static class LocalGamePatch + { + public static void Postfix(ref bool __result) + { + if (__result && AmongUsClient.Instance != null + && AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) + { + __result = false; + } + } + } } } From f4e444c85c0fd2f0468379e39f23bc0654fbb591 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Thu, 27 Aug 2026 14:59:54 +0800 Subject: [PATCH 6/7] Fix filter --- .../Patches/ModRegistrationPatch.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Reactor/Networking/Patches/ModRegistrationPatch.cs b/Reactor/Networking/Patches/ModRegistrationPatch.cs index e2dddfc9..e87129b6 100644 --- a/Reactor/Networking/Patches/ModRegistrationPatch.cs +++ b/Reactor/Networking/Patches/ModRegistrationPatch.cs @@ -3,8 +3,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using AmongUs.GameOptions; using AmongUs.Matchmaking; using HarmonyLib; +using InnerNet; namespace Reactor.Networking.Patches { @@ -25,15 +27,27 @@ public static void Prefix(GameFilterSet filterSet) } } - [HarmonyPatch(typeof(CurrentModRegistration), nameof(CurrentModRegistration.TryGetModRegistrationGuid))] + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HostGame), typeof(IGameOptions), typeof(GameFilterOptions))] public static class LocalGamePatch { - public static void Postfix(ref bool __result) + private static string _savedGuid; + + public static void Prefix() { - if (__result && AmongUsClient.Instance != null + if (AmongUsClient.Instance != null && AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) { - __result = false; + _savedGuid = CurrentModRegistration.ModRegistrationGuidString; + CurrentModRegistration.ModRegistrationGuidString = ""; + } + } + + public static void Postfix() + { + if (_savedGuid != null) + { + CurrentModRegistration.ModRegistrationGuidString = _savedGuid; + _savedGuid = null; } } } From 4f5c8ceee867bad97958f348facdf7610e5e7ccc Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Thu, 27 Aug 2026 21:30:38 +0800 Subject: [PATCH 7/7] Fix FreePlay --- Reactor/Networking/Patches/ModRegistrationPatch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Reactor/Networking/Patches/ModRegistrationPatch.cs b/Reactor/Networking/Patches/ModRegistrationPatch.cs index e87129b6..d9096d30 100644 --- a/Reactor/Networking/Patches/ModRegistrationPatch.cs +++ b/Reactor/Networking/Patches/ModRegistrationPatch.cs @@ -35,7 +35,7 @@ public static class LocalGamePatch public static void Prefix() { if (AmongUsClient.Instance != null - && AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) + && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) { _savedGuid = CurrentModRegistration.ModRegistrationGuidString; CurrentModRegistration.ModRegistrationGuidString = "";