From 510ed07389a63d091b43fe2de73499a316b6333a Mon Sep 17 00:00:00 2001
From: linmeideli <2558527373@qq.com>
Date: Wed, 19 Aug 2026 12:27:18 +0800
Subject: [PATCH] Basically support AMCI
---
.../packages.lock.json | 3 +-
src/Impostor.Api/Games/IGame.cs | 8 ++++-
.../Innersloth/GameFilters/GameFilter.cs | 3 ++
.../Innersloth/GameFilters/ModFilter.cs | 16 ++++++++++
.../Net/Messages/C2S/Message00HostGameC2S.cs | 8 ++++-
src/Impostor.Api/Net/Messages/MessageFlags.cs | 1 +
src/Impostor.Api/packages.lock.json | 9 +++++-
src/Impostor.Server/Http/ListingManager.cs | 15 ++++++++++
src/Impostor.Server/Net/Client.cs | 30 +++++++++++++++++--
.../Net/Manager/GameManager.cs | 14 +++++----
src/Impostor.Server/Net/State/Game.cs | 6 +++-
.../Recorder/ClientRecorder.cs | 4 +--
src/Impostor.Server/packages.lock.json | 6 ++++
13 files changed, 107 insertions(+), 16 deletions(-)
create mode 100644 src/Impostor.Api/Innersloth/GameFilters/ModFilter.cs
diff --git a/src/Impostor.Api.Innersloth.Generator/packages.lock.json b/src/Impostor.Api.Innersloth.Generator/packages.lock.json
index 4448822..fd80d2f 100644
--- a/src/Impostor.Api.Innersloth.Generator/packages.lock.json
+++ b/src/Impostor.Api.Innersloth.Generator/packages.lock.json
@@ -151,6 +151,7 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
}
- }
+ },
+ ".NETStandard,Version=v2.0/win-x64": {}
}
}
\ No newline at end of file
diff --git a/src/Impostor.Api/Games/IGame.cs b/src/Impostor.Api/Games/IGame.cs
index 71c764a..e1bdc21 100644
--- a/src/Impostor.Api/Games/IGame.cs
+++ b/src/Impostor.Api/Games/IGame.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
@@ -47,6 +48,11 @@ namespace Impostor.Api.Games
///
bool IsHostAuthoritive { get; }
+ ///
+ /// Gets the mod GUID that the game was registered with through the AMCI.
+ ///
+ Guid? ModGuid { get; }
+
IClientPlayer? GetClientPlayer(int clientId);
T? FindObjectByNetId(uint netId)
diff --git a/src/Impostor.Api/Innersloth/GameFilters/GameFilter.cs b/src/Impostor.Api/Innersloth/GameFilters/GameFilter.cs
index 43e7c91..7357f5f 100644
--- a/src/Impostor.Api/Innersloth/GameFilters/GameFilter.cs
+++ b/src/Impostor.Api/Innersloth/GameFilters/GameFilter.cs
@@ -73,6 +73,9 @@ namespace Impostor.Api.Innersloth.GameFilters
case "bool":
return JsonSerializer.Deserialize(filterString)
?? throw new InvalidOperationException("Deserialization returned null for BoolGameFilter");
+ case "mod":
+ return JsonSerializer.Deserialize(filterString)
+ ?? throw new InvalidOperationException("Deserialization returned null for ModFilter");
default:
throw new InvalidOperationException("No type matches subfilter");
}
diff --git a/src/Impostor.Api/Innersloth/GameFilters/ModFilter.cs b/src/Impostor.Api/Innersloth/GameFilters/ModFilter.cs
new file mode 100644
index 0000000..71e282d
--- /dev/null
+++ b/src/Impostor.Api/Innersloth/GameFilters/ModFilter.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Text.Json.Serialization;
+
+// https://github.com/Innersloth-LLC/AmongUsModdingInformation
+namespace Impostor.Api.Innersloth.GameFilters
+{
+ [Serializable]
+ public class ModFilter : ISubFilter
+ {
+ [JsonPropertyName("FilterType")]
+ public string FilterType { get; } = "mod";
+
+ [JsonPropertyName("AcceptedValues")]
+ public required Guid AcceptedValues { get; set; }
+ }
+}
diff --git a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs
index 8b802c2..6bc1758 100644
--- a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs
+++ b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs
@@ -1,4 +1,4 @@
-using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.GameOptions;
namespace Impostor.Api.Net.Messages.C2S
@@ -20,5 +20,11 @@ namespace Impostor.Api.Net.Messages.C2S
crossplayFlags = (CrossplayFlags)reader.ReadInt32();
gameFilterOptions = GameFilterOptions.Deserialize(reader);
}
+
+ public static void DeserializeModded(IMessageReader reader, out IGameOptions gameOptions, out CrossplayFlags crossplayFlags, out GameFilterOptions gameFilterOptions, out Guid modGuid)
+ {
+ Deserialize(reader, out gameOptions, out crossplayFlags, out gameFilterOptions);
+ modGuid = new Guid(reader.ReadBytes(16).Span);
+ }
}
}
diff --git a/src/Impostor.Api/Net/Messages/MessageFlags.cs b/src/Impostor.Api/Net/Messages/MessageFlags.cs
index 4faba36..a822fbd 100644
--- a/src/Impostor.Api/Net/Messages/MessageFlags.cs
+++ b/src/Impostor.Api/Net/Messages/MessageFlags.cs
@@ -29,6 +29,7 @@ namespace Impostor.Api.Net.Messages
public const byte QueryPlatformIds = 22;
public const byte QueryLobbyInfo = 23;
public const byte EndGameHostMigration = 24;
+ public const byte HostModdedGame = 25;
public const byte PackedGameDataTo = 26;
private static readonly Dictionary FlagCache;
diff --git a/src/Impostor.Api/packages.lock.json b/src/Impostor.Api/packages.lock.json
index c209cfa..ab47122 100644
--- a/src/Impostor.Api/packages.lock.json
+++ b/src/Impostor.Api/packages.lock.json
@@ -40,6 +40,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
}
},
+ "Microsoft.NET.ILLink.Tasks": {
+ "type": "Direct",
+ "requested": "[8.0.30, )",
+ "resolved": "8.0.30",
+ "contentHash": "SfmwuZjUPBTEoBMAsXZ/hWkaEK9oL+Y/UtAx7N6RyWHfcMt9J0ExrNPuOgTyPbCXIyiTBFsxFNrPLEZNPmJcvA=="
+ },
"StyleCop.Analyzers": {
"type": "Direct",
"requested": "[1.2.0-beta.556, )",
@@ -117,6 +123,7 @@
"resolved": "4.5.0",
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
}
- }
+ },
+ "net8.0/win-x64": {}
}
}
\ No newline at end of file
diff --git a/src/Impostor.Server/Http/ListingManager.cs b/src/Impostor.Server/Http/ListingManager.cs
index 0221d3d..f92b373 100644
--- a/src/Impostor.Server/Http/ListingManager.cs
+++ b/src/Impostor.Server/Http/ListingManager.cs
@@ -59,6 +59,10 @@ public sealed class ListingManager
{
continue;
}
+ if (game.ModGuid != null)
+ {
+ continue;
+ }
if (!_compatibilityConfig.AllowVersionMixing &&
game.Host != null &&
@@ -137,6 +141,10 @@ public sealed class ListingManager
{
continue;
}
+ if (game.ModGuid != null && !filterSet.Filters.Any(f => f.OptionType == "mod"))
+ {
+ continue;
+ }
var matchesAllFilters = true;
@@ -191,6 +199,13 @@ public sealed class ListingManager
}
}
+ break;
+ case "mod":
+ if (filter.SubFilter is ModFilter modFilter && game.ModGuid != modFilter.AcceptedValues)
+ {
+ matchesAllFilters = false;
+ }
+
break;
}
diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs
index 97bb7c2..6d83016 100644
--- a/src/Impostor.Server/Net/Client.cs
+++ b/src/Impostor.Server/Net/Client.cs
@@ -5,6 +5,7 @@ using Impostor.Api;
using Impostor.Api.Config;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.GameOptions;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Messages;
@@ -41,6 +42,11 @@ namespace Impostor.Server.Net
{
return false;
}
+
+ if (Player != null && Player.Game.ModGuid != null)
+ {
+ return false;
+ }
if (Player != null && Player.IsHost)
{
@@ -124,12 +130,25 @@ namespace Impostor.Server.Net
switch (flag)
{
case MessageFlags.HostGame:
+ case MessageFlags.HostModdedGame:
{
- // Read game settings.
- Message00HostGameC2S.Deserialize(reader, out var gameOptions, out _, out var gameFilterOptions);
+ IGameOptions gameOptions;
+ GameFilterOptions gameFilterOptions;
+ Guid? modGuid = null;
+
+ if (flag == MessageFlags.HostModdedGame)
+ {
+ Message00HostGameC2S.DeserializeModded(reader, out gameOptions, out _, out gameFilterOptions, out var parsedModGuid);
+ modGuid = parsedModGuid;
+ }
+ else
+ {
+ // Read game settings.
+ Message00HostGameC2S.Deserialize(reader, out gameOptions, out _, out gameFilterOptions);
+ }
// Create game.
- var game = await _gameManager.CreateAsync(this, gameOptions, gameFilterOptions);
+ var game = await _gameManager.CreateAsync(this, gameOptions, gameFilterOptions, modGuid);
if (game == null)
{
@@ -137,6 +156,11 @@ namespace Impostor.Server.Net
return;
}
+ if (modGuid != null)
+ {
+ _logger.LogInformation("Client {Name} ({Id}) hosted a modded game with mod GUID {ModGuid}.", Name, Id, modGuid);
+ }
+
// Code in the packet below will be used in JoinGame.
using (var writer = MessageWriter.Get(MessageType.Reliable))
{
diff --git a/src/Impostor.Server/Net/Manager/GameManager.cs b/src/Impostor.Server/Net/Manager/GameManager.cs
index d8d5e97..19d5f33 100644
--- a/src/Impostor.Server/Net/Manager/GameManager.cs
+++ b/src/Impostor.Server/Net/Manager/GameManager.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -85,7 +85,7 @@ namespace Impostor.Server.Net.Manager
await _eventManager.CallAsync(new GameDestroyedEvent(game));
}
- public async ValueTask CreateAsync(IClient? owner, IGameOptions options, GameFilterOptions filterOptions)
+ public async ValueTask CreateAsync(IClient? owner, IGameOptions options, GameFilterOptions filterOptions, Guid? modGuid = null)
{
if (owner != null && !_gamesCreatedBy.TryAdd(owner, null))
{
@@ -101,11 +101,11 @@ namespace Impostor.Server.Net.Manager
return null;
}
- var (success, game) = await TryCreateAsync(options, filterOptions, owner, @event.GameCode);
+ var (success, game) = await TryCreateAsync(options, filterOptions, owner, @event.GameCode, modGuid);
for (var i = 0; i < 10 && !success; i++)
{
- (success, game) = await TryCreateAsync(options, filterOptions, owner);
+ (success, game) = await TryCreateAsync(options, filterOptions, owner, modGuid: modGuid);
}
if (owner != null)
@@ -126,10 +126,12 @@ namespace Impostor.Server.Net.Manager
return CreateAsync(null, options, filterOptions);
}
- private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, IClient? owner, GameCode? desiredGameCode = null)
+ private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, IClient? owner, GameCode? desiredGameCode = null, Guid? modGuid = null)
{
var gameCode = desiredGameCode ?? _gameCodeFactory.Create();
- var game = ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options, filterOptions);
+ var game = modGuid.HasValue
+ ? ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options, filterOptions, modGuid.Value)
+ : ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options, filterOptions);
if (!_games.TryAdd(gameCode, game))
{
diff --git a/src/Impostor.Server/Net/State/Game.cs b/src/Impostor.Server/Net/State/Game.cs
index 07fd480..9fec3c8 100644
--- a/src/Impostor.Server/Net/State/Game.cs
+++ b/src/Impostor.Server/Net/State/Game.cs
@@ -47,7 +47,8 @@ namespace Impostor.Server.Net.State
IEventManager eventManager,
ICompatibilityManager compatibilityManager,
IOptions compatibilityConfig,
- IOptions timeoutConfig)
+ IOptions timeoutConfig,
+ Guid? modGuid = null)
{
_logger = logger;
_serviceProvider = serviceProvider;
@@ -62,6 +63,7 @@ namespace Impostor.Server.Net.State
GameNet = new GameNet();
Options = options;
FilterOptions = filterOptions;
+ ModGuid = modGuid;
_clientManager = clientManager;
_eventManager = eventManager;
_compatibilityManager = compatibilityManager;
@@ -96,6 +98,8 @@ namespace Impostor.Server.Net.State
public bool IsHostAuthoritive => Host != null && Host.Client.GameVersion.HasDisableServerAuthorityFlag;
+ public Guid? ModGuid { get; }
+
internal GameNet GameNet { get; }
public bool TryGetPlayer(int id, [MaybeNullWhen(false)] out ClientPlayer player)
diff --git a/src/Impostor.Server/Recorder/ClientRecorder.cs b/src/Impostor.Server/Recorder/ClientRecorder.cs
index 4671dd3..428b1fd 100644
--- a/src/Impostor.Server/Recorder/ClientRecorder.cs
+++ b/src/Impostor.Server/Recorder/ClientRecorder.cs
@@ -1,4 +1,4 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Impostor.Api.Config;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Custom;
@@ -52,7 +52,7 @@ namespace Impostor.Server.Recorder
await base.HandleMessageAsync(reader, messageType);
// Player created a game.
- if (reader.Tag == MessageFlags.HostGame)
+ if (reader.Tag is MessageFlags.HostGame or MessageFlags.HostModdedGame)
{
_createdGame = true;
}
diff --git a/src/Impostor.Server/packages.lock.json b/src/Impostor.Server/packages.lock.json
index e4565d7..6cd84f2 100644
--- a/src/Impostor.Server/packages.lock.json
+++ b/src/Impostor.Server/packages.lock.json
@@ -56,6 +56,12 @@
"resolved": "8.0.1",
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
},
+ "Microsoft.NET.ILLink.Tasks": {
+ "type": "Direct",
+ "requested": "[8.0.30, )",
+ "resolved": "8.0.30",
+ "contentHash": "SfmwuZjUPBTEoBMAsXZ/hWkaEK9oL+Y/UtAx7N6RyWHfcMt9J0ExrNPuOgTyPbCXIyiTBFsxFNrPLEZNPmJcvA=="
+ },
"Serilog.Extensions.Hosting": {
"type": "Direct",
"requested": "[8.0.0, )",
--
2.39.5