mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 11:39:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f55fe205d | ||
|
|
734643c8c9 | ||
|
|
661421da5a | ||
|
|
ff2e80d939 | ||
|
|
ddd701320d | ||
|
|
be39c9facf | ||
|
|
6f56e1acaf | ||
|
|
f1f5185f10 | ||
|
|
414303b2c1 | ||
|
|
0ba700ef68 | ||
|
|
c2b5a3e9df | ||
|
|
ac12bacb00 | ||
|
|
2f3e1a620b | ||
|
|
d391b24aad | ||
|
|
5854000220 | ||
|
|
ef022b04e4 | ||
|
|
155c3e69d9 | ||
|
|
3c9b0c305c | ||
|
|
2c0f45c5d1 | ||
|
|
9b7b1e8b26 | ||
|
|
1a11b5f43e | ||
|
|
bdd65e9e9f | ||
|
|
ba9eeb5bb4 | ||
|
|
5dafd9fe5b | ||
|
|
7f943e6b2e | ||
|
|
28f3cb3b2e | ||
|
|
7a49d2d917 | ||
|
|
092504f2f1 | ||
|
|
453d12171e | ||
|
|
b2e220a738 | ||
|
|
2be1d68eeb | ||
|
|
c7ea59fed9 | ||
|
|
6cc2185915 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Building from source
|
||||
|
||||
The solution contains the Impostor server and its dependencies, like Hazel and the plugin API. The server is built using [.NET 7](https://dotnet.microsoft.com/download/dotnet/7.0).
|
||||
The solution contains the Impostor server and its dependencies, like Hazel and the plugin API. The server is built using [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
|
||||
## Cloning Impostor
|
||||
|
||||
@@ -14,7 +14,7 @@ git clone https://github.com/Impostor/Impostor.git
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [.NET 7 SDK](https://dotnet.microsoft.com/download/dotnet/7.0)
|
||||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||||
- [Rider](https://www.jetbrains.com/rider/) or [Visual Studio](https://visualstudio.microsoft.com/vs/) (Optional, only if you want the full IDE experience)
|
||||
|
||||
### Build using the CLI
|
||||
|
||||
@@ -16,7 +16,7 @@ Depending on your host you may also need to port forward Impostor to the interne
|
||||
|
||||
## Normal installation
|
||||
|
||||
1. Install [.NET 7.0](https://dotnet.microsoft.com/download/dotnet/7.0). We recommend either the ASP.NET Core Runtime or the SDK. The SDK is necessary in case you want to develop Impostor or Impostor plugins.
|
||||
1. Install [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0). We recommend either the ASP.NET Core Runtime or the SDK. The SDK is necessary in case you want to develop Impostor or Impostor plugins.
|
||||
2. Download the [latest release](https://github.com/Impostor/Impostor/releases) or the [latest CI build](https://nightly.link/Impostor/Impostor/workflows/ci/master). Note that Impostor is built for multiple CPU-architectures and operating systems, you most likely want the x64 version, unless you are running on a Raspberry Pi or another device/VPS with an Arm processor.
|
||||
3. Extract the zip.
|
||||
4. Modify `config.json` to your liking. Documentation can be found [here](Server-configuration.md). You need to at least change `PublicIp` to the address people will connect to your server to.
|
||||
|
||||
@@ -27,10 +27,20 @@ Impostor has an Http Server that is used by recent versions of Among Us to conne
|
||||
|
||||
Impostor has an Anticheat that makes it possible to kick cheaters from games automatically. Note that the anticheat is tuned on the vanilla version of the game, so client-side modifications could trigger the Anticheat if you're playing with them.
|
||||
|
||||
| Key | Default | Value |
|
||||
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Enabled** | `true` | Whether the anticheat should be enabled. |
|
||||
| **BanIpFromGame** | `true` | When anticheat is enabled and a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. |
|
||||
| Key | Default | Value |
|
||||
|----------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Enabled** | `true` | Whether the anticheat should be enabled. |
|
||||
| **BanIpFromGame** | `true` | When anticheat is enabled and a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. |
|
||||
| **AllowCheatingHosts** | `"Never"` | Configure whether hosts are allowed to cheat. "Never" forbids it, "Always" allows it. "IfRequested" allows hosts to cheat if they connect with the DisableServerAuthorityFlag set. |
|
||||
| **EnableGameFlowChecks** | `true` | Enable checks that check if certain actions are done in the appropriate order or at the appropriate moment in the game. This includes changing cosmetics while in game or murdering too fast. |
|
||||
| **EnableMustBeHostChecks** | `true` | Enables checks that check if players are the host before they can do actions that require them to be host of the game. This includes starting the game and spawning objects. |
|
||||
| **EnableColorLimitChecks** | `true` | Enables checks that checks if players request colors that are already in use. |
|
||||
| **EnableNameLimitChecks** | `true` | Enables checks that checks if player names have a length that is possible to set using the user interface. |
|
||||
| **EnableOwnershipChecks** | `true` | Enables checks that check if players are allowed to perform a certain action on themself or another player. |
|
||||
| **EnableRoleChecks** | `true` | Enables checks that check if players have the correct role when performing certain role abilities like venting or murdering. |
|
||||
| **EnableTargetChecks** | `true` | Enables checks that check if certain packets to everyone that should only have been sent to certain players or vice versa. This includes sending votes or network objects. |
|
||||
|
||||
| **ForbidProtocolExtensions** | `true` | If disabled allows players to send network packets that go beyond the network packets sent by the vanilla game. This is necessary for most mods that need all players to install it. |
|
||||
|
||||
### Compatibility
|
||||
|
||||
|
||||
@@ -27,18 +27,10 @@ https://dotnet.microsoft.com/download
|
||||
|
||||
## 2. Create a C# project
|
||||
|
||||
The first step is creating a new C# project, it must be a **Class Library (.NET Standard)**. The target framework can be any of those compatible with .NET 7, which includes:
|
||||
|
||||
- .NET Standard 2.0
|
||||
- .NET Standard 2.1
|
||||
- .NET 5
|
||||
- .NET 6
|
||||
- .NET 7
|
||||
The first step is creating a new C# project, it must be a **Class Library (.NET Standard)**. The target framework can be any of those compatible with .NET 8, but we recommend sticking with **.NET 8.0**.
|
||||
|
||||
For more information about compatibility, see https://docs.microsoft.com/en-us/dotnet/standard/net-standard.
|
||||
|
||||
> At the moment of writing this document, I recommend you to use **.NET 7.0**.
|
||||
|
||||
When the project has been created, you should have `Class.cs` and `Project.csproj` files. Your `Project.csproj` should look something like this.
|
||||
|
||||
```xml
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<VersionPrefix>1.9.0</VersionPrefix>
|
||||
<VersionPrefix>1.9.2</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
<EnforceCodeStyleInBuild Condition="$(MSBuildProjectName)!='Hazel'">true</EnforceCodeStyleInBuild>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Impostor.Api;
|
||||
|
||||
public enum CheatCategory
|
||||
{
|
||||
/// <summary>A packet used a part of the network protocol that is unknown to Impostor, like a custom RPC.</summary>
|
||||
ProtocolExtension,
|
||||
|
||||
/// <summary>A packet was sent at an inappropriate moment.</summary>
|
||||
GameFlow,
|
||||
|
||||
/// <summary>A packet was sent by a non-host player that should normally only be sent by the host.</summary>
|
||||
MustBeHost,
|
||||
|
||||
/// <summary>A packet was sent that violated limits on the selection of player colors.</summary>
|
||||
ColorLimits,
|
||||
|
||||
/// <summary>A packet was sent that exceeded the limits of possible nicknames to enter ingame.</summary>
|
||||
NameLimits,
|
||||
|
||||
/// <summary>A packet was sent on behalf of another player.</summary>
|
||||
Ownership,
|
||||
|
||||
/// <summary>An ability was used that the current role cannot access.</summary>
|
||||
Role,
|
||||
|
||||
/// <summary>A packet was sent to a player that should be broadcasted, or vice versa.</summary>
|
||||
Target,
|
||||
|
||||
/// <summary>Legacy category for unsorted anticheat checks.</summary>
|
||||
Other,
|
||||
}
|
||||
@@ -7,5 +7,23 @@
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public bool BanIpFromGame { get; set; } = true;
|
||||
|
||||
public CheatingHostMode AllowCheatingHosts { get; set; } = CheatingHostMode.Never;
|
||||
|
||||
public bool EnableGameFlowChecks { get; set; } = true;
|
||||
|
||||
public bool EnableMustBeHostChecks { get; set; } = true;
|
||||
|
||||
public bool EnableColorLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableNameLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableOwnershipChecks { get; set; } = true;
|
||||
|
||||
public bool EnableRoleChecks { get; set; } = true;
|
||||
|
||||
public bool EnableTargetChecks { get; set; } = true;
|
||||
|
||||
public bool ForbidProtocolExtensions { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Impostor.Api.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Details if exceptions are made for hosts that are cheating.
|
||||
/// </summary>
|
||||
public enum CheatingHostMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Hosts follow the same policies as other players.
|
||||
/// </summary>
|
||||
Never,
|
||||
|
||||
/// <summary>
|
||||
/// Hosts are allowed to cheat if they request HostAuthority. If they
|
||||
/// do not request this, the same policies as for other players applies.
|
||||
/// </summary>
|
||||
/// <para>
|
||||
/// HostAuthority can be requested by hosts by adding 25 to their patch
|
||||
/// version when connecting. This flag is used by a lot of (host-only)
|
||||
/// mods and also disable server authority over MurderPlayer packets.
|
||||
/// </para>
|
||||
IfRequested,
|
||||
|
||||
/// <summary>
|
||||
/// Hosts are always allowed to cheat.
|
||||
/// </summary>
|
||||
Always,
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,24 @@
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net;
|
||||
|
||||
namespace Impostor.Api.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Called whenever a new <see cref="IGame" /> is created.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that the game just has been created, so no players have joined
|
||||
/// it yet. If you want to know the future host of this game, use the
|
||||
/// <see cref="Host"/> property.
|
||||
/// </remarks>
|
||||
public interface IGameCreatedEvent : IGameEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the client that requested creation of the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will be null if game creation was requested by a plugin.
|
||||
/// </remarks>
|
||||
IClient? Host { get; }
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
"IncorrectGame": 18,
|
||||
"ServerRequest": 19,
|
||||
"ServerFull": 20,
|
||||
"MismatchedVersion": 21,
|
||||
"InternalPlayerMissing": 100,
|
||||
"InternalNonceFailure": 101,
|
||||
"InternalConnectionToken": 102,
|
||||
@@ -42,5 +43,6 @@
|
||||
"PlatformFailedToGetUserBlock": 213,
|
||||
"ServerNotFound": 214,
|
||||
"ClientTimeout": 215,
|
||||
"ErrorAuthNonceFailure": 216,
|
||||
"Unknown": 255
|
||||
}
|
||||
@@ -506,6 +506,7 @@
|
||||
"ErrorSanction": 728,
|
||||
"ErrorClientTimeout": 729,
|
||||
"ErrorClientTimeoutConsole": 730,
|
||||
"ErrorAuthNonceFailure": 731,
|
||||
"VentDirection": 1000,
|
||||
"VentMove": 1001,
|
||||
"MenuNavigate": 1002,
|
||||
@@ -1328,6 +1329,8 @@
|
||||
"FriendsLabel": 3322,
|
||||
"ReportNotificationHeader": 3323,
|
||||
"ReportNotificationBody": 3324,
|
||||
"MissingGuardianEmailText": 3325,
|
||||
"AprilFoolsModeToggle": 3326,
|
||||
"MiningPit": 3400,
|
||||
"Jungle": 3401,
|
||||
"BuildSandcastle": 3402,
|
||||
@@ -1395,5 +1398,6 @@
|
||||
"HttpErrorInternalServerError": 3530,
|
||||
"HttpErrorBadGateway": 3531,
|
||||
"HttpErrorServiceUnavailable": 3532,
|
||||
"HttpErrorGatewayTimeout": 3533
|
||||
"HttpErrorGatewayTimeout": 3533,
|
||||
"UserVersionError": 3534
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dumpostorVersion": "1.0.0",
|
||||
"gameVersion": "2023.10.24",
|
||||
"platformType": "StandaloneSteamPC"
|
||||
}
|
||||
"gameVersion": "2024.3.5",
|
||||
"platformType": "StandaloneItch"
|
||||
}
|
||||
@@ -5,5 +5,7 @@ namespace Impostor.Api.Innersloth
|
||||
None,
|
||||
Normal,
|
||||
HideNSeek,
|
||||
NormalFools,
|
||||
SeekFools,
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,8 @@ public static class GameOptionsFactory
|
||||
|
||||
return gameMode switch
|
||||
{
|
||||
GameModes.Normal => NormalGameOptions.Deserialize(optionsReader, version),
|
||||
GameModes.HideNSeek => HideNSeekGameOptions.Deserialize(optionsReader, version),
|
||||
GameModes.Normal or GameModes.NormalFools => NormalGameOptions.Deserialize(optionsReader, version),
|
||||
GameModes.HideNSeek or GameModes.SeekFools => HideNSeekGameOptions.Deserialize(optionsReader, version),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
@@ -62,9 +62,11 @@ public static class GameOptionsFactory
|
||||
switch (gameMode)
|
||||
{
|
||||
case GameModes.Normal:
|
||||
case GameModes.NormalFools:
|
||||
((NormalGameOptions)gameOptions).Deserialize(optionsReader);
|
||||
break;
|
||||
case GameModes.HideNSeek:
|
||||
case GameModes.SeekFools:
|
||||
((HideNSeekGameOptions)gameOptions).Deserialize(optionsReader);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api.Innersloth;
|
||||
@@ -74,6 +75,9 @@ namespace Impostor.Api.Net
|
||||
/// </summary>
|
||||
PlatformSpecificData PlatformSpecificData { get; }
|
||||
|
||||
ValueTask<bool> ReportCheatAsync(CheatContext context, CheatCategory category, string message);
|
||||
|
||||
[Obsolete("Please use the overload that adds a cheat category")]
|
||||
ValueTask<bool> ReportCheatAsync(CheatContext context, string message);
|
||||
|
||||
ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using Impostor.Api.Events;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net;
|
||||
|
||||
namespace Impostor.Server.Events
|
||||
{
|
||||
public class GameCreatedEvent : IGameCreatedEvent
|
||||
{
|
||||
public GameCreatedEvent(IGame game)
|
||||
public GameCreatedEvent(IGame game, IClient? host)
|
||||
{
|
||||
Game = game;
|
||||
Host = host;
|
||||
}
|
||||
|
||||
public IGame Game { get; }
|
||||
|
||||
public IClient? Host { get; }
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,54 @@ namespace Impostor.Server.Net
|
||||
_customMessageManager = customMessageManager;
|
||||
}
|
||||
|
||||
public override async ValueTask<bool> ReportCheatAsync(CheatContext context, string message)
|
||||
public override async ValueTask<bool> ReportCheatAsync(CheatContext context, CheatCategory category, string message)
|
||||
{
|
||||
if (!_antiCheatConfig.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{Context}] {Message}", Name, Id, context.Name, message);
|
||||
if (Player != null && Player.IsHost)
|
||||
{
|
||||
var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch {
|
||||
CheatingHostMode.Always => true,
|
||||
CheatingHostMode.IfRequested => GameVersion.HasDisableServerAuthorityFlag,
|
||||
CheatingHostMode.Never => false,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (isHostCheatingAllowed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool LogUnknownCategory(CheatCategory category)
|
||||
{
|
||||
_logger.LogWarning("Unknown cheat category {Category} was used when reporting", category);
|
||||
return true;
|
||||
}
|
||||
|
||||
var isCategoryEnabled = category switch
|
||||
{
|
||||
CheatCategory.ProtocolExtension => _antiCheatConfig.ForbidProtocolExtensions,
|
||||
CheatCategory.GameFlow => _antiCheatConfig.EnableGameFlowChecks,
|
||||
CheatCategory.MustBeHost => _antiCheatConfig.EnableMustBeHostChecks,
|
||||
CheatCategory.ColorLimits => _antiCheatConfig.EnableColorLimitChecks,
|
||||
CheatCategory.NameLimits => _antiCheatConfig.EnableNameLimitChecks,
|
||||
CheatCategory.Ownership => _antiCheatConfig.EnableOwnershipChecks,
|
||||
CheatCategory.Role => _antiCheatConfig.EnableRoleChecks,
|
||||
CheatCategory.Target => _antiCheatConfig.EnableTargetChecks,
|
||||
CheatCategory.Other => true,
|
||||
_ => LogUnknownCategory(category),
|
||||
};
|
||||
|
||||
if (!isCategoryEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{Context}-{Category}] {Message}", Name, Id, context.Name, category, message);
|
||||
|
||||
if (_antiCheatConfig.BanIpFromGame)
|
||||
{
|
||||
|
||||
@@ -41,11 +41,16 @@ namespace Impostor.Server.Net
|
||||
|
||||
IClientPlayer? IClient.Player => Player;
|
||||
|
||||
public virtual ValueTask<bool> ReportCheatAsync(CheatContext context, string message)
|
||||
public virtual ValueTask<bool> ReportCheatAsync(CheatContext context, CheatCategory category, string message)
|
||||
{
|
||||
return new ValueTask<bool>(false);
|
||||
}
|
||||
|
||||
public ValueTask<bool> ReportCheatAsync(CheatContext context, string message)
|
||||
{
|
||||
return ReportCheatAsync(context, CheatCategory.Other, message);
|
||||
}
|
||||
|
||||
public abstract ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
|
||||
|
||||
public abstract ValueTask HandleDisconnectAsync(string reason);
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (!sender.IsOwner(this))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, $"Failed ownership check on {GetType().Name}"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Ownership, $"Failed ownership check on {GetType().Name}"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (!sender.IsHost)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Failed host check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.MustBeHost, "Failed host check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Failed target check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Target, "Failed target check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Failed broadcast check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Target, "Failed broadcast check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (target == null || !target.IsHost)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Failed cmd check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Target, "Failed cmd check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (playerInfo.IsImpostor != value)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Failed impostor check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, "Failed impostor check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (playerInfo.CanVent != value)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Failed can vent check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, "Failed can vent check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
if (playerInfo.RoleType != role)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, $"Failed role = {role} check"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, $"Failed role = {role} check"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -114,7 +114,7 @@ namespace Impostor.Server.Net.Inner
|
||||
|
||||
protected async ValueTask<bool> UnregisteredCall(CheatContext context, IClientPlayer sender)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, "Client sent unregistered call"))
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.ProtocolExtension, "Client sent unregistered call"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api;
|
||||
using Impostor.Api.Events.Managers;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Api.Net.Custom;
|
||||
@@ -67,7 +68,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components
|
||||
|
||||
if (Game.GameNet.ShipStatus == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(call, "Client interacted with vent on unknown map"))
|
||||
if (await sender.Client.ReportCheatAsync(call, CheatCategory.ProtocolExtension, "Client interacted with vent on unknown map"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -77,7 +78,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components
|
||||
|
||||
if (!Game.GameNet.ShipStatus.Data.Vents.TryGetValue(ventId, out var vent))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(call, "Client interacted with nonexistent vent"))
|
||||
if (await sender.Client.ReportCheatAsync(call, CheatCategory.ProtocolExtension, "Client interacted with nonexistent vent"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components
|
||||
|
||||
if (clientId != sender.Client.Id)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.AddVote, $"Client sent {nameof(RpcCalls.AddVote)} as other client"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.AddVote, CheatCategory.Ownership, $"Client sent {nameof(RpcCalls.AddVote)} as other client"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -44,7 +44,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
try
|
||||
{
|
||||
const float AnimationTime = 0.25f + 0.5f + 0.4f + 3f + 0.75f + 5f;
|
||||
if (Game.Options.GameMode == GameModes.Normal)
|
||||
if (Game.Options.GameMode is GameModes.Normal or GameModes.NormalFools)
|
||||
{
|
||||
var options = (NormalGameOptions)Game.Options;
|
||||
await Task.Delay(TimeSpan.FromSeconds(AnimationTime + options.DiscussionTime + options.VotingTime), _timerToken.Token);
|
||||
@@ -204,7 +204,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (playerId != sender.Character!.PlayerId)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CastVote, $"Client sent {nameof(RpcCalls.CastVote)} to an unowned {nameof(InnerPlayerControl)}"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CastVote, CheatCategory.Ownership, $"Client sent {nameof(RpcCalls.CastVote)} to an unowned {nameof(InnerPlayerControl)}"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
case RpcCalls.PlayAnimation:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -124,7 +125,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.CompleteTask:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -145,21 +147,10 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.SetInfected:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender) || !await ValidateHost(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc03SetInfected.Deserialize(reader, out var infectedIds);
|
||||
await HandleSetInfected(infectedIds);
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.CheckName:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender) || !await ValidateCmd(call, sender, target))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -170,7 +161,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetName:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -181,7 +173,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.CheckColor:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender) || !await ValidateCmd(call, sender, target))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -192,7 +185,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetColor:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -203,35 +197,38 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetHatStr:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc39SetHatStr.Deserialize(reader, out var hat);
|
||||
return true;
|
||||
return await HandleSetHat(sender, hat);
|
||||
}
|
||||
|
||||
case RpcCalls.SetSkinStr:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc40SetSkinStr.Deserialize(reader, out var skin);
|
||||
return true;
|
||||
return await HandleSetSkin(sender, skin);
|
||||
}
|
||||
|
||||
case RpcCalls.SetVisorStr:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc42SetVisorStr.Deserialize(reader, out var visor);
|
||||
return true;
|
||||
return await HandleSetVisor(sender, visor);
|
||||
}
|
||||
|
||||
case RpcCalls.SetNamePlateStr:
|
||||
@@ -242,12 +239,13 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
|
||||
Rpc43SetNamePlateStr.Deserialize(reader, out var namePlate);
|
||||
return true;
|
||||
return await HandleSetNamePlate(sender, namePlate);
|
||||
}
|
||||
|
||||
case RpcCalls.SetLevel:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -258,7 +256,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.ReportDeadBody:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -269,7 +268,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.MurderPlayer:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -280,7 +280,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SendChat:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -291,7 +292,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.StartMeeting:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -303,7 +305,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetScanner:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -314,7 +317,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SendChatNote:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -325,7 +329,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetPetStr:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -336,7 +341,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetStartCounter:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -347,7 +353,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.UsePlatform:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -358,7 +365,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SendQuickChat:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -369,7 +377,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.SetRole:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -387,7 +396,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.ProtectPlayer:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -398,12 +408,9 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.Shapeshift:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Shapeshifter))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Shapeshifter) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -415,12 +422,9 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.CheckMurder:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await ValidateImpostor(call, sender, PlayerInfo))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateImpostor(call, sender, PlayerInfo) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -431,23 +435,22 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.CheckProtect:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await ValidateRole(call, sender, PlayerInfo, RoleTypes.GuardianAngel))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.GuardianAngel) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// CheckProtect should only be passed to Host and not handled server side
|
||||
Rpc48CheckProtect.Deserialize(reader, Game, out _);
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.CheckZipline:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -458,7 +461,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.UseZipline:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -469,7 +473,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.TriggerSpores:
|
||||
{
|
||||
if (!await ValidateHost(call, sender))
|
||||
if (!await ValidateHost(call, sender) ||
|
||||
!await ValidateBroadcast(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -480,7 +485,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.CheckSpore:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -491,12 +497,14 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
case RpcCalls.CheckShapeshift:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Shapeshifter) ||
|
||||
!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc46Shapeshift.Deserialize(reader, Game, out var playerControl, out var shouldAnimate);
|
||||
Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out var shouldAnimate);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -565,9 +573,17 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask<bool> HandleCheckName(ClientPlayer sender, string name)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, CheatCategory.GameFlow, "Client tried to set a name midgame"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (name.Length > 10)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name exceeding 10 characters"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, CheatCategory.NameLimits, "Client sent name exceeding 10 characters"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -575,7 +591,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name containing illegal characters"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, CheatCategory.NameLimits, "Client sent name containing illegal characters"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -583,7 +599,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (sender.Client.Name != name)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name not matching his name from handshake"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, CheatCategory.NameLimits, "Client sent name not matching his name from handshake"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -598,7 +614,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (Game.GameState == GameStates.Started)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client tried to set a name midgame"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.GameFlow, "Client tried to set a name midgame"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -608,7 +624,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name that is already used"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent name that is already used"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -616,7 +632,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (sender.Client.Name != name)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name not matching his name from handshake"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent name not matching his name from handshake"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -647,14 +663,16 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (name != expected)
|
||||
{
|
||||
_logger.LogWarning($"Client sent {nameof(RpcCalls.SetName)} with incorrect name");
|
||||
await SetNameAsync(expected);
|
||||
return false;
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent SetName with incorrect name"))
|
||||
{
|
||||
await SetNameAsync(expected);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, $"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, $"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -668,9 +686,17 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask<bool> HandleCheckColor(ClientPlayer sender, ColorType color)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.GameFlow, "Client tried to ask for a color midgame"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((byte)color > ColorsCount)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, "Client sent invalid color"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ColorLimits, "Client sent invalid color"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -685,7 +711,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (Game.GameState == GameStates.Started)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client tried to set a color midgame"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.GameFlow, "Client tried to set a color midgame"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -695,7 +721,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == color))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client sent a color that is already used"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent a color that is already used"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -703,24 +729,44 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!RequestedColorId.Any())
|
||||
if (RequestedColorId.Any())
|
||||
{
|
||||
_logger.LogWarning($"Client sent {nameof(RpcCalls.SetColor)} for a player that didn't request it");
|
||||
return false;
|
||||
var expected = RequestedColorId.Dequeue();
|
||||
|
||||
for (var colorOffset = 0; colorOffset <= ColorsCount; colorOffset++)
|
||||
{
|
||||
var possibleColor = (ColorType)((byte)(expected + colorOffset) % ColorsCount);
|
||||
if (!Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == possibleColor))
|
||||
{
|
||||
expected = possibleColor;
|
||||
break;
|
||||
}
|
||||
|
||||
if (colorOffset == ColorsCount)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor but all colors are already in use"))
|
||||
{
|
||||
await SetColorAsync(expected);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (color != expected)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor with incorrect color"))
|
||||
{
|
||||
await SetColorAsync(expected);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var expected = RequestedColorId.Dequeue();
|
||||
|
||||
while (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == expected))
|
||||
else
|
||||
{
|
||||
expected = (ColorType)(((byte)expected + 1) % ColorsCount);
|
||||
}
|
||||
|
||||
if (color != expected)
|
||||
{
|
||||
_logger.LogWarning($"Client sent {nameof(RpcCalls.SetColor)} with incorrect color");
|
||||
await SetColorAsync(expected);
|
||||
return false;
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.GameFlow, "Client sent SetColor for a player that didn't request it"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,7 +777,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask<bool> HandleSetHat(ClientPlayer sender, string hat)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetHat, "Client tried to change hat while not in lobby"))
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetHat, CheatCategory.GameFlow, "Client tried to change hat while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -743,7 +790,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, string skin)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, "Client tried to change skin while not in lobby"))
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, CheatCategory.GameFlow, "Client tried to change skin while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -753,6 +801,32 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetVisor(ClientPlayer sender, string visor)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetVisor, CheatCategory.GameFlow, "Client tried to change visor while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.VisorId = visor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetNamePlate(ClientPlayer sender, string skin)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetNamePlate, CheatCategory.GameFlow, "Client tried to change skin while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = skin;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleCheckMurder(ClientPlayer sender, InnerPlayerControl? target)
|
||||
{
|
||||
if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
|
||||
@@ -762,15 +836,21 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
// This request was made too quickly by spamming the kill button, cancel it if we're in server authoritive mode
|
||||
return _game.IsHostAuthoritive;
|
||||
}
|
||||
else if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, "Client tried to murder too fast"))
|
||||
else if (!_game.IsHostAuthoritive)
|
||||
{
|
||||
return false;
|
||||
// Host-only mods may desync the kill timeout between players
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.GameFlow, "Client tried to murder too fast"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null || target.PlayerInfo.IsImpostor)
|
||||
// Host-only mods intentionally desync players, so it may appear that one killing role (like a genuine impostor) is killing another
|
||||
// killing role (like an sheriff). So this needs to be allowed if the host requested authority.
|
||||
if (target == null || (target.PlayerInfo.IsImpostor && !_game.IsHostAuthoritive))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, "Client tried to murder invalid target"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.GameFlow, "Client tried to murder invalid target"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -807,15 +887,15 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (!_game.IsHostAuthoritive)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder directly"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, CheatCategory.GameFlow, "Client tried to murder directly"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null || target.PlayerInfo.IsImpostor)
|
||||
if (target == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder invalid target"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, CheatCategory.GameFlow, "Client tried to murder invalid target"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -824,7 +904,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
// If the host is also the impostor that committed the murder, CheckMurder is actually sent *after* the MurderPlayer RPC
|
||||
if (sender.Character != this && target != IsMurdering)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Host tried to murder incorrect target"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, CheatCategory.GameFlow, "Host tried to murder incorrect target"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -859,7 +939,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckProtect, "Client tried to protect invalid target"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckProtect, CheatCategory.Target, "Client tried to protect invalid target"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -867,12 +947,9 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PlayerInfo.RoleType != RoleTypes.GuardianAngel)
|
||||
if (!await ValidateRole(RpcCalls.ProtectPlayer, sender, PlayerInfo, RoleTypes.GuardianAngel))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckProtect, "Client tried to protect but it wasn't a guardian angel"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
((InnerPlayerControl)target).Protect(this);
|
||||
@@ -896,7 +973,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask<bool> HandleSetPet(ClientPlayer sender, string pet)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetPet, "Client tried to change pet while not in lobby"))
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetPet, CheatCategory.GameFlow, "Client tried to change pet while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -910,7 +988,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (!sender.IsHost && startCounter != -1)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to set start counter as a non-host"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetStartCounter, CheatCategory.MustBeHost, "Client tried to set start counter as a non-host"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Innersloth;
|
||||
@@ -60,7 +60,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
// the impostor has a cooldown of half the usual duration. As a workaround we always assume the kill was
|
||||
// prevented and the impostor only has half of its cooldown.
|
||||
// FIXME when the base game improved their implementation.
|
||||
if (game.Options.GameMode == GameModes.Normal)
|
||||
if (game.Options.GameMode is GameModes.Normal or GameModes.NormalFools)
|
||||
{
|
||||
var options = (NormalGameOptions)game.Options;
|
||||
return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= options.KillCooldown / 2;
|
||||
|
||||
@@ -20,6 +20,10 @@ internal class CompatibilityManager : ICompatibilityManager
|
||||
new GameVersion(2023, 10, 1), // 2023.10.24
|
||||
new GameVersion(2023, 11, 15), // 2023.11.28
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2024, 2, 1), // 2024.3.5
|
||||
},
|
||||
};
|
||||
|
||||
private readonly List<CompatibilityGroup> _compatibilityGroups = new();
|
||||
|
||||
@@ -93,11 +93,11 @@ namespace Impostor.Server.Net.Manager
|
||||
return null;
|
||||
}
|
||||
|
||||
var (success, game) = await TryCreateAsync(options, filterOptions, @event.GameCode);
|
||||
var (success, game) = await TryCreateAsync(options, filterOptions, owner, @event.GameCode);
|
||||
|
||||
for (var i = 0; i < 10 && !success; i++)
|
||||
{
|
||||
(success, game) = await TryCreateAsync(options, filterOptions);
|
||||
(success, game) = await TryCreateAsync(options, filterOptions, owner);
|
||||
}
|
||||
|
||||
if (!success || game == null)
|
||||
@@ -113,7 +113,7 @@ namespace Impostor.Server.Net.Manager
|
||||
return CreateAsync(null, options, filterOptions);
|
||||
}
|
||||
|
||||
private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, GameCode? desiredGameCode = null)
|
||||
private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, IClient? owner, GameCode? desiredGameCode = null)
|
||||
{
|
||||
var gameCode = desiredGameCode ?? _gameCodeFactory.Create();
|
||||
var game = ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options, filterOptions);
|
||||
@@ -125,7 +125,7 @@ namespace Impostor.Server.Net.Manager
|
||||
|
||||
_logger.LogDebug("Created game with code {0}.", game.Code);
|
||||
|
||||
await _eventManager.CallAsync(new GameCreatedEvent(game));
|
||||
await _eventManager.CallAsync(new GameCreatedEvent(game, owner));
|
||||
|
||||
return (true, game);
|
||||
}
|
||||
|
||||
@@ -121,10 +121,10 @@ namespace Impostor.Server.Net.State
|
||||
|
||||
case GameDataTag.SpawnFlag:
|
||||
{
|
||||
// Only the host is allowed to despawn objects.
|
||||
// Only the host is allowed to spawn objects.
|
||||
if (!sender.IsHost)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), "Tried to send SpawnFlag as non-host."))
|
||||
if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), CheatCategory.MustBeHost, "Tried to send SpawnFlag as non-host."))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -230,6 +230,8 @@ namespace Impostor.Server.Net.State
|
||||
{
|
||||
// Sender is only allowed to change his own scene.
|
||||
var clientId = reader.ReadPackedInt32();
|
||||
var scene = reader.ReadString();
|
||||
|
||||
if (clientId != sender.Client.Id)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
@@ -239,7 +241,17 @@ namespace Impostor.Server.Net.State
|
||||
return false;
|
||||
}
|
||||
|
||||
sender.Scene = reader.ReadString();
|
||||
// According to game assembly, sender is only allowed to send OnlineGame.
|
||||
if (scene != "OnlineGame")
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Player {0} ({1}) tried to send SceneChangeFlag with disallowed scene.",
|
||||
sender.Client.Name,
|
||||
sender.Client.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
sender.Scene = scene;
|
||||
|
||||
_logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene);
|
||||
break;
|
||||
@@ -248,6 +260,16 @@ namespace Impostor.Server.Net.State
|
||||
case GameDataTag.ReadyFlag:
|
||||
{
|
||||
var clientId = reader.ReadPackedInt32();
|
||||
|
||||
if (clientId != sender.Client.Id)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Player {0} ({1}) tried to send ReadyFlag for another player.",
|
||||
sender.Client.Name,
|
||||
sender.Client.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogTrace("> IsReady {0}", clientId);
|
||||
break;
|
||||
}
|
||||
@@ -259,7 +281,7 @@ namespace Impostor.Server.Net.State
|
||||
|
||||
if (clientId != sender.Client.Id)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.ConsoleDeclareClientPlatformFlag)), "Client sent info with wrong client id"))
|
||||
if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.ConsoleDeclareClientPlatformFlag)), CheatCategory.Ownership, "Client sent info with wrong client id"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -331,7 +353,7 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
else
|
||||
{
|
||||
await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), "Failed to find player that spawned the InnerPlayerControl");
|
||||
await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), CheatCategory.GameFlow, "Failed to find player that spawned the InnerPlayerControl");
|
||||
}
|
||||
|
||||
// Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo.
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Impostor.Server.Net.State
|
||||
if (player.Client.Connection.IsConnected && player.Client.Connection is HazelConnection hazel)
|
||||
{
|
||||
_logger.LogInformation("{0} - Player {1} ({2}) kept connection open after leaving, disposing.", Code, player.Client.Name, playerId);
|
||||
hazel.DisposeInnerConnection();
|
||||
await player.Client.DisconnectAsync(isBan ? DisconnectReason.Banned : DisconnectReason.Kicked);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user