+++ /dev/null
-# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 60
-# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 7
-# Issues with these labels will never be considered stale
-exemptLabels:
- - pinned
- - security
-# Label to use when marking an issue as stale
-staleLabel: stale
-# Comment to post when marking an issue as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs. Thank you
- for your contributions.
-# Comment to post when closing a stale issue. Set to `false` to disable
-closeComment: false
version: '{build}'
environment:
- IMPOSTOR_VERSION: '1.2.2'
DOTNET_CLI_TELEMETRY_OPTOUT: 1
branches:
except:
- - gh-pages
+ - gh-pages
pull_requests:
do_not_increment_build_number: true
dotnet_csproj:
patch: false
-image: Visual Studio 2019 Preview
+image: Ubuntu2004
install:
- git submodule update --init --recursive
#addin "nuget:?package=Cake.Compression&Version=0.2.4"
#addin "nuget:?package=Cake.FileHelpers&Version=3.3.0"
-
-var buildId = EnvironmentVariable("APPVEYOR_BUILD_VERSION") ?? "0";
-var buildVersion = EnvironmentVariable("IMPOSTOR_VERSION") ?? "1.0.0";
-var buildBranch = EnvironmentVariable("APPVEYOR_REPO_BRANCH") ?? "dev";
+var buildId = EnvironmentVariable("GITHUB_RUN_NUMBER") ?? EnvironmentVariable("APPVEYOR_BUILD_VERSION");
+var buildRelease = EnvironmentVariable("APPVEYOR_REPO_TAG") == "true";
+var buildVersion = FindRegexMatchGroupInFile("./src/Directory.Build.props", @"\<VersionPrefix\>(.*?)\<\/VersionPrefix\>", 1, System.Text.RegularExpressions.RegexOptions.None).Value;
var buildDir = MakeAbsolute(Directory("./build"));
-var prNumber = EnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER");
var target = Argument("target", "Deploy");
var configuration = Argument("configuration", "Release");
-// On any branch that is not master, we need to tag the version as prerelease.
-if (buildBranch != "master") {
- buildVersion = buildVersion + "-ci." + buildId;
+var msbuildSettings = new DotNetCoreMSBuildSettings();
+
+if (!buildRelease && buildId != null) {
+ msbuildSettings.Properties["VersionSuffix"] = new[] { "ci." + buildId };
+ buildVersion += "-ci." + buildId;
}
//////////////////////////////////////////////////////////////////////
SelfContained = false,
PublishSingleFile = true,
PublishTrimmed = false,
- OutputDirectory = projBuildDir
+ OutputDirectory = projBuildDir,
+ MSBuildSettings = msbuildSettings
});
if (isServer) {
}
}
- if (runtime == "win-x64") {
- Zip(projBuildDir, buildDir.CombineWithFilePath(projBuildName + ".zip"));
- } else {
- GZipCompress(projBuildDir, buildDir.CombineWithFilePath(projBuildName + ".tar.gz"));
- }
+ Zip(projBuildDir, buildDir.CombineWithFilePath(projBuildName + ".zip"));
}
private void ImpostorPublishNF(string name, string project) {
Configuration = configuration,
NoRestore = true,
Framework = "net472",
- OutputDirectory = projBuildDir
+ OutputDirectory = projBuildDir,
+ MSBuildSettings = msbuildSettings
});
Zip(projBuildDir, projBuildZip);
DotNetCoreRestore("./src/Impostor.sln");
});
-Task("Patch")
- .WithCriteria(BuildSystem.AppVeyor.IsRunningOnAppVeyor)
- .Does(() => {
- ReplaceRegexInFiles("./src/**/*.csproj", @"<Version>.*?<\/Version>", "<Version>" + buildVersion + "</Version>");
- ReplaceRegexInFiles("./src/**/*.props", @"<Version>.*?<\/Version>", "<Version>" + buildVersion + "</Version>");
- });
-
Task("Replay")
.Does(() => {
// D:\Projects\GitHub\Impostor\Impostor\src\Impostor.Tools.ServerReplay\sessions
Task("Build")
.IsDependentOn("Clean")
- .IsDependentOn("Patch")
.IsDependentOn("Restore")
.IsDependentOn("Replay")
.Does(() => {
Configuration = configuration,
});
- // Only build artifacts if;
- // - buildBranch is master/dev
- // - it is not a pull request
- if ((buildBranch == "master" || buildBranch == "dev") && string.IsNullOrEmpty(prNumber)) {
- // Client.
- ImpostorPublishNF("Impostor-Patcher", "./src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj");
+ // Client.
+ ImpostorPublishNF("Impostor-Patcher", "./src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj");
- ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "win-x64");
- ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "osx-x64");
- ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "linux-x64");
+ ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "win-x64");
+ ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "osx-x64");
+ ImpostorPublish("Impostor-Patcher-Cli", "./src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj", "linux-x64");
- // Server.
- ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "win-x64", true);
- ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "osx-x64", true);
- ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-x64", true);
- ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm", true);
- ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm64", true);
-
- // API.
- DotNetCorePack("./src/Impostor.Api/Impostor.Api.csproj", new DotNetCorePackSettings {
- Configuration = configuration,
- OutputDirectory = buildDir,
- IncludeSource = true,
- IncludeSymbols = true
- });
- } else {
- DotNetCoreBuild("./src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj", new DotNetCoreBuildSettings {
- Configuration = configuration,
- NoRestore = true,
- Framework = "net472"
- });
-
- DotNetCoreBuild("./src/Impostor.Server/Impostor.Server.csproj", new DotNetCoreBuildSettings {
- Configuration = configuration,
- NoRestore = true,
- Framework = "net5.0"
- });
- }
+ // Server.
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "win-x64", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "osx-x64", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-x64", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm", true);
+ ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm64", true);
+
+ // API.
+ DotNetCorePack("./src/Impostor.Api/Impostor.Api.csproj", new DotNetCorePackSettings {
+ Configuration = configuration,
+ OutputDirectory = buildDir,
+ IncludeSource = true,
+ IncludeSymbols = true,
+ MSBuildSettings = msbuildSettings
+ });
});
Task("Test")
# Don't use tabs for indentation.
[*]
charset = utf-8
-end_of_line = crlf
-insert_final_newline = false
+end_of_line = lf
+insert_final_newline = true
indent_style = space
# Code files
# Visual Studio 2015 cache/options directory
.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
# DNX
project.lock.json
*.svclog
*.scc
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# DotCover is a Code Coverage Tool
*.dotCover
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
# Click-Once directory
publish/
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
-# but database connection strings (with potential passwords) will be unencrypted
-#*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
# NuGet Packages
*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/packages/*
-# except build/, which is used as an MSBuild target.
-!**/packages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignoreable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
node_modules/
orleans.codegen.cs
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
UpgradeLog*.XML
UpgradeLog*.htm
-# SQL Server files
-*.mdf
-*.ldf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
# JetBrains Rider
.idea/
*.sln.iml
-
-# CodeRush
-.cr/
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
\ No newline at end of file
--- /dev/null
+<Project>
+ <PropertyGroup>
+ <VersionPrefix>1.2.2</VersionPrefix>
+ <VersionSuffix>dev</VersionSuffix>
+ </PropertyGroup>
+</Project>
+
--- /dev/null
+using Impostor.Server.Net.Inner;
+
+namespace Impostor.Api
+{
+ public class CheatContext
+ {
+ public CheatContext(string name)
+ {
+ Name = name;
+ }
+
+ public static CheatContext Deserialize { get; } = new CheatContext(nameof(Deserialize));
+
+ public static CheatContext Serialize { get; } = new CheatContext(nameof(Serialize));
+
+ public string Name { get; }
+
+ public static implicit operator CheatContext(RpcCalls rpcCalls) => new CheatContext(rpcCalls.ToString());
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Events.Announcements
+{
+ /// <summary>
+ /// Event fired after client requests a announcement.
+ /// </summary>
+ public interface IAnnouncementRequestEvent : IEvent
+ {
+ public interface IResponse
+ {
+ /// <summary>
+ /// Gets or sets FreeWeekendState, currently unused by the client.
+ /// </summary>
+ public FreeWeekendState FreeWeekendState { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether announcement should be loaded from client's cache, can save some bytes.
+ /// </summary>
+ public bool UseCached { get; set; }
+
+ /// <summary>
+ /// Gets or sets announcement, should be null when <see cref="UseCached"/> is set to true.
+ /// </summary>
+ public Announcement? Announcement { get; set; }
+ }
+
+ /// <summary>
+ /// Gets client's last announcement id.
+ /// </summary>
+ public int Id { get; }
+
+ /// <summary>
+ /// Gets client's language.
+ /// </summary>
+ public Language Language { get; }
+
+ /// <summary>
+ /// Gets or sets plugin made response.
+ /// </summary>
+ public IResponse Response { get; set; }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Events.Player
+{
+ public interface IPlayerMovementEvent : IPlayerEvent
+ {
+ }
+}
+++ /dev/null
-using System;
-using System.Runtime.Serialization;
-
-namespace Impostor.Api
-{
- public class ImpostorCheatException : ImpostorException
- {
- public ImpostorCheatException()
- {
- }
-
- protected ImpostorCheatException(SerializationInfo info, StreamingContext context) : base(info, context)
- {
- }
-
- public ImpostorCheatException(string? message) : base(message)
- {
- }
-
- public ImpostorCheatException(string? message, Exception? innerException) : base(message, innerException)
- {
- }
- }
-}
\ No newline at end of file
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner;
-using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
namespace Impostor.Api.Games
IClientPlayer GetClientPlayer(int clientId);
+ T? FindObjectByNetId<T>(uint netId)
+ where T : IInnerNetObject;
+
/// <summary>
/// Adds an <see cref="IPAddress"/> to the ban list of this game.
/// Prevents all future joins from this <see cref="IPAddress"/>.
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask SyncSettingsAsync();
- /// <summary>
- /// Sets the specified list as Impostor on all connected players.
- /// </summary>
- /// <param name="players">List of players to be Impostor.</param>
- /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
- ValueTask SetInfectedAsync(IEnumerable<IInnerPlayerControl> players);
-
/// <summary>
/// Send the message to all players.
/// </summary>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<CodeAnalysisRuleSet>ProjectRules.ruleset</CodeAnalysisRuleSet>
<LangVersion>9</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
- <Version>1.0.0</Version>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AssemblyName>Impostor.Api</AssemblyName>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Nullable" Version="1.3.0">
--- /dev/null
+namespace Impostor.Api.Innersloth
+{
+ public readonly struct Announcement
+ {
+ public readonly int Id;
+ public readonly string Message;
+
+ public Announcement(int id, string message)
+ {
+ Id = id;
+ Message = message;
+ }
+ }
+}
namespace Impostor.Api.Innersloth.Customization
{
- public enum HatType
+ public enum HatType : uint
{
NoHat = 0,
Astronaut = 1,
ThirdEyeHat = 40,
ToiletPaperHat = 41,
Toppat = 42,
- Fedora = 43,
- Goggles2 = 44,
+ BlackFedora = 43,
+ SkiGoggles = 44,
Headphones = 45,
MaskHat = 46,
PaperMask = 47,
Cheese = 53,
Cherry = 54,
Egg = 55,
- Fedora2 = 56,
+ GreenFedora = 56,
Flamingo = 57,
FlowerPin = 58,
Helmet = 59,
MiniCrewmate = 90,
NinjaMask = 91,
RamHorns = 92,
- Snowman2 = 93,
+ SnowCrewmate = 93,
+ GeoffHat = 94,
}
}
namespace Impostor.Api.Innersloth.Customization
{
- public enum PetType
+ public enum PetType : uint
{
NoPet = 0,
Alien = 1,
namespace Impostor.Api.Innersloth.Customization
{
- public enum SkinType : byte
+ public enum SkinType : uint
{
None = 0,
Astro = 1,
+++ /dev/null
-using Impostor.Api.Unity;
-
-namespace Impostor.Api.Innersloth
-{
- public class FloatRange
- {
- private readonly float _min;
- private readonly float _max;
-
- public FloatRange(float min, float max)
- {
- _min = min;
- _max = max;
- }
-
- public float Width => _max - _min;
-
- public float Lerp(float v) => Mathf.Lerp(_min, _max, v);
-
- public float ReverseLerp(float t) => Mathf.Clamp((t - _min) / Width, 0.0f, 1f);
- }
-}
\ No newline at end of file
--- /dev/null
+using System;
+
+namespace Impostor.Api.Innersloth
+{
+ [Flags]
+ public enum FreeWeekendState : byte
+ {
+ NotFree,
+ FreeMIRA,
+ FreePolus,
+ }
+}
/// </summary>
/// <param name="writer">The stream to write the message to.</param>
/// <param name="version">The version of the game.</param>
- public void Serialize(BinaryWriter writer, byte version)
+ public void Serialize(BinaryWriter writer, byte version = LatestVersion)
{
writer.Write((byte)version);
writer.Write((byte)MaxPlayers);
}
}
+ public void Serialize(IMessageWriter writer)
+ {
+ using var memory = new MemoryStream();
+ using var writerBin = new BinaryWriter(memory);
+ Serialize(writerBin);
+ writer.WriteBytesAndSize(memory.ToArray());
+ }
+
/// <summary>
/// Deserialize a ReadOnlyMemory object to this instance of the GameOptionsData object.
/// </summary>
--- /dev/null
+namespace Impostor.Api.Innersloth
+{
+ public enum Language
+ {
+ English,
+ Spanish,
+ Portuguese,
+ Korean,
+ Russian,
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Impostor.Api.Innersloth
+{
+ public class MapSpawn
+ {
+ private MapSpawn(float spawnRadius, Vector2 initialSpawnCenter, Vector2 meetingSpawnCenter)
+ {
+ SpawnRadius = spawnRadius;
+ InitialSpawnCenter = initialSpawnCenter;
+ MeetingSpawnCenter = meetingSpawnCenter;
+ }
+
+ public static Dictionary<MapTypes, MapSpawn> Maps { get; } = new Dictionary<MapTypes, MapSpawn>
+ {
+ [MapTypes.Skeld] = new MapSpawn(1.6f, new Vector2(-0.72f, 0.62f), new Vector2(-0.72f, 0.62f)),
+ [MapTypes.MiraHQ] = new MapSpawn(1.55f, new Vector2(-4.4f, 2.2f), new Vector2(24.043f, 1.72f)),
+ [MapTypes.Polus] = new MapSpawn(1f, new Vector2(16.64f, -2.46f), new Vector2(17.726f, -16.286f)),
+ };
+
+ public float SpawnRadius { get; }
+
+ public Vector2 InitialSpawnCenter { get; }
+
+ public Vector2 MeetingSpawnCenter { get; }
+
+ public Vector2 GetSpawnLocation(int playerId, int numPlayer, bool initialSpawn)
+ {
+ var vector = new Vector2(0, 1);
+ vector = Rotate(vector, (playerId - 1) * (360f / numPlayer));
+ vector *= this.SpawnRadius;
+ return (initialSpawn ? this.InitialSpawnCenter : this.MeetingSpawnCenter) + vector + new Vector2(0f, 0.3636f);
+ }
+
+ private static Vector2 Rotate(Vector2 self, float degrees)
+ {
+ var f = 0.017453292f * degrees;
+ var num = (float)Math.Cos(f);
+ var num2 = (float)Math.Sin(f);
+ return new Vector2((self.X * num) - (num2 * self.Y), (self.X * num2) + (num * self.Y));
+ }
+ }
+}
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Reactor;
namespace Impostor.Api.Net
{
/// </remarks>
string Name { get; }
+ /// <summary>
+ /// Gets mods sent by client in modded handshake.
+ /// </summary>
+ ISet<Mod> Mods { get; }
+
/// <summary>
/// Gets the connection of the client.
/// </summary>
/// </summary>
IClientPlayer? Player { get; }
+ ValueTask<bool> ReportCheatAsync(CheatContext context, string message);
+
ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
ValueTask HandleDisconnectAsync(string reason);
{
public interface IInnerCustomNetworkTransform : IInnerNetObject
{
+ /// <summary>
+ /// Gets position where the object thinks it is (not interpolated).
+ /// </summary>
+ Vector2 Position { get; }
+
+ /// <summary>
+ /// Gets current object's velocity.
+ /// </summary>
+ Vector2 Velocity { get; }
+
/// <summary>
/// Snaps the current to the given position <see cref="IInnerPlayerControl"/>.
/// </summary>
/// Sets the color of the current <see cref="IInnerPlayerControl"/>.
/// Visible to all players.
/// </summary>
- /// <param name="colorId">A color for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetColorAsync(byte colorId);
-
/// <param name="colorType">A color for the player.</param>
- /// <inheritdoc cref="SetColorAsync(byte)" />
+ /// <returns>Task that must be awaited.</returns>
ValueTask SetColorAsync(ColorType colorType);
/// <summary>
/// Sets the hat of the current <see cref="IInnerPlayerControl"/>.
/// Visible to all players.
/// </summary>
- /// <param name="hatId">An hat for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetHatAsync(uint hatId);
-
/// <param name="hatType">An hat for the player.</param>
- /// <inheritdoc cref="SetHatAsync(uint)" />
+ /// <returns>Task that must be awaited.</returns>
ValueTask SetHatAsync(HatType hatType);
/// <summary>
/// Sets the pet of the current <see cref="IInnerPlayerControl"/>.
/// Visible to all players.
/// </summary>
- /// <param name="petId">A pet for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetPetAsync(uint petId);
-
/// <param name="petType">A pet for the player.</param>
- /// <inheritdoc cref="SetPetAsync(uint)" />
+ /// <returns>Task that must be awaited.</returns>
ValueTask SetPetAsync(PetType petType);
/// <summary>
/// Sets the skin of the current <see cref="IInnerPlayerControl"/>.
/// Visible to all players.
/// </summary>
- /// <param name="skinId">A skin for the player.</param>
- /// <returns>Task that must be awaited.</returns>
- ValueTask SetSkinAsync(uint skinId);
-
/// <param name="skinType">A skin for the player.</param>
- /// <inheritdoc cref="SetSkinAsync(uint)" />
+ /// <returns>Task that must be awaited.</returns>
ValueTask SetSkinAsync(SkinType skinType);
/// <summary>
ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null);
/// <summary>
- /// Sets the current to be murdered by an impostor <see cref="IInnerPlayerControl"/>.
- /// Visible to all players.
+ /// Murder <paramref name="target"/> player.
/// </summary>
- /// /// <param name="impostor">The Impostor who kill.</param>
+ /// <param name="target">Target player to murder.</param>
+ /// <exception cref="ImpostorProtocolException">Thrown when player is not the impostor.</exception>
+ /// <exception cref="ImpostorProtocolException">Thrown when player is dead.</exception>
+ /// <exception cref="ImpostorProtocolException">Thrown when target is dead.</exception>
/// <returns>Task that must be awaited.</returns>
- ValueTask SetMurderedByAsync(IClientPlayer impostor);
+ ValueTask MurderPlayerAsync(IInnerPlayerControl target);
}
}
using System;
using System.Collections.Generic;
using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.Customization;
namespace Impostor.Api.Net.Inner.Objects
{
/// <summary>
/// Gets the color of the player.
/// </summary>
- byte ColorId { get; }
+ ColorType Color { get; }
/// <summary>
/// Gets the hat of the player.
/// </summary>
- uint HatId { get; }
+ HatType Hat { get; }
/// <summary>
/// Gets the pet of the player.
/// </summary>
- uint PetId { get; }
+ PetType Pet { get; }
/// <summary>
/// Gets the skin of the player.
/// </summary>
- uint SkinId { get; }
+ SkinType Skin { get; }
/// <summary>
/// Gets a value indicating whether the player is an impostor.
--- /dev/null
+namespace Impostor.Server.Net.Inner
+{
+ public enum RpcCalls : byte
+ {
+ PlayAnimation = 0,
+ CompleteTask = 1,
+ SyncSettings = 2,
+ SetInfected = 3,
+ Exiled = 4,
+ CheckName = 5,
+ SetName = 6,
+ CheckColor = 7,
+ SetColor = 8,
+ SetHat = 9,
+ SetSkin = 10,
+ ReportDeadBody = 11,
+ MurderPlayer = 12,
+ SendChat = 13,
+ StartMeeting = 14,
+ SetScanner = 15,
+ SendChatNote = 16,
+ SetPet = 17,
+ SetStartCounter = 18,
+ EnterVent = 19,
+ ExitVent = 20,
+ SnapTo = 21,
+ Close = 22,
+ VotingComplete = 23,
+ CastVote = 24,
+ ClearVote = 25,
+ AddVote = 26,
+ CloseDoorsOfType = 27,
+ RepairSystem = 28,
+ SetTasks = 29,
+ UpdateGameData = 30,
+ CustomRpc = byte.MaxValue
+ }
+}
\ No newline at end of file
--- /dev/null
+namespace Impostor.Api.Net.Messages.Announcements
+{
+ public static class Message00UseCache
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ writer.StartMessage(AnnouncementsMessageFlags.UseCache);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Announcements
+{
+ public static class Message01Update
+ {
+ public static void Serialize(IMessageWriter writer, int id, string message)
+ {
+ writer.StartMessage(AnnouncementsMessageFlags.SetUpdate);
+ writer.WritePacked(id);
+ writer.Write(message);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader, out int id, out string message)
+ {
+ id = reader.ReadPackedInt32();
+ message = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.Announcements
+{
+ public static class Message02SetFreeWeekend
+ {
+ public static void Serialize(IMessageWriter writer, FreeWeekendState state)
+ {
+ writer.StartMessage(AnnouncementsMessageFlags.SetFreeWeekend);
+ writer.Write((byte)state);
+ writer.EndMessage();
+ }
+
+ public static void Deserialize(IMessageReader reader, out FreeWeekendState state)
+ {
+ state = (FreeWeekendState)reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.Announcements
+{
+ public class MessageHello
+ {
+ public static void Deserialize(IMessageReader reader, out int announcementVersion, out int id, out Language language)
+ {
+ reader.ReadByte(); // SendOption header, probably added by accident
+ announcementVersion = reader.ReadPackedInt32();
+ id = reader.ReadPackedInt32();
+ language = (Language)reader.ReadPackedInt32();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages
+{
+ public class AnnouncementsMessageFlags
+ {
+ public const byte UseCache = 0;
+ public const byte SetUpdate = 1;
+ public const byte SetFreeWeekend = 2;
+ }
+}
-using System.IO;
-using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth;
namespace Impostor.Api.Net.Messages.C2S
{
public static void Serialize(IMessageWriter writer, GameOptionsData gameOptionsData)
{
writer.StartMessage(MessageFlags.HostGame);
-
- using (var memory = new MemoryStream())
- using (var writerBin = new BinaryWriter(memory))
- {
- gameOptionsData.Serialize(writerBin, GameOptionsData.LatestVersion);
- writer.WriteBytesAndSize(memory.ToArray());
- }
-
+ gameOptionsData.Serialize(writer);
writer.EndMessage();
}
return GameOptionsData.DeserializeCreate(reader);
}
}
-}
\ No newline at end of file
+}
using System;
+using System.Numerics;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner;
namespace Impostor.Api.Net.Messages
{
float ReadSingle();
+ string ReadString(int length);
+
string ReadString();
ReadOnlyMemory<byte> ReadBytesAndSize();
void RemoveMessage(IMessageReader message);
IMessageReader Copy(int offset = 0);
+
+ T? ReadNetObject<T>(IGame game)
+ where T : IInnerNetObject;
+
+ Vector2 ReadVector2();
}
}
using System;
using System.Net;
+using System.Numerics;
using Impostor.Api.Games;
+using Impostor.Api.Net.Inner;
namespace Impostor.Api.Net.Messages
{
void WriteBytesAndSize(byte[] bytes, int offset, int length);
+ void Write(IInnerNetObject innerNetObject);
+
+ void Write(Vector2 vector);
+
/// <summary>
/// Starts a new message.
/// </summary>
/// <param name="type">New type of the message.</param>
void Clear(MessageType type);
}
-}
\ No newline at end of file
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc00PlayAnimation
+ {
+ public static void Serialize(IMessageWriter writer, TaskTypes task)
+ {
+ writer.Write((byte)task);
+ }
+
+ public static void Deserialize(IMessageReader reader, out TaskTypes task)
+ {
+ task = (TaskTypes)reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc01CompleteTask
+ {
+ public static void Serialize(IMessageWriter writer, uint taskId)
+ {
+ writer.WritePacked(taskId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out uint taskId)
+ {
+ taskId = reader.ReadPackedUInt32();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc02SyncSettings
+ {
+ public static void Serialize(IMessageWriter writer, GameOptionsData gameOptionsData)
+ {
+ gameOptionsData.Serialize(writer);
+ }
+
+ public static void Deserialize(IMessageReader reader, GameOptionsData gameOptionsData)
+ {
+ gameOptionsData.Deserialize(reader.ReadBytesAndSize());
+ }
+ }
+}
--- /dev/null
+using System;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc03SetInfected
+ {
+ public static void Serialize(IMessageWriter writer, byte[] infectedIds)
+ {
+ writer.WriteBytesAndSize(infectedIds);
+ }
+
+ public static void Deserialize(IMessageReader reader, out ReadOnlyMemory<byte> infectedIds)
+ {
+ infectedIds = reader.ReadBytesAndSize();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc04Exiled
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc05CheckName
+ {
+ public static void Serialize(IMessageWriter writer, string name)
+ {
+ writer.Write(name);
+ }
+
+ public static void Deserialize(IMessageReader reader, out string name)
+ {
+ name = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc06SetName
+ {
+ public static void Serialize(IMessageWriter writer, string name)
+ {
+ writer.Write(name);
+ }
+
+ public static void Deserialize(IMessageReader reader, out string name)
+ {
+ name = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth.Customization;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc07CheckColor
+ {
+ public static void Serialize(IMessageWriter writer, ColorType color)
+ {
+ writer.Write((byte)color);
+ }
+
+ public static void Deserialize(IMessageReader reader, out ColorType color)
+ {
+ color = (ColorType)reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth.Customization;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc08SetColor
+ {
+ public static void Serialize(IMessageWriter writer, ColorType color)
+ {
+ writer.Write((byte)color);
+ }
+
+ public static void Deserialize(IMessageReader reader, out ColorType color)
+ {
+ color = (ColorType)reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth.Customization;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc09SetHat
+ {
+ public static void Serialize(IMessageWriter writer, HatType hat)
+ {
+ writer.WritePacked((uint)hat);
+ }
+
+ public static void Deserialize(IMessageReader reader, out HatType hat)
+ {
+ hat = (HatType)reader.ReadPackedUInt32();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth.Customization;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc10SetSkin
+ {
+ public static void Serialize(IMessageWriter writer, SkinType skin)
+ {
+ writer.WritePacked((uint)skin);
+ }
+
+ public static void Deserialize(IMessageReader reader, out SkinType skin)
+ {
+ skin = (SkinType)reader.ReadPackedUInt32();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc11ReportDeadBody
+ {
+ public static void Serialize(IMessageWriter writer, byte targetId)
+ {
+ writer.Write(targetId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out byte targetId)
+ {
+ targetId = reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc12MurderPlayer
+ {
+ public static void Serialize(IMessageWriter writer, IInnerPlayerControl target)
+ {
+ writer.Write(target);
+ }
+
+ public static void Deserialize(IMessageReader reader, IGame game, out IInnerPlayerControl? target)
+ {
+ target = reader.ReadNetObject<IInnerPlayerControl>(game);
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc13SendChat
+ {
+ public static void Serialize(IMessageWriter writer, string message)
+ {
+ writer.Write(message);
+ }
+
+ public static void Deserialize(IMessageReader reader, out string message)
+ {
+ message = reader.ReadString();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc14StartMeeting
+ {
+ public static void Serialize(IMessageWriter writer, byte targetId)
+ {
+ writer.Write(targetId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out byte targetId)
+ {
+ targetId = reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc15SetScanner
+ {
+ public static void Serialize(IMessageWriter writer, bool on, byte scannerCount)
+ {
+ writer.Write(on);
+ writer.Write(scannerCount);
+ }
+
+ public static void Deserialize(IMessageReader reader, out bool on, out byte scannerCount)
+ {
+ on = reader.ReadBoolean();
+ scannerCount = reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc16SendChatNote
+ {
+ public static void Serialize(IMessageWriter writer, byte playerId, ChatNoteType chatNoteType)
+ {
+ writer.Write(playerId);
+ writer.Write((byte)chatNoteType);
+ }
+
+ public static void Deserialize(IMessageReader reader, out byte playerId, out ChatNoteType chatNoteType)
+ {
+ playerId = reader.ReadByte();
+ chatNoteType = (ChatNoteType)reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth.Customization;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc17SetPet
+ {
+ public static void Serialize(IMessageWriter writer, PetType pet)
+ {
+ writer.WritePacked((uint)pet);
+ }
+
+ public static void Deserialize(IMessageReader reader, out PetType pet)
+ {
+ pet = (PetType)reader.ReadPackedUInt32();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc18SetStartCounter
+ {
+ public static void Serialize(IMessageWriter writer, int sequenceId, sbyte startCounter)
+ {
+ writer.Write(sequenceId);
+ writer.Write(startCounter);
+ }
+
+ public static void Deserialize(IMessageReader reader, out int sequenceId, out sbyte startCounter)
+ {
+ sequenceId = reader.ReadPackedInt32();
+ startCounter = reader.ReadSByte();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc19EnterVent
+ {
+ public static void Serialize(IMessageWriter writer, int ventId)
+ {
+ writer.WritePacked(ventId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out int ventId)
+ {
+ ventId = reader.ReadPackedInt32();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc20ExitVent
+ {
+ public static void Serialize(IMessageWriter writer, int ventId)
+ {
+ writer.WritePacked(ventId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out int ventId)
+ {
+ ventId = reader.ReadPackedInt32();
+ }
+ }
+}
--- /dev/null
+using System.Numerics;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc21SnapTo
+ {
+ public static void Serialize(IMessageWriter writer, Vector2 position, ushort minSid)
+ {
+ writer.Write(position);
+ writer.Write(minSid);
+ }
+
+ public static void Deserialize(IMessageReader reader, out Vector2 position, out ushort minSid)
+ {
+ position = reader.ReadVector2();
+ minSid = reader.ReadUInt16();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc22Close
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ }
+ }
+}
--- /dev/null
+using System;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc23VotingComplete
+ {
+ public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie)
+ {
+ writer.WriteBytesAndSize(states);
+ writer.Write(playerId);
+ writer.Write(tie);
+ }
+
+ public static void Deserialize(IMessageReader reader, out ReadOnlyMemory<byte> states, out byte playerId, out bool tie)
+ {
+ states = reader.ReadBytesAndSize();
+ playerId = reader.ReadByte();
+ tie = reader.ReadBoolean();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc24CastVote
+ {
+ public static void Serialize(IMessageWriter writer, byte playerId, sbyte suspectPlayerId)
+ {
+ writer.Write(playerId);
+ writer.Write(suspectPlayerId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out byte playerId, out sbyte suspectPlayerId)
+ {
+ playerId = reader.ReadByte();
+ suspectPlayerId = reader.ReadSByte();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc25ClearVote
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ }
+
+ public static void Deserialize(IMessageReader reader)
+ {
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc26AddVote
+ {
+ public static void Serialize(IMessageWriter writer, int clientId, int targetClientId)
+ {
+ writer.Write(clientId);
+ writer.Write(targetClientId);
+ }
+
+ public static void Deserialize(IMessageReader reader, out int clientId, out int targetClientId)
+ {
+ clientId = reader.ReadInt32();
+ targetClientId = reader.ReadInt32();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc27CloseDoorsOfType
+ {
+ public static void Serialize(IMessageWriter writer, SystemTypes systemType)
+ {
+ writer.Write((byte)systemType);
+ }
+
+ public static void Deserialize(IMessageReader reader, out SystemTypes systemType)
+ {
+ systemType = (SystemTypes)reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc28RepairSystem
+ {
+ public static void Serialize(IMessageWriter writer, SystemTypes systemType, IInnerPlayerControl player, byte amount)
+ {
+ writer.Write((byte)systemType);
+ writer.Write(player);
+ writer.Write(amount);
+ }
+
+ public static void Deserialize(IMessageReader reader, IGame game, out SystemTypes systemType, out IInnerPlayerControl? player, out byte amount)
+ {
+ systemType = (SystemTypes)reader.ReadByte();
+ player = reader.ReadNetObject<IInnerPlayerControl>(game);
+ amount = reader.ReadByte();
+ }
+ }
+}
--- /dev/null
+using System;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc29SetTasks
+ {
+ public static void Serialize(IMessageWriter writer, byte playerId, ReadOnlyMemory<byte> taskTypeIds)
+ {
+ writer.Write(playerId);
+ writer.Write(taskTypeIds);
+ }
+
+ public static void Deserialize(IMessageReader reader, out byte playerId, out ReadOnlyMemory<byte> taskTypeIds)
+ {
+ playerId = reader.ReadByte();
+ taskTypeIds = reader.ReadBytesAndSize();
+ }
+ }
+}
--- /dev/null
+using System;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+ public static class Rpc30UpdateGameData
+ {
+ public static void Serialize(IMessageWriter writer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public static void Deserialize(IMessageReader reader, IInnerGameData gameData)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
<Rule Id="SA1633" Action="None" />
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
- <Rule Id="SA1101" Action="None" />
+ <Rule Id="SA1101" Action="None" />
+ <Rule Id="SA1111" Action="None" />
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.NamingRules">
<Rule Id="SA1309" Action="None" />
--- /dev/null
+namespace Impostor.Api.Reactor
+{
+ 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})";
+ }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Reactor
+{
+ public static class ModList
+ {
+ public static void Deserialize(IMessageReader reader, out ISet<Mod> mods)
+ {
+ var length = reader.ReadPackedInt32();
+
+ mods = new HashSet<Mod>(length);
+
+ for (var i = 0; i < length; i++)
+ {
+ var id = reader.ReadString();
+ var version = reader.ReadString();
+ var pluginSide = (PluginSide)reader.ReadByte();
+
+ mods.Add(new Mod(id, version, pluginSide));
+ }
+ }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Reactor
+{
+ public static class ModdedHandshakeC2S
+ {
+ public static void Deserialize(IMessageReader reader, out int clientVersion, out string name, out ISet<Mod>? mods)
+ {
+ clientVersion = reader.ReadInt32();
+ name = reader.ReadString();
+
+ if (reader.Length > reader.Position)
+ {
+ ModList.Deserialize(reader, out mods);
+ }
+ else
+ {
+ mods = null;
+ }
+ }
+ }
+}
--- /dev/null
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Reactor
+{
+ public static class ModdedHandshakeS2C
+ {
+ public static void Serialize(IMessageWriter writer, string serverBrand)
+ {
+ writer.StartMessage(byte.MaxValue);
+ writer.Write(serverBrand);
+ writer.EndMessage();
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Reactor
+{
+ /// <summary>
+ /// Plugin side used in modded handshake
+ /// </summary>
+ public enum PluginSide : byte
+ {
+ /// <summary>
+ /// Required by both sides, reject connection if missing on the other side
+ /// </summary>
+ Both,
+
+ /// <summary>
+ /// Required only by client
+ /// </summary>
+ ClientOnly,
+
+ /// <summary>
+ /// Required only by server
+ /// </summary>
+ ServerOnly,
+ }
+}
/// <para>The interpolated float result between the two float values.</para>
/// </returns>
public static float Lerp(float a, float b, float t) => a + ((b - a) * Clamp01(t));
+
+ public static float ReverseLerp(float t)
+ {
+ return Clamp((t - -40f) / (40f - -40f), 0f, 1f);
+ }
}
-}
\ No newline at end of file
+}
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net5.0</TargetFramework>
<DefineConstants>HAZEL_BAG</DefineConstants>
- <Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
using System;
using System.Buffers;
using System.Buffers.Binary;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using Impostor.Api;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Unity;
using Microsoft.Extensions.ObjectPool;
namespace Impostor.Hazel
return output;
}
- public string ReadString()
+ public string ReadString(int length)
{
- var len = ReadPackedInt32();
- var output = Encoding.UTF8.GetString(Buffer.AsSpan(ReadPosition, len));
- Position += len;
+ var output = Encoding.UTF8.GetString(Buffer.AsSpan(ReadPosition, length));
+ Position += length;
return output;
}
+ public string ReadString()
+ {
+ return ReadString(ReadPackedInt32());
+ }
+
public ReadOnlyMemory<byte> ReadBytesAndSize()
{
var len = ReadPackedInt32();
return reader;
}
+ public T ReadNetObject<T>(IGame game) where T : IInnerNetObject
+ {
+ return game.FindObjectByNetId<T>(ReadPackedUInt32());
+ }
+
+ public Vector2 ReadVector2()
+ {
+ var x = ReadUInt16() / (float) ushort.MaxValue;
+ var y = ReadUInt16() / (float) ushort.MaxValue;
+
+ return new Vector2(Mathf.Lerp(-40f, 40f, x), Mathf.Lerp(-40f, 40f, y));
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte FastByte()
{
using System;
using System.Collections.Generic;
using System.Net;
+using System.Numerics;
using System.Text;
+using Impostor.Api.Net.Inner;
+using Impostor.Api.Unity;
namespace Impostor.Hazel
{
this.Write(value.Value);
}
+ public void Write(IInnerNetObject innerNetObject)
+ {
+ if (innerNetObject == null)
+ {
+ this.Write(0);
+ }
+ else
+ {
+ this.WritePacked(innerNetObject.NetId);
+ }
+ }
+
+ public void Write(Vector2 vector)
+ {
+ Write((ushort)(Mathf.ReverseLerp(vector.X) * (double) ushort.MaxValue));
+ Write((ushort)(Mathf.ReverseLerp(vector.Y) * (double) ushort.MaxValue));
+ }
+
///
public void StartMessage(byte typeFlag)
{
//Get the ID form the packet
var id = (ushort)((b1 << 8) + b2);
- //Send an acknowledgement
- await SendAck(id);
-
/*
* It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
*
* So...
*/
+ var result = true;
+
lock (reliableDataPacketsMissing)
{
//Calculate overwritePointer
//See if we're missing it, else this packet is a duplicate as so we return false
if (!reliableDataPacketsMissing.Remove(id))
{
- return false;
+ result = false;
}
}
}
+
+ //Send an acknowledgement
+ await SendAck(id);
- return true;
+ return result;
}
/// <summary>
return false;
}
- return WriteIp(ipAddress, port);
+ var result = WriteIp(ipAddress, port);
+ OnSaved(ip, port);
+
+ return result;
}
/// <summary>
region.Serialize(writer);
- OnSaved(ip, port);
return true;
}
}
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
- <Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
services.AddSingleton<IEventListener, GameEventListener>();
services.AddSingleton<IEventListener, PlayerEventListener>();
services.AddSingleton<IEventListener, MeetingEventListener>();
+ services.AddSingleton<IEventListener, AnnouncementsListener>();
}
}
}
--- /dev/null
+using Impostor.Api.Events;
+using Impostor.Api.Events.Announcements;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Plugins.Example.Handlers
+{
+ public class AnnouncementsListener : IEventListener
+ {
+ private const int Id = 50;
+
+ [EventListener]
+ public void OnAnnouncementRequestEvent(IAnnouncementRequestEvent e)
+ {
+ if (e.Id == Id)
+ {
+ // Client already has announcement cached, lets just use that
+ e.Response.UseCached = true;
+ }
+ else
+ {
+ // Client is receiving this announcement for the first time, window will popup
+ e.Response.Announcement = new Announcement(Id, "Hello!");
+ }
+ }
+ }
+}
--- /dev/null
+using Impostor.Server.Utils;
+
+namespace Impostor.Server.Config
+{
+ internal class AnnouncementsServerConfig
+ {
+ private string? _resolvedListenIp;
+
+ public const string Section = "AnnouncementsServer";
+
+ public bool Enabled { get; set; } = true;
+
+ public string ListenIp { get; set; } = "0.0.0.0";
+
+ public ushort ListenPort { get; set; } = 22024;
+
+ public string ResolveListenIp()
+ {
+ return _resolvedListenIp ??= IpUtils.ResolveIp(ListenIp);
+ }
+ }
+}
{
public const string Section = "AntiCheat";
+ public bool Enabled { get; set; } = true;
+
public bool BanIpFromGame { get; set; } = true;
}
-}
\ No newline at end of file
+}
--- /dev/null
+using Impostor.Api.Events.Announcements;
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Server.Events.Announcements
+{
+ public class AnnouncementRequestEvent : IAnnouncementRequestEvent
+ {
+ public AnnouncementRequestEvent(int id, Language language)
+ {
+ Id = id;
+ Language = language;
+ }
+
+ public int Id { get; }
+
+ public Language Language { get; }
+
+ public IAnnouncementRequestEvent.IResponse Response { get; set; } = new AnnouncementResponse();
+
+ public class AnnouncementResponse : IAnnouncementRequestEvent.IResponse
+ {
+ public FreeWeekendState FreeWeekendState { get; set; } = FreeWeekendState.NotFree;
+
+ public bool UseCached { get; set; } = false;
+
+ public Announcement? Announcement { get; set; } = null;
+ }
+ }
+}
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using Impostor.Api;
using Impostor.Api.Events;
using Impostor.Api.Events.Managers;
using Impostor.Server.Events.Register;
await eventListener.InvokeAsync(handler, @event, _serviceProvider);
}
}
- catch (ImpostorCheatException)
- {
- throw;
- }
catch (Exception e)
{
_logger.LogError(e, "Invocation of event {0} threw an exception.", @event.GetType().Name);
-using Impostor.Api.Events.Player;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Player;
using Impostor.Api.Games;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Events.Player
{
- public class PlayerChatEvent : IPlayerChatEvent
+ public class PlayerChatEvent : IPlayerChatEvent, IEventCancelable
{
public PlayerChatEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, string message)
{
public IInnerPlayerControl PlayerControl { get; }
public string Message { get; }
+
+ public bool IsCancelled { get; set; }
}
}
using Impostor.Api.Events.Player;
using Impostor.Api.Games;
-using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Games;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
+using Microsoft.Extensions.ObjectPool;
namespace Impostor.Server.Events.Player
{
- // TODO: Finish and use event, needs to be pooled
- public class PlayerMovementEvent : IPlayerEvent
+ public class PlayerMovementEvent : IPlayerMovementEvent
{
- public PlayerMovementEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl)
+#pragma warning disable 8766
+ public IGame? Game { get; private set; }
+
+ public IClientPlayer? ClientPlayer { get; private set; }
+
+ public IInnerPlayerControl? PlayerControl { get; private set; }
+#pragma warning restore 8766
+
+ public void Reset(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl)
{
Game = game;
ClientPlayer = clientPlayer;
PlayerControl = playerControl;
}
- public IGame Game { get; }
+ public void Reset()
+ {
+ Game = null;
+ ClientPlayer = null;
+ PlayerControl = null;
+ }
- public IClientPlayer ClientPlayer { get; }
+ public class PlayerMovementEventObjectPolicy : IPooledObjectPolicy<PlayerMovementEvent>
+ {
+ public PlayerMovementEvent Create()
+ {
+ return new PlayerMovementEvent();
+ }
- public IInnerPlayerControl PlayerControl { get; }
+ public bool Return(PlayerMovementEvent obj)
+ {
+ obj.Reset();
+ return true;
+ }
+ }
}
}
+++ /dev/null
-using Impostor.Api.Net.Messages;
-using Impostor.Server.Net.Inner;
-using Impostor.Server.Net.State;
-
-namespace Impostor.Server
-{
- internal static class MessageReaderExtensions
- {
- public static T ReadNetObject<T>(this IMessageReader reader, Game game)
- where T : InnerNetObject
- {
- return game.FindObjectByNetId<T>(reader.ReadPackedUInt32());
- }
- }
-}
\ No newline at end of file
--- /dev/null
+using Impostor.Server.Events.Player;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Impostor.Server
+{
+ public static class ServiceProviderExtensions
+ {
+ public static void AddEventPools(this IServiceCollection services)
+ {
+ services.TryAddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
+
+ services.AddSingleton(serviceProvider =>
+ {
+ var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
+ var policy = ActivatorUtilities.CreateInstance<PlayerMovementEvent.PlayerMovementEventObjectPolicy>(serviceProvider);
+ return provider.Create(policy);
+ });
+ }
+ }
+}
<AssemblyTitle>Impostor.Server</AssemblyTitle>
<Product>Impostor.Server</Product>
<Copyright>Copyright © AeonLucid 2020</Copyright>
- <Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+ <!-- HACK: Some plugins depend on System.IO.Pipelines and expect the version to be 5.0.0.0, but .NET 5 only ships version 5.0.0.1. It is pretty hard to convince MSBuild to build against the correct DLL, so we special case it here. Remove once we move past .NET 5 -->
+ <PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
</Content>
</ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
--- /dev/null
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Announcements;
+using Impostor.Hazel;
+using Impostor.Hazel.Udp;
+using Impostor.Server.Config;
+using Impostor.Server.Events.Announcements;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net
+{
+ internal class AnnouncementsService : IHostedService
+ {
+ private readonly AnnouncementsServerConfig _config;
+ private readonly ObjectPool<MessageReader> _readerPool;
+ private readonly IEventManager _eventManager;
+ private UdpConnectionListener _connection;
+
+ public AnnouncementsService(IOptions<AnnouncementsServerConfig> config, ObjectPool<MessageReader> readerPool, IEventManager eventManager)
+ {
+ _config = config.Value;
+ _readerPool = readerPool;
+ _eventManager = eventManager;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ var endpoint = new IPEndPoint(IPAddress.Parse(_config.ResolveListenIp()), _config.ListenPort);
+
+ var mode = endpoint.AddressFamily switch
+ {
+ AddressFamily.InterNetwork => IPMode.IPv4,
+ AddressFamily.InterNetworkV6 => IPMode.IPv6,
+ _ => throw new InvalidOperationException()
+ };
+
+ _connection = new UdpConnectionListener(endpoint, _readerPool, mode)
+ {
+ NewConnection = OnNewConnection,
+ };
+
+ await _connection.StartAsync();
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ await _connection.DisposeAsync();
+ }
+
+ private async ValueTask OnNewConnection(NewConnectionEventArgs e)
+ {
+ MessageHello.Deserialize(e.HandshakeData, out var announcementVersion, out var id, out var language);
+
+ if (announcementVersion != 2)
+ {
+ await e.Connection.Disconnect("Unsupported announcement version");
+ return;
+ }
+
+ var @event = new AnnouncementRequestEvent(id, language);
+ await _eventManager.CallAsync(@event);
+
+ var response = @event.Response;
+
+ if (response.UseCached)
+ {
+ using var writer = MessageWriter.Get(MessageType.Reliable);
+ Message00UseCache.Serialize(writer);
+ await e.Connection.SendAsync(writer);
+ }
+
+ if (response.Announcement != null)
+ {
+ using var writer = MessageWriter.Get(MessageType.Reliable);
+ var announcement = response.Announcement.Value;
+ Message01Update.Serialize(writer, announcement.Id, announcement.Message);
+ await e.Connection.SendAsync(writer);
+ }
+
+ if (response.FreeWeekendState != FreeWeekendState.NotFree)
+ {
+ using var writer = MessageWriter.Get(MessageType.Reliable);
+ Message02SetFreeWeekend.Serialize(writer, response.FreeWeekendState);
+ await e.Connection.SendAsync(writer);
+ }
+
+ await e.Connection.Disconnect(null);
+ }
+ }
+}
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Games;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.C2S;
using Impostor.Api.Net.Messages.S2C;
+using Impostor.Api.Reactor;
using Impostor.Hazel;
using Impostor.Server.Config;
using Impostor.Server.Net.Manager;
private readonly ClientManager _clientManager;
private readonly GameManager _gameManager;
- public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection)
- : base(name, connection)
+ public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection, ISet<Mod> mods)
+ : base(name, connection, mods)
{
_logger = logger;
_antiCheatConfig = antiCheatOptions.Value;
_gameManager = gameManager;
}
+ public override async ValueTask<bool> ReportCheatAsync(CheatContext context, string message)
+ {
+ _logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{Context}] {Message}", Name, Id, context.Name, message);
+
+ if (!_antiCheatConfig.Enabled)
+ {
+ return false;
+ }
+
+ if (_antiCheatConfig.BanIpFromGame)
+ {
+ Player?.Game.BanIp(Connection.EndPoint.Address);
+ }
+
+ await DisconnectAsync(DisconnectReason.Hacking, context.Name + ": " + message);
+
+ return true;
+ }
+
public override async ValueTask HandleMessageAsync(IMessageReader reader, MessageType messageType)
{
var flag = reader.Tag;
// Handle packet.
using var readerCopy = reader.Copy();
- // TODO: Return value, either a bool (to cancel) or a writer (to cancel (null) or modify/overwrite).
- try
+ var verified = await Player.Game.HandleGameDataAsync(readerCopy, Player, toPlayer);
+ if (verified)
{
- var verified = await Player.Game.HandleGameDataAsync(readerCopy, Player, toPlayer);
- if (verified)
+ // Broadcast packet to all other players.
+ using (var writer = MessageWriter.Get(messageType))
{
- // Broadcast packet to all other players.
- using (var writer = MessageWriter.Get(messageType))
+ if (toPlayer)
{
- if (toPlayer)
- {
- var target = reader.ReadPackedInt32();
- reader.CopyTo(writer);
- await Player.Game.SendToAsync(writer, target);
- }
- else
- {
- reader.CopyTo(writer);
- await Player.Game.SendToAllExceptAsync(writer, Id);
- }
+ var target = reader.ReadPackedInt32();
+ reader.CopyTo(writer);
+ await Player.Game.SendToAsync(writer, target);
+ }
+ else
+ {
+ reader.CopyTo(writer);
+ await Player.Game.SendToAllExceptAsync(writer, Id);
}
}
}
- catch (ImpostorCheatException e)
- {
- if (_antiCheatConfig.BanIpFromGame)
- {
- Player.Game.BanIp(Connection.EndPoint.Address);
- }
-
- await DisconnectAsync(DisconnectReason.Hacking, e.Message);
- }
break;
}
Message08EndGameC2S.Deserialize(
reader,
out var gameOverReason);
-
+
await Player.Game.HandleEndGame(reader, gameOverReason);
break;
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
+using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.S2C;
+using Impostor.Api.Reactor;
using Impostor.Hazel;
using Impostor.Server.Net.State;
{
internal abstract class ClientBase : IClient
{
- protected ClientBase(string name, IHazelConnection connection)
+ protected ClientBase(string name, IHazelConnection connection, ISet<Mod> mods)
{
Name = name;
Connection = connection;
+ Mods = mods;
Items = new ConcurrentDictionary<object, object>();
+
+ ModIdMap = new Dictionary<int, string>();
+
+ var i = -1;
+
+ foreach (var mod in mods.OrderBy(x => x.Id))
+ {
+ if (mod.Side == PluginSide.Both)
+ {
+ ModIdMap[i--] = mod.Id;
+ }
+ }
}
public int Id { get; set; }
public string Name { get; }
+ public ISet<Mod> Mods { get; }
+
+ public Dictionary<int, string> ModIdMap { get; }
+
public IHazelConnection Connection { get; }
public IDictionary<object, object> Items { get; }
IClientPlayer? IClient.Player => Player;
+ public virtual ValueTask<bool> ReportCheatAsync(CheatContext context, string message)
+ {
+ return new ValueTask<bool>(false);
+ }
+
public abstract ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
public abstract ValueTask HandleDisconnectAsync(string reason);
using System;
+using System.Collections.Generic;
using Impostor.Api.Net;
+using Impostor.Api.Reactor;
using Microsoft.Extensions.DependencyInjection;
namespace Impostor.Server.Net.Factories
_serviceProvider = serviceProvider;
}
- public ClientBase Create(IHazelConnection connection, string name, int clientVersion)
+ public ClientBase Create(IHazelConnection connection, string name, int clientVersion, ISet<Mod> mods)
{
- var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, connection);
+ var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, connection, mods);
connection.Client = client;
return client;
}
-using Impostor.Api.Net;
+using System.Collections.Generic;
+using Impostor.Api.Net;
+using Impostor.Api.Reactor;
namespace Impostor.Server.Net.Factories
{
internal interface IClientFactory
{
- ClientBase Create(IHazelConnection connection, string name, int clientVersion);
+ ClientBase Create(IHazelConnection connection, string name, int clientVersion, ISet<Mod> mods);
}
}
\ No newline at end of file
--- /dev/null
+using System.Threading.Tasks;
+using Impostor.Api;
+using Impostor.Api.Net;
+using Impostor.Server.Net.Inner.Objects;
+
+namespace Impostor.Server.Net.Inner
+{
+ internal abstract partial class InnerNetObject
+ {
+ protected async ValueTask<bool> ValidateOwnership(CheatContext context, IClientPlayer sender)
+ {
+ if (!sender.IsOwner(this))
+ {
+ if (await sender.Client.ReportCheatAsync(context, $"Failed ownership check on {GetType().Name}"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected async ValueTask<bool> ValidateHost(CheatContext context, IClientPlayer sender)
+ {
+ if (!sender.IsHost)
+ {
+ if (await sender.Client.ReportCheatAsync(context, "Failed host check"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected async ValueTask<bool> ValidateTarget(CheatContext context, IClientPlayer sender, IClientPlayer? target)
+ {
+ if (target == null)
+ {
+ if (await sender.Client.ReportCheatAsync(context, "Failed target check"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected async ValueTask<bool> ValidateBroadcast(CheatContext context, IClientPlayer sender, IClientPlayer? target)
+ {
+ if (target != null)
+ {
+ if (await sender.Client.ReportCheatAsync(context, "Failed broadcast check"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected async ValueTask<bool> ValidateCmd(CheatContext context, IClientPlayer sender, IClientPlayer? target)
+ {
+ if (target == null || !target.IsHost)
+ {
+ if (await sender.Client.ReportCheatAsync(context, "Failed cmd check"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected async ValueTask<bool> ValidateImpostor(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, bool value = true)
+ {
+ if (playerInfo.IsImpostor != value)
+ {
+ if (await sender.Client.ReportCheatAsync(context, "Failed impostor check"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected async ValueTask<bool> UnregisteredCall(CheatContext context, IClientPlayer sender)
+ {
+ if (await sender.Client.ReportCheatAsync(context, "Client sent unregistered call"))
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages;
namespace Impostor.Server.Net.Inner
{
- internal abstract class InnerNetObject : GameObject, IInnerNetObject
+ internal abstract partial class InnerNetObject : GameObject, IInnerNetObject
{
private const int HostInheritId = -2;
public SpawnFlags SpawnFlags { get; internal set; }
- public abstract ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader);
-
- public abstract bool Serialize(IMessageWriter writer, bool initialState);
-
- public abstract void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState);
-
public bool IsOwnedBy(IClientPlayer player)
{
return OwnerId == player.Client.Id ||
(OwnerId == HostInheritId && player.IsHost);
}
+
+ public abstract ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState);
+
+ public abstract ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState);
+
+ public abstract ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader);
+
+ protected ValueTask<bool> HandleCustomRpc(IMessageReader reader, Game game)
+ {
+ var lengthOrShortId = reader.ReadPackedInt32();
+
+ var pluginId = lengthOrShortId < 0
+ ? game.Host!.Client.ModIdMap[lengthOrShortId]
+ : reader.ReadString(lengthOrShortId);
+
+ var id = reader.ReadPackedInt32();
+
+ // TODO handle custom rpcs
+
+ return ValueTask.FromResult(true);
+ }
}
-}
\ No newline at end of file
+}
var minSid = (ushort)(_lastSequenceId + 5U);
// Snap in the server.
- SnapTo(position, minSid);
+ await SnapToAsync(_game.GetClientPlayer(OwnerId), position, minSid);
// Broadcast to all clients.
using (var writer = _game.StartRpc(NetId, RpcCalls.SnapTo))
{
- WriteVector2(writer, position);
+ writer.Write(position);
writer.Write(_lastSequenceId);
await _game.FinishRpcAsync(writer);
}
-using System.Numerics;
+using System.Collections.Generic;
+using System.Numerics;
using System.Threading.Tasks;
using Impostor.Api;
-using Impostor.Api.Innersloth;
+using Impostor.Api.Events.Managers;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
+using Impostor.Server.Events.Player;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ObjectPool;
namespace Impostor.Server.Net.Inner.Objects.Components
{
internal partial class InnerCustomNetworkTransform : InnerNetObject
{
- private static readonly FloatRange XRange = new FloatRange(-40f, 40f);
- private static readonly FloatRange YRange = new FloatRange(-40f, 40f);
-
private readonly ILogger<InnerCustomNetworkTransform> _logger;
private readonly InnerPlayerControl _playerControl;
private readonly Game _game;
+ private readonly IEventManager _eventManager;
+ private readonly ObjectPool<PlayerMovementEvent> _pool;
private ushort _lastSequenceId;
- private Vector2 _targetSyncPosition;
- private Vector2 _targetSyncVelocity;
- public InnerCustomNetworkTransform(ILogger<InnerCustomNetworkTransform> logger, InnerPlayerControl playerControl, Game game)
+ public Vector2 Position { get; private set; }
+
+ public Vector2 Velocity { get; private set; }
+
+ public InnerCustomNetworkTransform(ILogger<InnerCustomNetworkTransform> logger, InnerPlayerControl playerControl, Game game, IEventManager eventManager, ObjectPool<PlayerMovementEvent> pool)
{
_logger = logger;
_playerControl = playerControl;
_game = game;
+ _game = game;
+ _eventManager = eventManager;
+ _pool = pool;
}
private static bool SidGreaterThan(ushort newSid, ushort prevSid)
{
- var num = (ushort)(prevSid + (uint) short.MaxValue);
+ var num = (ushort)(prevSid + (uint)short.MaxValue);
- return (int) prevSid < (int) num
+ return (int)prevSid < (int)num
? newSid > prevSid && newSid <= num
: newSid > prevSid || newSid <= num;
}
- private static void WriteVector2(IMessageWriter writer, Vector2 vec)
- {
- writer.Write((ushort)(XRange.ReverseLerp(vec.X) * (double) ushort.MaxValue));
- writer.Write((ushort)(YRange.ReverseLerp(vec.Y) * (double) ushort.MaxValue));
- }
-
- private static Vector2 ReadVector2(IMessageReader reader)
- {
- var v1 = reader.ReadUInt16() / (float) ushort.MaxValue;
- var v2 = reader.ReadUInt16() / (float) ushort.MaxValue;
-
- return new Vector2(XRange.Lerp(v1), YRange.Lerp(v2));
- }
-
- public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
- {
- if (call == RpcCalls.SnapTo)
- {
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to an unowned {nameof(InnerPlayerControl)}");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to a specific player instead of broadcast");
- }
-
- if (!sender.Character.PlayerInfo.IsImpostor)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} as crewmate");
- }
-
- SnapTo(ReadVector2(reader), reader.ReadUInt16());
- }
- else
- {
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerCustomNetworkTransform), call);
- }
-
- return default;
- }
-
- public override bool Serialize(IMessageWriter writer, bool initialState)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
if (initialState)
{
writer.Write(_lastSequenceId);
- WriteVector2(writer, _targetSyncPosition);
- WriteVector2(writer, _targetSyncVelocity);
- return true;
+ writer.Write(Position);
+ writer.Write(Velocity);
+ return new ValueTask<bool>(true);
}
// TODO: DirtyBits == 0 return false.
_lastSequenceId++;
writer.Write(_lastSequenceId);
- WriteVector2(writer, _targetSyncPosition);
- WriteVector2(writer, _targetSyncVelocity);
- return true;
+ writer.Write(Position);
+ writer.Write(Velocity);
+ return new ValueTask<bool>(true);
}
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
var sequenceId = reader.ReadUInt16();
if (initialState)
{
_lastSequenceId = sequenceId;
- _targetSyncPosition = ReadVector2(reader);
- _targetSyncVelocity = ReadVector2(reader);
+ await SetPositionAsync(sender, reader.ReadVector2(), reader.ReadVector2());
}
else
{
- if (!sender.IsOwner(this))
+ if (!await ValidateOwnership(CheatContext.Deserialize, sender) || !await ValidateBroadcast(CheatContext.Deserialize, sender, target))
{
- throw new ImpostorCheatException($"Client attempted to send unowned {nameof(InnerCustomNetworkTransform)} data");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client attempted to send {nameof(InnerCustomNetworkTransform)} data to a specific player, must be broadcast");
+ return;
}
if (!SidGreaterThan(sequenceId, _lastSequenceId))
}
_lastSequenceId = sequenceId;
- _targetSyncPosition = ReadVector2(reader);
- _targetSyncVelocity = ReadVector2(reader);
+ await SetPositionAsync(sender, reader.ReadVector2(), reader.ReadVector2());
}
}
- private void SnapTo(Vector2 position, ushort minSid)
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (call == RpcCalls.SnapTo)
+ {
+ if (!await ValidateOwnership(call, sender) || !await ValidateImpostor(RpcCalls.MurderPlayer, sender, _playerControl.PlayerInfo))
+ {
+ return false;
+ }
+
+ Rpc21SnapTo.Deserialize(reader, out var position, out var minSid);
+
+ await SnapToAsync(sender, position, minSid);
+ return true;
+ }
+
+ return await UnregisteredCall(call, sender);
+ }
+
+ internal async ValueTask SetPositionAsync(IClientPlayer sender, Vector2 position, Vector2 velocity)
+ {
+ Position = position;
+ Velocity = velocity;
+
+ var playerMovementEvent = _pool.Get();
+ playerMovementEvent.Reset(_game, sender, _playerControl);
+ await _eventManager.CallAsync(playerMovementEvent);
+ _pool.Return(playerMovementEvent);
+ }
+
+ private ValueTask SnapToAsync(IClientPlayer sender, Vector2 position, ushort minSid)
{
if (!SidGreaterThan(minSid, _lastSequenceId))
{
- return;
+ return default;
}
_lastSequenceId = minSid;
- _targetSyncPosition = position;
- _targetSyncVelocity = Vector2.Zero;
+ return SetPositionAsync(sender, position, Velocity);
}
}
}
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
-using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
_game = game;
}
- public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
- if (call != RpcCalls.EnterVent && call != RpcCalls.ExitVent)
- {
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerPlayerPhysics), call);
- return;
- }
+ throw new NotImplementedException();
+ }
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {call} to an unowned {nameof(InnerPlayerControl)}");
- }
+ public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
- if (target != null)
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (!await ValidateOwnership(call, sender) || !await ValidateImpostor(RpcCalls.MurderPlayer, sender, _playerControl.PlayerInfo))
{
- throw new ImpostorCheatException($"Client sent {call} to a specific player instead of broadcast");
+ return false;
}
- if (!sender.Character.PlayerInfo.IsImpostor)
+ int ventId;
+
+ switch (call)
{
- throw new ImpostorCheatException($"Client sent {call} as crewmate");
- }
+ case RpcCalls.EnterVent:
+ Rpc19EnterVent.Deserialize(reader, out ventId);
+ break;
- var ventId = reader.ReadPackedUInt32();
- var ventEnter = call == RpcCalls.EnterVent;
+ case RpcCalls.ExitVent:
+ Rpc19EnterVent.Deserialize(reader, out ventId);
+ break;
- await _eventManager.CallAsync(new PlayerVentEvent(_game, sender, _playerControl, (VentLocation)ventId, ventEnter));
+ case RpcCalls.CustomRpc:
+ return await HandleCustomRpc(reader, _game);
- return;
- }
+ default:
+ return await UnregisteredCall(call, sender);
+ }
- public override bool Serialize(IMessageWriter writer, bool initialState)
- {
- throw new NotImplementedException();
- }
+ await _eventManager.CallAsync(new PlayerVentEvent(_game, sender, _playerControl, (VentLocation)ventId, call == RpcCalls.EnterVent));
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
- {
- throw new NotImplementedException();
+ return true;
}
}
}
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
_votes = new Dictionary<int, int[]>();
}
- public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
- {
- if (call != RpcCalls.AddVote)
- {
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerVoteBanSystem), call);
- return default;
- }
-
- var clientId = reader.ReadInt32();
- if (clientId != sender.Client.Id)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.AddVote)} as other client");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to wrong destinition, must be broadcast");
- }
-
- var targetClientId = reader.ReadInt32();
-
- // TODO: Use.
-
- return default;
- }
-
- public override bool Serialize(IMessageWriter writer, bool initialState)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
}
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
- if (!sender.IsHost)
+ if (!await ValidateHost(CheatContext.Deserialize, sender))
{
- throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerShipStatus)} as non-host");
+ return;
}
var votes = _votes;
}
}
}
+
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (call == RpcCalls.AddVote)
+ {
+ if (!await ValidateOwnership(call, sender))
+ {
+ return false;
+ }
+
+ Rpc26AddVote.Deserialize(reader, out var clientId, out var targetClientId);
+
+ if (clientId != sender.Client.Id)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.AddVote, $"Client sent {nameof(RpcCalls.AddVote)} as other client"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return await UnregisteredCall(call, sender);
+ }
}
-}
\ No newline at end of file
+}
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api;
-using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Net.Inner.Objects.Components;
using Impostor.Server.Net.State;
using Microsoft.Extensions.DependencyInjection;
return _allPlayers.TryGetValue(id, out var player) ? player : null;
}
- public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
- switch (call)
+ throw new NotImplementedException();
+ }
+
+ public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!await ValidateHost(CheatContext.Deserialize, sender))
{
- case RpcCalls.SetTasks:
+ return;
+ }
+
+ if (initialState)
+ {
+ var num = reader.ReadPackedInt32();
+
+ for (var i = 0; i < num; i++)
{
- if (!sender.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} but was not a host");
- }
+ var playerId = reader.ReadByte();
+ var playerInfo = new InnerPlayerInfo(playerId);
+
+ playerInfo.Deserialize(reader);
- if (target != null)
+ if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} to a specific player instead of broadcast");
+ throw new ImpostorException("Failed to add player to InnerGameData.");
}
+ }
+ }
+ else
+ {
+ throw new NotImplementedException("This shouldn't happen, according to Among Us disassembly.");
+ }
+ }
- var playerId = reader.ReadByte();
- var taskTypeIds = reader.ReadBytesAndSize();
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (!await ValidateHost(call, sender))
+ {
+ return false;
+ }
+ switch (call)
+ {
+ case RpcCalls.SetTasks:
+ {
+ Rpc29SetTasks.Deserialize(reader, out var playerId, out var taskTypeIds);
SetTasks(playerId, taskTypeIds);
break;
}
case RpcCalls.UpdateGameData:
{
- if (!sender.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} but was not a host");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} to a specific player instead of broadcast");
- }
-
while (reader.Position < reader.Length)
{
using var message = reader.ReadMessage();
break;
}
- default:
- {
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerGameData), call);
- break;
- }
- }
-
- return default;
- }
-
- public override bool Serialize(IMessageWriter writer, bool initialState)
- {
- throw new NotImplementedException();
- }
+ case RpcCalls.CustomRpc:
+ return await HandleCustomRpc(reader, _game);
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
- {
- if (!sender.IsHost)
- {
- throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerGameData)} as non-host");
+ default:
+ return await UnregisteredCall(call, sender);
}
- if (initialState)
- {
- var num = reader.ReadPackedInt32();
-
- for (var i = 0; i < num; i++)
- {
- var playerId = reader.ReadByte();
- var playerInfo = new InnerPlayerInfo(playerId);
-
- playerInfo.Deserialize(reader);
-
- if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
- {
- throw new ImpostorException("Failed to add player to InnerGameData.");
- }
- }
- }
- else
- {
- throw new NotImplementedException("This shouldn't happen, according to Among Us disassembly.");
- }
+ return true;
}
internal void AddPlayer(InnerPlayerControl control)
Components.Add(this);
}
- public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new System.NotImplementedException();
}
- public override bool Serialize(IMessageWriter writer, bool initialState)
+ public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
throw new System.NotImplementedException();
}
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ public override ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
throw new System.NotImplementedException();
}
}
-}
\ No newline at end of file
+}
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Meeting;
using Impostor.Server.Events.Player;
using Impostor.Server.Net.State;
.ToArray();
}
- public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
- switch (call)
+ throw new NotImplementedException();
+ }
+
+ public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!await ValidateHost(CheatContext.Deserialize, sender) || !await ValidateBroadcast(CheatContext.Deserialize, sender, target))
{
- case RpcCalls.Close:
+ return;
+ }
+
+ if (initialState)
+ {
+ PopulateButtons(0);
+
+ foreach (var playerState in _playerStates)
{
- if (!sender.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Close)} but was not a host");
- }
+ playerState.Deserialize(reader);
- if (target != null)
+ if (playerState.DidReport)
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Close)} to a specific player instead of broadcast");
+ ReporterId = playerState.TargetPlayerId;
}
-
- break;
}
+ }
+ else
+ {
+ var num = reader.ReadPackedUInt32();
- case RpcCalls.VotingComplete:
+ for (var i = 0; i < _playerStates.Length; i++)
{
- if (!sender.IsHost)
+ if ((num & 1 << i) != 0)
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.VotingComplete)} but was not a host");
+ _playerStates[i].Deserialize(reader);
}
+ }
+ }
+ }
- if (target != null)
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ switch (call)
+ {
+ case RpcCalls.Close:
+ {
+ if (!await ValidateHost(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.VotingComplete)} to a specific player instead of broadcast");
+ return false;
}
- var states = reader.ReadBytesAndSize();
- var playerId = reader.ReadByte();
- var tie = reader.ReadBoolean();
+ Rpc22Close.Deserialize(reader);
+ break;
+ }
- if (playerId != byte.MaxValue)
+ case RpcCalls.VotingComplete:
+ {
+ if (!await ValidateHost(call, sender))
{
- var player = _game.GameNet.GameData.GetPlayerById(playerId);
- if (player != null)
- {
- player.Controller.Die(DeathReason.Exile);
- await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, player.Controller));
- }
+ return false;
}
- await _eventManager.CallAsync(new MeetingEndedEvent(_game, this));
-
+ Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
+ await HandleVotingComplete(sender, states, playerId, tie);
break;
}
case RpcCalls.CastVote:
{
- var srcPlayerId = reader.ReadByte();
- if (srcPlayerId != sender.Character.PlayerId)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to an unowned {nameof(InnerPlayerControl)}");
- }
-
- // Host broadcasts vote to others.
- if (sender.IsHost && target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to a specific player instead of broadcast");
- }
+ Rpc24CastVote.Deserialize(reader, out var playerId, out var suspectPlayerId);
+ return await HandleCastVote(sender, target, playerId, suspectPlayerId);
+ }
- // Player sends vote to host.
- if (target == null || !target.IsHost)
+ case RpcCalls.ClearVote:
+ {
+ if (!await ValidateHost(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to wrong destinition, must be host");
+ return false;
}
- var targetPlayerId = reader.ReadByte();
+ Rpc25ClearVote.Deserialize(reader);
break;
}
+ case RpcCalls.CustomRpc:
+ return await HandleCustomRpc(reader, _game);
+
default:
- {
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerMeetingHud), call);
- break;
- }
+ return await UnregisteredCall(call, sender);
}
- }
- public override bool Serialize(IMessageWriter writer, bool initialState)
- {
- throw new NotImplementedException();
+ return true;
}
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ private async ValueTask HandleVotingComplete(ClientPlayer sender, ReadOnlyMemory<byte> states, byte playerId, bool tie)
{
- if (!sender.IsHost)
+ if (playerId != byte.MaxValue)
{
- throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerMeetingHud)} as non-host");
+ var player = _game.GameNet.GameData.GetPlayerById(playerId);
+ if (player != null)
+ {
+ player.Controller.Die(DeathReason.Exile);
+ await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, player.Controller));
+ }
}
- if (target != null)
- {
- throw new ImpostorCheatException($"Client attempted to send {nameof(InnerMeetingHud)} data to a specific player, must be broadcast");
- }
+ await _eventManager.CallAsync(new MeetingEndedEvent(_game, this));
+ }
- if (initialState)
+ private async ValueTask<bool> HandleCastVote(ClientPlayer sender, ClientPlayer? target, byte playerId, sbyte suspectPlayerId)
+ {
+ if (sender.IsHost)
{
- PopulateButtons(0);
-
- foreach (var playerState in _playerStates)
+ if (!await ValidateBroadcast(RpcCalls.CastVote, sender, target))
{
- playerState.Deserialize(reader);
-
- if (playerState.DidReport)
- {
- ReporterId = playerState.TargetPlayerId;
- }
+ return false;
}
}
else
{
- var num = reader.ReadPackedUInt32();
+ if (!await ValidateCmd(RpcCalls.CastVote, sender, target))
+ {
+ return false;
+ }
+ }
- for (var i = 0; i < _playerStates.Length; i++)
+ if (playerId != sender.Character!.PlayerId)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CastVote, $"Client sent {nameof(RpcCalls.CastVote)} to an unowned {nameof(InnerPlayerControl)}"))
{
- if ((num & 1 << i) != 0)
- {
- _playerStates[i].Deserialize(reader);
- }
+ return false;
}
}
+
+ return true;
}
}
}
using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
-using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Inner.Objects.Components;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
namespace Impostor.Server.Net.Inner.Objects
await _game.FinishRpcAsync(writer);
}
- public async ValueTask SetColorAsync(byte colorId)
+ public async ValueTask SetColorAsync(ColorType color)
{
- PlayerInfo.ColorId = colorId;
+ PlayerInfo.Color = color;
using var writer = _game.StartRpc(NetId, RpcCalls.SetColor);
- writer.Write(colorId);
+ Rpc08SetColor.Serialize(writer, color);
await _game.FinishRpcAsync(writer);
}
- public ValueTask SetColorAsync(ColorType colorType)
+ public async ValueTask SetHatAsync(HatType hat)
{
- return SetColorAsync((byte)colorType);
- }
-
- public async ValueTask SetHatAsync(uint hatId)
- {
- PlayerInfo.HatId = hatId;
+ PlayerInfo.Hat = hat;
using var writer = _game.StartRpc(NetId, RpcCalls.SetHat);
- writer.WritePacked(hatId);
+ Rpc09SetHat.Serialize(writer, hat);
await _game.FinishRpcAsync(writer);
}
- public ValueTask SetHatAsync(HatType hatType)
+ public async ValueTask SetPetAsync(PetType pet)
{
- return SetHatAsync((uint)hatType);
- }
-
- public async ValueTask SetPetAsync(uint petId)
- {
- PlayerInfo.PetId = petId;
+ PlayerInfo.Pet = pet;
using var writer = _game.StartRpc(NetId, RpcCalls.SetPet);
- writer.WritePacked(petId);
+ Rpc17SetPet.Serialize(writer, pet);
await _game.FinishRpcAsync(writer);
}
- public ValueTask SetPetAsync(PetType petType)
- {
- return SetPetAsync((uint)petType);
- }
-
- public async ValueTask SetSkinAsync(uint skinId)
+ public async ValueTask SetSkinAsync(SkinType skin)
{
- PlayerInfo.SkinId = skinId;
+ PlayerInfo.Skin = skin;
using var writer = _game.StartRpc(NetId, RpcCalls.SetSkin);
- writer.WritePacked(skinId);
+ Rpc10SetSkin.Serialize(writer, skin);
await _game.FinishRpcAsync(writer);
}
- public ValueTask SetSkinAsync(SkinType skinType)
- {
- return SetSkinAsync((uint)skinType);
- }
-
public async ValueTask SendChatAsync(string text)
{
using var writer = _game.StartRpc(NetId, RpcCalls.SendChat);
await _game.FinishRpcAsync(writer, player.OwnerId);
}
- public async ValueTask SetMurderedByAsync(IClientPlayer impostor)
+ public async ValueTask MurderPlayerAsync(IInnerPlayerControl target)
{
- if (impostor.Character == null)
- {
- throw new ImpostorException("Character is null.");
- }
-
- if (!impostor.Character.PlayerInfo.IsImpostor)
+ if (!PlayerInfo.IsImpostor)
{
- throw new ImpostorProtocolException("Plugin tried to murder a player while the impostor specified was not an impostor.");
+ throw new ImpostorProtocolException("Tried to murder a player, but murderer was not the impostor.");
}
- if (impostor.Character.PlayerInfo.IsDead)
+ if (PlayerInfo.IsDead)
{
- throw new ImpostorProtocolException("Plugin tried to murder a player while the impostor specified was dead.");
+ throw new ImpostorProtocolException("Tried to murder a player, but murderer was not alive.");
}
- if (PlayerInfo.IsDead)
+ if (target.PlayerInfo.IsDead)
{
- return;
+ throw new ImpostorProtocolException("Tried to murder a player, but target was not alive.");
}
- // Update player.
- Die(DeathReason.Kill);
+ ((InnerPlayerControl)target).Die(DeathReason.Kill);
- // Send RPC.
- using var writer = _game.StartRpc(impostor.Character.NetId, RpcCalls.MurderPlayer);
- writer.WritePacked(NetId);
+ using var writer = _game.StartRpc(NetId, RpcCalls.MurderPlayer);
+ Rpc12MurderPlayer.Serialize(writer, target);
await _game.FinishRpcAsync(writer);
- // Notify plugins.
- await _eventManager.CallAsync(new PlayerMurderEvent(_game, impostor, impostor.Character, this));
+ await _eventManager.CallAsync(new PlayerMurderEvent(_game, _game.GetClientPlayer(OwnerId), this, target));
}
}
}
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Net;
+using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
using Impostor.Server.Net.Inner.Objects.Components;
using Impostor.Server.Net.State;
+using Impostor.Server.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
private readonly ILogger<InnerPlayerControl> _logger;
private readonly IEventManager _eventManager;
private readonly Game _game;
+ private readonly ServerEnvironment _serverEnvironment;
- public InnerPlayerControl(ILogger<InnerPlayerControl> logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game)
+ public InnerPlayerControl(ILogger<InnerPlayerControl> logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game, ServerEnvironment serverEnvironment)
{
_logger = logger;
_eventManager = eventManager;
_game = game;
+ _serverEnvironment = serverEnvironment;
Physics = ActivatorUtilities.CreateInstance<InnerPlayerPhysics>(serviceProvider, this, _eventManager, _game);
NetworkTransform = ActivatorUtilities.CreateInstance<InnerCustomNetworkTransform>(serviceProvider, this, _game);
public InnerPlayerInfo PlayerInfo { get; internal set; }
- public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ internal Queue<string> RequestedPlayerName { get; } = new Queue<string>();
+
+ internal Queue<ColorType> RequestedColorId { get; } = new Queue<ColorType>();
+
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ {
+ if (!await ValidateHost(CheatContext.Deserialize, sender))
+ {
+ return;
+ }
+
+ if (initialState)
+ {
+ IsNew = reader.ReadBoolean();
+ }
+
+ PlayerId = reader.ReadByte();
+ }
+
+ internal void Die(DeathReason reason)
+ {
+ PlayerInfo.IsDead = true;
+ PlayerInfo.LastDeathReason = reason;
+ }
+
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
switch (call)
{
- // Play an animation.
case RpcCalls.PlayAnimation:
{
- if (!sender.IsOwner(this))
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.PlayAnimation)} to an unowned {nameof(InnerPlayerControl)}");
+ return false;
}
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.PlayAnimation)} to a specific player instead of broadcast");
- }
-
- var animation = reader.ReadByte();
+ Rpc00PlayAnimation.Deserialize(reader, out var task);
break;
}
- // Complete a task.
case RpcCalls.CompleteTask:
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CompleteTask)} to an unowned {nameof(InnerPlayerControl)}");
- }
-
- if (target != null)
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CompleteTask)} to a specific player instead of broadcast");
- }
-
- var taskId = reader.ReadPackedUInt32();
- var task = PlayerInfo.Tasks[(int)taskId];
- if (task == null)
- {
- _logger.LogWarning($"Client sent {nameof(RpcCalls.CompleteTask)} with a taskIndex that is not in their {nameof(InnerPlayerInfo)}");
- }
- else
- {
- task.Complete = true;
- await _eventManager.CallAsync(new PlayerCompletedTaskEvent(_game, sender, this, task));
+ return false;
}
+ Rpc01CompleteTask.Deserialize(reader, out var taskId);
+ await HandleCompleteTask(sender, taskId);
break;
}
- // Update GameOptions.
case RpcCalls.SyncSettings:
{
- if (!sender.IsHost)
+ if (!await ValidateHost(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SyncSettings)} but was not a host");
+ return false;
}
- _game.Options.Deserialize(reader.ReadBytesAndSize());
+ Rpc02SyncSettings.Deserialize(reader, _game.Options);
break;
}
- // Set Impostors.
case RpcCalls.SetInfected:
{
- if (!sender.IsHost)
+ if (!await ValidateOwnership(call, sender) || !await ValidateHost(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetInfected)} but was not a host");
+ return false;
}
- var length = reader.ReadPackedInt32();
-
- for (var i = 0; i < length; i++)
- {
- var playerId = reader.ReadByte();
- var player = _game.GameNet.GameData.GetPlayerById(playerId);
- if (player != null)
- {
- player.IsImpostor = true;
- }
- }
+ Rpc03SetInfected.Deserialize(reader, out var infectedIds);
+ await HandleSetInfected(infectedIds);
+ break;
+ }
- if (_game.GameState == GameStates.Starting)
+ case RpcCalls.CheckName:
+ {
+ if (!await ValidateOwnership(call, sender) || !await ValidateCmd(call, sender, target))
{
- await _game.StartedAsync();
+ return false;
}
- break;
+ Rpc05CheckName.Deserialize(reader, out var name);
+ return await HandleCheckName(sender, name);
}
- // Player was voted out.
- case RpcCalls.Exiled:
+ case RpcCalls.SetName:
{
- if (!sender.IsHost)
+ if (!await ValidateHost(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} but was not a host");
+ return false;
}
- if (target != null)
+ Rpc06SetName.Deserialize(reader, out var name);
+ return await HandleSetName(sender, name);
+ }
+
+ case RpcCalls.CheckColor:
+ {
+ if (!await ValidateOwnership(call, sender) || !await ValidateCmd(call, sender, target))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} to a specific player instead of broadcast");
+ return false;
}
- // TODO: Not hit?
- Die(DeathReason.Exile);
-
- await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, this));
- break;
+ Rpc07CheckColor.Deserialize(reader, out var color);
+ return await HandleCheckColor(sender, color);
}
- // Validates the player name at the host.
- case RpcCalls.CheckName:
+ case RpcCalls.SetColor:
{
- if (!sender.IsOwner(this))
+ if (!await ValidateHost(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to an unowned {nameof(InnerPlayerControl)}");
+ return false;
}
- if (target == null || !target.IsHost)
+ Rpc08SetColor.Deserialize(reader, out var color);
+ return await HandleSetColor(sender, color);
+ }
+
+ case RpcCalls.SetHat:
+ {
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to the wrong player");
+ return false;
}
- var name = reader.ReadString();
+ Rpc09SetHat.Deserialize(reader, out var hat);
+ return await HandleSetHat(sender, hat);
+ }
- if (name.Length > 10)
+ case RpcCalls.SetSkin:
+ {
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} with name exceeding 10 characters");
+ return false;
}
- if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} with name containing illegal characters");
- }
+ Rpc10SetSkin.Deserialize(reader, out var skin);
+ return await HandleSetSkin(sender, skin);
+ }
- if (sender.Client.Name != name)
+ case RpcCalls.ReportDeadBody:
+ {
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with name not matching his name from handshake");
+ return false;
}
- PlayerInfo.RequestedPlayerName = name;
+ Rpc11ReportDeadBody.Deserialize(reader, out var targetId);
break;
}
- // Update the name of a player.
- case RpcCalls.SetName:
+ case RpcCalls.MurderPlayer:
{
- if (!sender.IsHost)
+ if (!await ValidateOwnership(call, sender) || !await ValidateImpostor(RpcCalls.MurderPlayer, sender, PlayerInfo))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} but was not a host");
+ return false;
}
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} to a specific player instead of broadcast");
- }
-
- var name = reader.ReadString();
+ Rpc12MurderPlayer.Deserialize(reader, _game, out var murdered);
+ return await HandleMurderPlayer(sender, murdered);
+ }
- if (sender.IsOwner(this))
+ case RpcCalls.SendChat:
+ {
+ if (!await ValidateOwnership(call, sender))
{
- if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with a name that is already used");
- }
-
- if (sender.Client.Name != name)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with name not matching his name from handshake");
- }
+ return false;
}
- else
- {
- if (PlayerInfo.RequestedPlayerName == null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it");
- }
- var expected = PlayerInfo.RequestedPlayerName!;
-
- if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
- {
- var i = 1;
- while (true)
- {
- string text = expected + " " + i;
-
- if (_game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text))
- {
- expected = text;
- break;
- }
-
- i++;
- }
- }
+ Rpc13SendChat.Deserialize(reader, out var message);
+ return await HandleSendChat(sender, message);
+ }
- if (name != expected)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with incorrect name");
- }
+ case RpcCalls.StartMeeting:
+ {
+ if (!await ValidateHost(call, sender))
+ {
+ return false;
}
- PlayerInfo.PlayerName = name;
- PlayerInfo.RequestedPlayerName = null;
+ Rpc14StartMeeting.Deserialize(reader, out var targetId);
+ await HandleStartMeeting(targetId);
break;
}
- // Validates the color at the host.
- case RpcCalls.CheckColor:
+ case RpcCalls.SetScanner:
{
- if (!sender.IsOwner(this))
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to an unowned {nameof(InnerPlayerControl)}");
+ return false;
}
- if (target == null || !target.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to the wrong player");
- }
-
- var color = reader.ReadByte();
+ Rpc15SetScanner.Deserialize(reader, out var on, out var scannerCount);
+ break;
+ }
- if (color > Enum.GetValues<ColorType>().Length)
+ case RpcCalls.SendChatNote:
+ {
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} with invalid color");
+ return false;
}
- PlayerInfo.RequestedColorId = color;
+ Rpc16SendChatNote.Deserialize(reader, out var playerId, out var chatNoteType);
break;
}
- // Update the color of a player.
- case RpcCalls.SetColor:
+ case RpcCalls.SetPet:
{
- if (!sender.IsHost)
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} but was not a host");
+ return false;
}
- if (target != null)
+ Rpc17SetPet.Deserialize(reader, out var pet);
+ return await HandleSetPet(sender, pet);
+ }
+
+ case RpcCalls.SetStartCounter:
+ {
+ if (!await ValidateOwnership(call, sender))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} to a specific player instead of broadcast");
+ return false;
}
- var color = reader.ReadByte();
+ Rpc18SetStartCounter.Deserialize(reader, out var sequenceId, out var startCounter);
+ return await HandleSetStartCounter(sender, sequenceId, startCounter);
+ }
- if (sender.IsOwner(this))
- {
- if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.ColorId == color))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} with a color that is already used");
- }
- }
- else
- {
- if (PlayerInfo.RequestedColorId == null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} for a player that didn't request it");
- }
+ case RpcCalls.CustomRpc:
+ return await HandleCustomRpc(reader, _game);
- var expected = PlayerInfo.RequestedColorId!.Value;
+ default:
+ return await UnregisteredCall(call, sender);
+ }
- while (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.ColorId == expected))
- {
- expected = (byte)((expected + 1) % Enum.GetValues<ColorType>().Length);
- }
+ return true;
+ }
- if (color != expected)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} with incorrect color");
- }
- }
+ private async ValueTask HandleCompleteTask(ClientPlayer sender, uint taskId)
+ {
+ var task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId);
- PlayerInfo.ColorId = color;
- PlayerInfo.RequestedColorId = null;
- break;
- }
+ if (task != null)
+ {
+ task.Complete = true;
+ await _eventManager.CallAsync(new PlayerCompletedTaskEvent(_game, sender, this, task));
+ }
+ else
+ {
+ _logger.LogWarning($"Client sent {nameof(RpcCalls.CompleteTask)} with a taskIndex that is not in their {nameof(InnerPlayerInfo)}");
+ }
+ }
- // Update the hat of a player.
- case RpcCalls.SetHat:
+ private async ValueTask HandleSetInfected(ReadOnlyMemory<byte> infectedIds)
+ {
+ for (var i = 0; i < infectedIds.Length; i++)
+ {
+ var player = _game.GameNet.GameData.GetPlayerById(infectedIds.Span[i]);
+ if (player != null)
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to an unowned {nameof(InnerPlayerControl)}");
- }
+ player.IsImpostor = true;
+ }
+ }
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast");
- }
+ if (_game.GameState == GameStates.Starting)
+ {
+ await _game.StartedAsync();
+ }
+ }
- PlayerInfo.HatId = reader.ReadPackedUInt32();
- break;
+ private async ValueTask<bool> HandleCheckName(ClientPlayer sender, string name)
+ {
+ if (name.Length > 10)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name exceeding 10 characters"))
+ {
+ return false;
}
+ }
- case RpcCalls.SetSkin:
+ if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed))
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name containing illegal characters"))
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetSkin)} to an unowned {nameof(InnerPlayerControl)}");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast");
- }
-
- PlayerInfo.SkinId = reader.ReadPackedUInt32();
- break;
+ return false;
}
+ }
- // TODO: (ANTICHEAT) Location check?
- // only called by a non-host player on to start meeting
- case RpcCalls.ReportDeadBody:
+ if (sender.Client.Name != name)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name not matching his name from handshake"))
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to an unowned {nameof(InnerPlayerControl)}");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to a specific player instead of broadcast");
- }
+ return false;
+ }
+ }
+ RequestedPlayerName.Enqueue(name);
- var deadBodyPlayerId = reader.ReadByte();
- // deadBodyPlayerId == byte.MaxValue -- means emergency call by button
+ return true;
+ }
- break;
+ private async ValueTask<bool> HandleSetName(ClientPlayer sender, string name)
+ {
+ if (_game.GameState == GameStates.Started)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client tried to set a name midgame"))
+ {
+ return false;
}
+ }
- // TODO: (ANTICHEAT) Cooldown check?
- case RpcCalls.MurderPlayer:
+ if (sender.IsOwner(this))
+ {
+ if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
{
- if (!sender.IsOwner(this))
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name that is already used"))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to an unowned {nameof(InnerPlayerControl)}");
+ return false;
}
+ }
- if (target != null)
+ if (sender.Client.Name != name)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name not matching his name from handshake"))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to a specific player instead of broadcast");
+ return false;
}
+ }
+ }
+ else
+ {
+ if (!RequestedPlayerName.Any())
+ {
+ _logger.LogWarning($"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it");
+ return false;
+ }
- if (!sender.Character.PlayerInfo.IsImpostor)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} as crewmate");
- }
+ var expected = RequestedPlayerName.Dequeue();
- if (!sender.Character.PlayerInfo.CanMurder(_game))
+ if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
+ {
+ var i = 1;
+ while (true)
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} too fast");
- }
+ string text = expected + " " + i;
- sender.Character.PlayerInfo.LastMurder = DateTimeOffset.UtcNow;
+ if (_game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text))
+ {
+ expected = text;
+ break;
+ }
- var player = reader.ReadNetObject<InnerPlayerControl>(_game);
- if (!player.PlayerInfo.IsDead)
- {
- player.Die(DeathReason.Kill);
- await _eventManager.CallAsync(new PlayerMurderEvent(_game, sender, this, player));
+ i++;
}
-
- break;
}
- case RpcCalls.SendChat:
+ if (name != expected)
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to an unowned {nameof(InnerPlayerControl)}");
- }
+ _logger.LogWarning($"Client sent {nameof(RpcCalls.SetName)} with incorrect name");
+ await SetNameAsync(expected);
+ return false;
+ }
+ }
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to a specific player instead of broadcast");
- }
+ PlayerInfo.PlayerName = name;
- var chat = reader.ReadString();
+ return true;
+ }
- await _eventManager.CallAsync(new PlayerChatEvent(_game, sender, this, chat));
- break;
- }
+ private static readonly byte ColorsCount = (byte)Enum.GetValues<ColorType>().Length;
- case RpcCalls.StartMeeting:
+ private async ValueTask<bool> HandleCheckColor(ClientPlayer sender, ColorType color)
+ {
+ if ((byte)color > ColorsCount)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, "Client sent invalid color"))
{
- if (!sender.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} but was not a host");
- }
+ return false;
+ }
+ }
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} to a specific player instead of broadcast");
- }
+ RequestedColorId.Enqueue(color);
- // deadBodyPlayerId == byte.MaxValue -- means emergency call by button
- var deadBodyPlayerId = reader.ReadByte();
- var deadPlayer = deadBodyPlayerId != byte.MaxValue
- ? _game.GameNet.GameData.GetPlayerById(deadBodyPlayerId)?.Controller
- : null;
+ return true;
+ }
- await _eventManager.CallAsync(new PlayerStartMeetingEvent(_game, _game.GetClientPlayer(this.OwnerId), this, deadPlayer));
- break;
+ private async ValueTask<bool> HandleSetColor(ClientPlayer sender, ColorType color)
+ {
+ if (_game.GameState == GameStates.Started)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client tried to set a color midgame"))
+ {
+ return false;
}
+ }
- case RpcCalls.SetScanner:
+ if (sender.IsOwner(this))
+ {
+ if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == color))
{
- if (!sender.IsOwner(this))
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client sent a color that is already used"))
{
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to an unowned {nameof(InnerPlayerControl)}");
+ return false;
}
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to a specific player instead of broadcast");
- }
-
- var on = reader.ReadBoolean();
- var count = reader.ReadByte();
- break;
}
-
- case RpcCalls.SendChatNote:
+ }
+ else
+ {
+ if (!RequestedColorId.Any())
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChatNote)} to an unowned {nameof(InnerPlayerControl)}");
- }
+ _logger.LogWarning($"Client sent {nameof(RpcCalls.SetColor)} for a player that didn't request it");
+ return false;
+ }
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChatNote)} to a specific player instead of broadcast");
- }
+ var expected = RequestedColorId.Dequeue();
- var playerId = reader.ReadByte();
- var chatNote = (ChatNoteType)reader.ReadByte();
- break;
+ while (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == expected))
+ {
+ expected = (ColorType)(((byte)expected + 1) % ColorsCount);
}
- case RpcCalls.SetPet:
+ if (color != expected)
{
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetPet)} to an unowned {nameof(InnerPlayerControl)}");
- }
+ _logger.LogWarning($"Client sent {nameof(RpcCalls.SetColor)} with incorrect color");
+ await SetColorAsync(expected);
+ return false;
+ }
+ }
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetPet)} to a specific player instead of broadcast");
- }
+ PlayerInfo.Color = color;
- PlayerInfo.PetId = reader.ReadPackedUInt32();
- break;
- }
+ return true;
+ }
- // TODO: Understand this RPC
- case RpcCalls.SetStartCounter:
- {
- if (!sender.IsOwner(this))
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetStartCounter)} to an unowned {nameof(InnerPlayerControl)}");
- }
+ private async ValueTask<bool> HandleSetHat(ClientPlayer sender, HatType hat)
+ {
+ if (_game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetHat, "Client tried to change hat while not in lobby"))
+ {
+ return false;
+ }
- if (target != null)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetStartCounter)} to a specific player instead of broadcast");
- }
+ PlayerInfo.Hat = hat;
- // Used to compare with LastStartCounter.
- var startCounter = reader.ReadPackedUInt32();
+ return true;
+ }
- // Is either start countdown or byte.MaxValue
- var secondsLeft = reader.ReadByte();
- if (secondsLeft < byte.MaxValue)
- {
- await _eventManager.CallAsync(new PlayerSetStartCounterEvent(_game, sender, this, secondsLeft));
- }
+ private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, SkinType skin)
+ {
+ if (_game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, "Client tried to change skin while not in lobby"))
+ {
+ return false;
+ }
- break;
+ PlayerInfo.Skin = skin;
+
+ return true;
+ }
+
+ private async ValueTask<bool> HandleMurderPlayer(ClientPlayer sender, IInnerPlayerControl? target)
+ {
+ // TODO record replay with timestamps
+ if (!_serverEnvironment.IsReplay && !PlayerInfo.CanMurder(_game))
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder too fast"))
+ {
+ return false;
}
+ }
- default:
+ if (target == null || target.PlayerInfo.IsImpostor)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder invalid target"))
{
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerPlayerControl), call);
- break;
+ return false;
}
}
+
+ PlayerInfo.LastMurder = DateTimeOffset.UtcNow;
+
+ if (!target.PlayerInfo.IsDead)
+ {
+ ((InnerPlayerControl)target).Die(DeathReason.Kill);
+ await _eventManager.CallAsync(new PlayerMurderEvent(_game, sender, this, target));
+ }
+
+ return true;
}
- public override bool Serialize(IMessageWriter writer, bool initialState)
+ private async ValueTask<bool> HandleSendChat(ClientPlayer sender, string message)
{
- throw new NotImplementedException();
+ var @event = new PlayerChatEvent(_game, sender, this, message);
+ await _eventManager.CallAsync(@event);
+
+ return !@event.IsCancelled;
}
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ private async ValueTask HandleStartMeeting(byte targetId)
{
- if (!sender.IsHost)
- {
- throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerPlayerControl)} as non-host");
- }
+ var deadPlayer = _game.GameNet.GameData.GetPlayerById(targetId)?.Controller;
+ await _eventManager.CallAsync(new PlayerStartMeetingEvent(_game, _game.GetClientPlayer(this.OwnerId), this, deadPlayer));
+ }
- if (initialState)
+ private async ValueTask<bool> HandleSetPet(ClientPlayer sender, PetType pet)
+ {
+ if (_game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetPet, "Client tried to change pet while not in lobby"))
{
- IsNew = reader.ReadBoolean();
+ return false;
}
- PlayerId = reader.ReadByte();
+ PlayerInfo.Pet = pet;
+
+ return true;
}
- internal void Die(DeathReason reason)
+ private async ValueTask<bool> HandleSetStartCounter(ClientPlayer sender, int sequenceId, sbyte startCounter)
{
- PlayerInfo.IsDead = true;
- PlayerInfo.LastDeathReason = reason;
+ if (!sender.IsHost && startCounter != -1)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to set start counter as a non-host"))
+ {
+ return false;
+ }
+ }
+
+ if (startCounter != -1)
+ {
+ await _eventManager.CallAsync(new PlayerSetStartCounterEvent(_game, sender, this, (byte)startCounter));
+ }
+
+ return true;
}
}
}
using System.Collections.Generic;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Net.Messages;
namespace Impostor.Server.Net.Inner.Objects
public string PlayerName { get; internal set; }
- public string? RequestedPlayerName { get; internal set; }
+ public ColorType Color { get; internal set; }
- public byte ColorId { get; internal set; }
+ public HatType Hat { get; internal set; }
- public byte? RequestedColorId { get; internal set; }
+ public PetType Pet { get; internal set; }
- public uint HatId { get; internal set; }
-
- public uint PetId { get; internal set; }
-
- public uint SkinId { get; internal set; }
+ public SkinType Skin { get; internal set; }
public bool Disconnected { get; internal set; }
public void Deserialize(IMessageReader reader)
{
PlayerName = reader.ReadString();
- ColorId = reader.ReadByte();
- HatId = reader.ReadPackedUInt32();
- PetId = reader.ReadPackedUInt32();
- SkinId = reader.ReadPackedUInt32();
+ Color = (ColorType)reader.ReadByte();
+ Hat = (HatType)reader.ReadPackedUInt32();
+ Pet = (PetType)reader.ReadPackedUInt32();
+ Skin = (SkinType)reader.ReadPackedUInt32();
var flag = reader.ReadByte();
Disconnected = (flag & 1) > 0;
IsImpostor = (flag & 2) > 0;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Net.Inner.Objects.Systems;
using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus;
using Impostor.Server.Net.State;
_systems.Add(SystemTypes.Sabotage, new SabotageSystemType(new[]
{
- (IActivatable)_systems[SystemTypes.Comms],
- (IActivatable)_systems[SystemTypes.Reactor],
- (IActivatable)_systems[SystemTypes.LifeSupp],
- (IActivatable)_systems[SystemTypes.Electrical],
+ (IActivatable)_systems[SystemTypes.Comms], (IActivatable)_systems[SystemTypes.Reactor], (IActivatable)_systems[SystemTypes.LifeSupp], (IActivatable)_systems[SystemTypes.Electrical],
}));
Components.Add(this);
}
- public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call,
- IMessageReader reader)
- {
- switch (call)
- {
- case RpcCalls.CloseDoorsOfType:
- {
- if (target == null || !target.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CloseDoorsOfType)} to wrong destinition, must be host");
- }
-
- if (!sender.Character.PlayerInfo.IsImpostor)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CloseDoorsOfType)} as crewmate");
- }
-
- var systemType = (SystemTypes)reader.ReadByte();
-
- break;
- }
-
- case RpcCalls.RepairSystem:
- {
- if (target == null || !target.IsHost)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.RepairSystem)} to wrong destinition, must be host");
- }
-
- var systemType = (SystemTypes)reader.ReadByte();
- if (systemType == SystemTypes.Sabotage && !sender.Character.PlayerInfo.IsImpostor)
- {
- throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.RepairSystem)} for {systemType} as crewmate");
- }
-
- var player = reader.ReadNetObject<InnerPlayerControl>(_game);
- var amount = reader.ReadByte();
-
- // TODO: Modify data (?)
- break;
- }
-
- default:
- {
- _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerShipStatus), call);
- break;
- }
- }
-
- return default;
- }
-
- public override bool Serialize(IMessageWriter writer, bool initialState)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
}
- public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
+ public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
- if (!sender.IsHost)
+ if (!await ValidateHost(CheatContext.Deserialize, sender) || !await ValidateBroadcast(CheatContext.Deserialize, sender, target))
{
- throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerShipStatus)} as non-host");
- }
-
- if (target != null)
- {
- throw new ImpostorCheatException($"Client attempted to send {nameof(InnerShipStatus)} data to a specific player, must be broadcast");
+ return;
}
if (initialState)
}
}
}
+
+ public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ {
+ if (!await ValidateCmd(call, sender, target))
+ {
+ return false;
+ }
+
+ switch (call)
+ {
+ case RpcCalls.CloseDoorsOfType:
+ {
+ if (!await ValidateImpostor(RpcCalls.MurderPlayer, sender, sender.Character!.PlayerInfo))
+ {
+ return false;
+ }
+
+ Rpc27CloseDoorsOfType.Deserialize(reader, out var systemType);
+ break;
+ }
+
+ case RpcCalls.RepairSystem:
+ {
+ Rpc28RepairSystem.Deserialize(reader, _game, out var systemType, out var player, out var amount);
+
+ if (systemType == SystemTypes.Sabotage && !await ValidateImpostor(call, sender, sender.Character!.PlayerInfo))
+ {
+ return false;
+ }
+
+ break;
+ }
+
+ case RpcCalls.CustomRpc:
+ return await HandleCustomRpc(reader, _game);
+
+ default:
+ return await UnregisteredCall(call, sender);
+ }
+
+ return true;
+ }
}
-}
\ No newline at end of file
+}
+++ /dev/null
-namespace Impostor.Server.Net.Inner
-{
- public enum RpcCalls : byte
- {
- PlayAnimation = 0,
- CompleteTask = 1,
- SyncSettings = 2,
- SetInfected = 3,
- Exiled = 4,
- CheckName = 5,
- SetName = 6,
- CheckColor = 7,
- SetColor = 8,
- SetHat = 9,
- SetSkin = 10,
- ReportDeadBody = 11,
- MurderPlayer = 12,
- SendChat = 13,
- StartMeeting = 14,
- SetScanner = 15,
- SendChatNote = 16,
- SetPet = 17,
- SetStartCounter = 18,
- EnterVent = 19,
- ExitVent = 20,
- SnapTo = 21,
- Close = 22,
- VotingComplete = 23,
- CastVote = 24,
- ClearVote = 25,
- AddVote = 26,
- CloseDoorsOfType = 27,
- RepairSystem = 28,
- SetTasks = 29,
- UpdateGameData = 30,
- }
-}
\ No newline at end of file
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.S2C;
+using Impostor.Api.Reactor;
using Impostor.Hazel;
using Impostor.Server.Config;
using Impostor.Server.Net.Factories;
+using Impostor.Server.Utils;
using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Manager
GameVersion.GetVersion(2020, 11, 17), // 2020.11.17
};
+ private static string ServerBrand { get; } = $"Impostor {DotnetUtils.GetVersion()}";
+
private readonly ILogger<ClientManager> _logger;
private readonly ConcurrentDictionary<int, ClientBase> _clients;
private readonly IClientFactory _clientFactory;
return clientId;
}
- public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion)
+ public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion, ISet<Mod>? mods)
{
if (!SupportedVersions.Contains(clientVersion))
{
return;
}
- var client = _clientFactory.Create(connection, name, clientVersion);
+ var client = _clientFactory.Create(connection, name, clientVersion, mods ?? new HashSet<Mod>(0));
var id = NextId();
client.Id = id;
_logger.LogTrace("Client connected.");
_clients.TryAdd(id, client);
+
+ using var writer = MessageWriter.Get(MessageType.Reliable);
+ ModdedHandshakeS2C.Serialize(writer, ServerBrand);
+ await connection.SendAsync(writer);
}
public void Remove(IClient client)
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
+using Impostor.Api.Reactor;
using Impostor.Hazel;
using Impostor.Hazel.Udp;
using Impostor.Server.Net.Hazel;
private async ValueTask OnNewConnection(NewConnectionEventArgs e)
{
// Handshake.
- var clientVersion = e.HandshakeData.ReadInt32();
- var name = e.HandshakeData.ReadString();
+ ModdedHandshakeC2S.Deserialize(e.HandshakeData, out var clientVersion, out var name, out var mods);
var connection = new HazelConnection(e.Connection, _connectionLogger);
// Register client
- await _clientManager.RegisterConnectionAsync(connection, name, clientVersion);
+ await _clientManager.RegisterConnectionAsync(connection, name, clientVersion, mods);
}
}
}
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.C2S;
using Impostor.Api.Net.Messages.S2C;
+using Impostor.Api.Reactor;
using Impostor.Hazel;
using Impostor.Server.Config;
using Impostor.Server.Net.Hazel;
public ClientRedirector(
string name,
HazelConnection connection,
+ ISet<Mod> mods,
ClientManager clientManager,
INodeProvider nodeProvider,
INodeLocator nodeLocator)
- : base(name, connection)
+ : base(name, connection, mods)
{
_clientManager = clientManager;
_nodeProvider = nodeProvider;
-using System.Collections.Generic;
-using System.IO;
+using System.IO;
using System.Net;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner;
-using Impostor.Api.Net.Inner.Objects;
using Impostor.Server.Net.Inner;
namespace Impostor.Server.Net.State
await FinishRpcAsync(writer);
}
}
-
- public async ValueTask SetInfectedAsync(IEnumerable<IInnerPlayerControl> players)
- {
- if (Host.Character == null)
- {
- throw new ImpostorException("Attempted to set infected when the host was not spawned.");
- }
-
- using (var writer = StartRpc(Host.Character.NetId, RpcCalls.SetInfected))
- {
- writer.Write((byte)Host.Character.NetId);
-
- foreach (var player in players)
- {
- writer.Write((byte)player.PlayerId);
- }
-
- await FinishRpcAsync(writer);
- }
- }
}
}
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.S2C;
using Impostor.Hazel;
var netId = reader.ReadPackedUInt32();
if (_allObjectsFast.TryGetValue(netId, out var obj))
{
- obj.Deserialize(sender, target, reader, false);
+ await obj.DeserializeAsync(sender, target, reader, false);
}
else
{
var netId = reader.ReadPackedUInt32();
if (_allObjectsFast.TryGetValue(netId, out var obj))
{
- await obj.HandleRpc(sender, target, (RpcCalls) reader.ReadByte(), reader);
+ if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader))
+ {
+ return false;
+ }
}
else
{
// Only the host is allowed to despawn objects.
if (!sender.IsHost)
{
- throw new ImpostorCheatException("Tried to send SpawnFlag as non-host.");
+ if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), "Tried to send SpawnFlag as non-host."))
+ {
+ return false;
+ }
}
var objectId = reader.ReadPackedUInt32();
using var readerSub = reader.ReadMessage();
if (readerSub.Length > 0)
{
- obj.Deserialize(sender, target, readerSub, true);
+ await obj.DeserializeAsync(sender, target, readerSub, true);
}
await OnSpawnAsync(obj);
obj.NetId = uint.MaxValue;
}
- public T FindObjectByNetId<T>(uint netId)
- where T : InnerNetObject
+ public T? FindObjectByNetId<T>(uint netId)
+ where T : IInnerNetObject
{
if (_allObjectsFast.TryGetValue(netId, out var obj))
{
- return (T) obj;
+ return (T)(IInnerNetObject)obj;
}
- return null;
+ return default;
}
}
}
using System;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Reactor;
using Impostor.Hazel;
using Impostor.Server.Events;
using Microsoft.Extensions.DependencyInjection;
return GameJoinResult.FromError(GameJoinError.GameDestroyed);
}
+ if (Host != null)
+ {
+ foreach (var hostMod in Host.Client.Mods)
+ {
+ if (hostMod.Side == PluginSide.Both && client.Mods.All(clientMod => hostMod.Id != clientMod.Id))
+ {
+ return GameJoinResult.CreateCustomError($"You are missing {hostMod.Id} - {hostMod.Version}");
+ }
+ }
+
+ foreach (var clientMod in client.Mods)
+ {
+ if (clientMod.Side == PluginSide.Both && Host.Client.Mods.All(hostMod => clientMod.Id != hostMod.Id))
+ {
+ return GameJoinResult.CreateCustomError($"Host of this game is missing {clientMod.Id} - {clientMod.Version}");
+ }
+ }
+ }
+
var isNew = false;
if (player == null || player.Game != this)
return;
}
+ foreach (var player in _players.Values)
+ {
+ player.Character?.RequestedPlayerName.Clear();
+ player.Character?.RequestedColorId.Clear();
+ }
+
HostId = host.Client.Id;
_logger.LogInformation("{0} - Assigned {1} ({2}) as new host.", Code, host.Client.Name, host.Client.Id);
}
}
}
-}
\ No newline at end of file
+}
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using System.Numerics;
using System.Threading.Tasks;
using Impostor.Api.Events.Managers;
using Impostor.Api.Games;
public int PlayerCount => _players.Count;
- public ClientPlayer Host => _players[HostId];
+ public ClientPlayer? Host => _players.GetValueOrDefault(HostId);
public IEnumerable<IClientPlayer> Players => _players.Select(p => p.Value);
return _players.TryGetValue(clientId, out var clientPlayer) ? clientPlayer : null;
}
- internal ValueTask StartedAsync()
+ internal async ValueTask StartedAsync()
{
if (GameState == GameStates.Starting)
{
+ for (var i = 0; i < _players.Values.Count; i++)
+ {
+ var player = _players.Values.ElementAt(i);
+ await player.Character!.NetworkTransform.SetPositionAsync(player, MapSpawn.Maps[Options.Map].GetSpawnLocation(i, PlayerCount, true), Vector2.Zero);
+ }
+
GameState = GameStates.Started;
- return _eventManager.CallAsync(new GameStartedEvent(this));
+ await _eventManager.CallAsync(new GameStartedEvent(this));
}
-
- return default;
}
public ValueTask EndAsync()
using System.Reflection;
using System.Runtime.Loader;
using Impostor.Api.Plugins;
-using Impostor.Server.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Hosting;
.GetSection(ServerRedirectorConfig.Section)
.Get<ServerRedirectorConfig>() ?? new ServerRedirectorConfig();
+ var announcementsServer = host.Configuration
+ .GetSection(AnnouncementsServerConfig.Section)
+ .Get<AnnouncementsServerConfig>() ?? new AnnouncementsServerConfig();
+
+ services.AddSingleton<ServerEnvironment>();
+
services.Configure<DebugConfig>(host.Configuration.GetSection(DebugConfig.Section));
services.Configure<AntiCheatConfig>(host.Configuration.GetSection(AntiCheatConfig.Section));
services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
+ services.Configure<AnnouncementsServerConfig>(host.Configuration.GetSection(AnnouncementsServerConfig.Section));
services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
if (redirector.Enabled)
services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
}
+ services.AddEventPools();
services.AddHazel();
services.AddSingleton<IMessageWriterProvider, MessageWriterProvider>();
services.AddSingleton<IGameCodeFactory, GameCodeFactory>();
services.AddSingleton<IEventManager, EventManager>();
services.AddSingleton<Matchmaker>();
services.AddHostedService<MatchmakerService>();
+
+ if (announcementsServer.Enabled)
+ {
+ services.AddHostedService<AnnouncementsService>();
+ }
})
.UseSerilog()
.UseConsoleLifetime()
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
<Rule Id="SA1101" Action="None" />
+ <Rule Id="SA1111" Action="None"/>
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.NamingRules">
<Rule Id="SA1309" Action="None" />
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using Impostor.Api.Net.Messages;
+using Impostor.Api.Reactor;
using Impostor.Server.Config;
using Impostor.Server.Net;
using Impostor.Server.Net.Hazel;
private bool _createdGame;
private bool _recordAfter;
- public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, PacketRecorder recorder)
- : base(logger, antiCheatOptions, clientManager, gameManager, name, connection)
+ public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, ISet<Mod> mods, PacketRecorder recorder)
+ : base(logger, antiCheatOptions, clientManager, gameManager, name, connection, mods)
{
_recorder = recorder;
_isFirst = true;
--- /dev/null
+namespace Impostor.Server.Utils
+{
+ public class ServerEnvironment
+ {
+ public bool IsReplay { get; init; }
+ }
+}
-{
+{
"Server": {
"PublicIp": "127.0.0.1",
"PublicPort": 22023,
"ListenIp": "0.0.0.0",
"ListenPort": 22023
},
+ "AnnouncementsServer": {
+ "Enabled": true,
+ "ListenIp": "0.0.0.0",
+ "ListenPort": 22024
+ },
"AntiCheat": {
+ "Enabled": true,
"BanIpFromGame": true
},
"ServerRedirector": {
"GameRecorderEnabled": true,
"GameRecorderPath": ""
}
-}
\ No newline at end of file
+}
using Impostor.Api.Net.Messages.C2S;
using Impostor.Hazel;
using Impostor.Hazel.Extensions;
+using Impostor.Server;
using Impostor.Server.Events;
using Impostor.Server.Net;
using Impostor.Server.Net.Factories;
using Impostor.Server.Net.Manager;
using Impostor.Server.Net.Redirector;
using Impostor.Server.Recorder;
+using Impostor.Server.Utils;
using Impostor.Tools.ServerReplay.Mocks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
{
var services = new ServiceCollection();
+ services.AddSingleton(new ServerEnvironment
+ {
+ IsReplay = true
+ });
+
services.AddLogging(builder =>
{
builder.ClearProviders();
services.AddSingleton<INodeLocator, NodeLocatorNoOp>();
services.AddSingleton<IEventManager, EventManager>();
+ services.AddEventPools();
services.AddHazel();
return services.BuildServiceProvider();
private static async Task ParsePacket(BinaryReader reader)
{
- var dataType = (RecordedPacketType) reader.ReadByte();
+ var dataType = (RecordedPacketType)reader.ReadByte();
// Read client id.
var clientId = reader.ReadInt32();
// Create and register connection.
var connection = new MockHazelConnection(address);
- await _clientManager.RegisterConnectionAsync(connection, name, 50516550);
+ await _clientManager.RegisterConnectionAsync(connection, name, 50516550, null);
// Store reference for ourselfs.
Connections.Add(clientId, connection);