]> git.deb.at Git - rhonda/impostor.git/commitdiff
Sync Impostor with 6pak fork (#327)
authorjs6pak <kubastaron@hotmail.com>
Wed, 3 Mar 2021 08:53:53 +0000 (08:53 +0000)
committerGitHub <noreply@github.com>
Wed, 3 Mar 2021 08:53:53 +0000 (08:53 +0000)
Co-authored-by: DatGuy1 <datguysteam@gmail.com>
Co-authored-by: miniduikboot <5243971+miniduikboot@users.noreply.github.com>
124 files changed:
.github/stale.yml [deleted file]
appveyor.yml
build.cake
src/.editorconfig
src/.gitignore
src/Directory.Build.props [new file with mode: 0644]
src/Impostor.Api/CheatContext.cs [new file with mode: 0644]
src/Impostor.Api/Events/Announcements/IAnnouncementRequestEvent.cs [new file with mode: 0644]
src/Impostor.Api/Events/Game/Player/IPlayerMovementEvent.cs [new file with mode: 0644]
src/Impostor.Api/Exceptions/ImpostorCheatException.cs [deleted file]
src/Impostor.Api/Games/IGame.cs
src/Impostor.Api/Impostor.Api.csproj
src/Impostor.Api/Innersloth/Announcement.cs [new file with mode: 0644]
src/Impostor.Api/Innersloth/Customization/HatType.cs
src/Impostor.Api/Innersloth/Customization/PetType.cs
src/Impostor.Api/Innersloth/Customization/SkinType.cs
src/Impostor.Api/Innersloth/FloatRange.cs [deleted file]
src/Impostor.Api/Innersloth/FreeWeekendState.cs [new file with mode: 0644]
src/Impostor.Api/Innersloth/GameOptionsData.cs
src/Impostor.Api/Innersloth/Language.cs [new file with mode: 0644]
src/Impostor.Api/Innersloth/MapSpawn.cs [new file with mode: 0644]
src/Impostor.Api/Net/IClient.cs
src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs
src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs
src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs
src/Impostor.Api/Net/Inner/RpcCalls.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Announcements/Message00UseCache.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Announcements/Message01Update.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Announcements/Message02SetFreeWeekend.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Announcements/MessageHello.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/AnnouncementsMessageFlags.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs
src/Impostor.Api/Net/Messages/IMessageReader.cs
src/Impostor.Api/Net/Messages/IMessageWriter.cs
src/Impostor.Api/Net/Messages/Rpcs/Rpc00PlayAnimation.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc01CompleteTask.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc03SetInfected.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc04Exiled.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc05CheckName.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc07CheckColor.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc09SetHat.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc10SetSkin.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc11ReportDeadBody.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc12MurderPlayer.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc13SendChat.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc14StartMeeting.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc15SetScanner.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc16SendChatNote.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc17SetPet.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc18SetStartCounter.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc19EnterVent.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc20ExitVent.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc21SnapTo.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc22Close.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc23VotingComplete.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc24CastVote.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc25ClearVote.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc26AddVote.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc27CloseDoorsOfType.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc28RepairSystem.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs [new file with mode: 0644]
src/Impostor.Api/Net/Messages/Rpcs/Rpc30UpdateGameData.cs [new file with mode: 0644]
src/Impostor.Api/ProjectRules.ruleset
src/Impostor.Api/Reactor/Mod.cs [new file with mode: 0644]
src/Impostor.Api/Reactor/ModList.cs [new file with mode: 0644]
src/Impostor.Api/Reactor/ModdedHandshakeC2S.cs [new file with mode: 0644]
src/Impostor.Api/Reactor/ModdedHandshakeS2C.cs [new file with mode: 0644]
src/Impostor.Api/Reactor/PluginSide.cs [new file with mode: 0644]
src/Impostor.Api/Unity/Mathf.cs
src/Impostor.Hazel/Impostor.Hazel.csproj
src/Impostor.Hazel/MessageReader.cs
src/Impostor.Hazel/MessageWriter.cs
src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs
src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs
src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj
src/Impostor.Plugins.Example/ExamplePluginStartup.cs
src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs [new file with mode: 0644]
src/Impostor.Server/Config/AnnouncementsServerConfig.cs [new file with mode: 0644]
src/Impostor.Server/Config/AntiCheatConfig.cs
src/Impostor.Server/Events/Announcements/AnnouncementRequestEvent.cs [new file with mode: 0644]
src/Impostor.Server/Events/EventManager.cs
src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs
src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs
src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs
src/Impostor.Server/Extensions/MessageReaderExtensions.cs [deleted file]
src/Impostor.Server/Extensions/ServiceProviderExtensions.cs [new file with mode: 0644]
src/Impostor.Server/Impostor.Server.csproj
src/Impostor.Server/Net/AnnouncementsService.cs [new file with mode: 0644]
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/ClientBase.cs
src/Impostor.Server/Net/Factories/ClientFactory.cs
src/Impostor.Server/Net/Factories/IClientFactory.cs
src/Impostor.Server/Net/Inner/InnerNetObject.Anticheat.cs [new file with mode: 0644]
src/Impostor.Server/Net/Inner/InnerNetObject.cs
src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs
src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs
src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs
src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs
src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs
src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs
src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs
src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs
src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs
src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs
src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs
src/Impostor.Server/Net/Inner/RpcCalls.cs [deleted file]
src/Impostor.Server/Net/Manager/ClientManager.cs
src/Impostor.Server/Net/Matchmaker.cs
src/Impostor.Server/Net/Redirector/ClientRedirector.cs
src/Impostor.Server/Net/State/Game.Api.cs
src/Impostor.Server/Net/State/Game.Data.cs
src/Impostor.Server/Net/State/Game.Incoming.cs
src/Impostor.Server/Net/State/Game.State.cs
src/Impostor.Server/Net/State/Game.cs
src/Impostor.Server/Plugins/PluginLoader.cs
src/Impostor.Server/Program.cs
src/Impostor.Server/ProjectRules.ruleset
src/Impostor.Server/Recorder/ClientRecorder.cs
src/Impostor.Server/Utils/ServerEnvironment.cs [new file with mode: 0644]
src/Impostor.Server/config-full.json
src/Impostor.Tools.ServerReplay/Program.cs

diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644 (file)
index e556fa9..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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
index e0c82f688f916bc9ba28efec1dd28363aca0ba7f..ce3b999ae6622d9acca593bdf2180ccf0de16ce2 100644 (file)
@@ -1,12 +1,11 @@
 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
@@ -17,7 +16,7 @@ assembly_info:
 dotnet_csproj:
   patch: false
 
-image: Visual Studio 2019 Preview
+image: Ubuntu2004
 
 install:
   - git submodule update --init --recursive
index 7b2869857c1667f28475f341ac024165da0410d4..e8d9519d9f37e2c2ca13cc6f348ebb5434ce3611 100644 (file)
@@ -2,19 +2,19 @@
 #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;
 }
 
 //////////////////////////////////////////////////////////////////////
@@ -34,7 +34,8 @@ private void ImpostorPublish(string name, string project, string runtime, bool i
         SelfContained = false,
         PublishSingleFile = true,
         PublishTrimmed = false,
-        OutputDirectory = projBuildDir
+        OutputDirectory = projBuildDir,
+        MSBuildSettings = msbuildSettings
     });
 
     if (isServer) {
@@ -46,11 +47,7 @@ private void ImpostorPublish(string name, string project, string runtime, bool i
         }
     }
 
-    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) {
@@ -62,7 +59,8 @@ private void ImpostorPublishNF(string name, string project) {
         Configuration = configuration,
         NoRestore = true,
         Framework = "net472",
-        OutputDirectory = projBuildDir
+        OutputDirectory = projBuildDir,
+        MSBuildSettings = msbuildSettings
     });
 
     Zip(projBuildDir, projBuildZip);
@@ -86,13 +84,6 @@ Task("Restore")
         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
@@ -107,7 +98,6 @@ Task("Replay")
 
 Task("Build")
     .IsDependentOn("Clean")
-    .IsDependentOn("Patch")
     .IsDependentOn("Restore")
     .IsDependentOn("Replay")
     .Does(() => {
@@ -116,44 +106,28 @@ Task("Build")
             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")
index ef7041d326cf018cbc67a47bc68a988adaf5476f..af97d16c25df14bfbea99dca2d552581126af613 100644 (file)
@@ -4,8 +4,8 @@ root = true
 # 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
index e0838bae98cb7d60d4df5a849ac9424cc24dca0e..1810abe12ea5c5bbf252551a0226e1ae2a031a8a 100644 (file)
@@ -26,21 +26,6 @@ bld/
 
 # 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
@@ -72,32 +57,12 @@ artifacts/
 *.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
@@ -112,72 +77,11 @@ _TeamCity*
 # 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
@@ -197,13 +101,6 @@ ClientBin/
 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 ;-)
@@ -212,52 +109,6 @@ Backup*/
 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
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644 (file)
index 0000000..58fd8ce
--- /dev/null
@@ -0,0 +1,7 @@
+<Project>
+  <PropertyGroup>
+    <VersionPrefix>1.2.2</VersionPrefix>
+    <VersionSuffix>dev</VersionSuffix>
+  </PropertyGroup>
+</Project>
+  
diff --git a/src/Impostor.Api/CheatContext.cs b/src/Impostor.Api/CheatContext.cs
new file mode 100644 (file)
index 0000000..45a9956
--- /dev/null
@@ -0,0 +1,20 @@
+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());
+    }
+}
diff --git a/src/Impostor.Api/Events/Announcements/IAnnouncementRequestEvent.cs b/src/Impostor.Api/Events/Announcements/IAnnouncementRequestEvent.cs
new file mode 100644 (file)
index 0000000..b4eaa01
--- /dev/null
@@ -0,0 +1,43 @@
+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; }
+    }
+}
diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerMovementEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerMovementEvent.cs
new file mode 100644 (file)
index 0000000..816684d
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Impostor.Api.Events.Player
+{
+    public interface IPlayerMovementEvent : IPlayerEvent
+    {
+    }
+}
diff --git a/src/Impostor.Api/Exceptions/ImpostorCheatException.cs b/src/Impostor.Api/Exceptions/ImpostorCheatException.cs
deleted file mode 100644 (file)
index 8eb72f8..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-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
index ad719867eab7001fa2d876a5fd2f3fd0f2c786e2..7c6140c09591cd6769d8a94cad7ff7c511454af0 100644 (file)
@@ -4,7 +4,6 @@ using System.Threading.Tasks;
 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
@@ -35,6 +34,9 @@ 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"/>.
@@ -53,13 +55,6 @@ namespace Impostor.Api.Games
         /// <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>
index 8ad2582883d2393d29e36d063dd45650b60189c5..188f2a677704242674c994985b6f9f827b675936 100644 (file)
@@ -1,12 +1,11 @@
 <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>
@@ -22,7 +21,6 @@
   </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">
diff --git a/src/Impostor.Api/Innersloth/Announcement.cs b/src/Impostor.Api/Innersloth/Announcement.cs
new file mode 100644 (file)
index 0000000..fca0601
--- /dev/null
@@ -0,0 +1,14 @@
+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;
+        }
+    }
+}
index 5e0a3efe07e8f12db55fc00c760cb71ffc42dd41..c4af44c9872565ca6f7d38ab5ea000464ab6977a 100644 (file)
@@ -1,6 +1,6 @@
 namespace Impostor.Api.Innersloth.Customization
 {
-    public enum HatType
+    public enum HatType : uint
     {
         NoHat = 0,
         Astronaut = 1,
@@ -45,8 +45,8 @@
         ThirdEyeHat = 40,
         ToiletPaperHat = 41,
         Toppat = 42,
-        Fedora = 43,
-        Goggles2 = 44,
+        BlackFedora = 43,
+        SkiGoggles = 44,
         Headphones = 45,
         MaskHat = 46,
         PaperMask = 47,
@@ -58,7 +58,7 @@
         Cheese = 53,
         Cherry = 54,
         Egg = 55,
-        Fedora2 = 56,
+        GreenFedora = 56,
         Flamingo = 57,
         FlowerPin = 58,
         Helmet = 59,
@@ -95,6 +95,7 @@
         MiniCrewmate = 90,
         NinjaMask = 91,
         RamHorns = 92,
-        Snowman2 = 93,
+        SnowCrewmate = 93,
+        GeoffHat = 94,
     }
 }
index 456e3274f97a3606c63769ffbbe0fbdb4a956a17..f8753f273d02ace160c268a9ac3aadc7a18a5226 100644 (file)
@@ -1,6 +1,6 @@
 namespace Impostor.Api.Innersloth.Customization
 {
-    public enum PetType
+    public enum PetType : uint
     {
         NoPet = 0,
         Alien = 1,
index 35da3120978e72c377b2410efee142b29b3bd0b8..9f0aa2297d9709f1c76eff6a609e23c258aafbf6 100644 (file)
@@ -1,6 +1,6 @@
 namespace Impostor.Api.Innersloth.Customization
 {
-    public enum SkinType : byte
+    public enum SkinType : uint
     {
         None = 0,
         Astro = 1,
diff --git a/src/Impostor.Api/Innersloth/FloatRange.cs b/src/Impostor.Api/Innersloth/FloatRange.cs
deleted file mode 100644 (file)
index c8a0824..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-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
diff --git a/src/Impostor.Api/Innersloth/FreeWeekendState.cs b/src/Impostor.Api/Innersloth/FreeWeekendState.cs
new file mode 100644 (file)
index 0000000..2effda8
--- /dev/null
@@ -0,0 +1,12 @@
+using System;
+
+namespace Impostor.Api.Innersloth
+{
+    [Flags]
+    public enum FreeWeekendState : byte
+    {
+        NotFree,
+        FreeMIRA,
+        FreePolus,
+    }
+}
index d18efd8c53bd8d50aeab9c2aa075e9b8452ffb10..7f7e0c5efcad96b1fda51a4fbd2f8ae74f6eae8b 100644 (file)
@@ -161,7 +161,7 @@ namespace Impostor.Api.Innersloth
         /// </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);
@@ -204,6 +204,14 @@ namespace Impostor.Api.Innersloth
             }
         }
 
+        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>
diff --git a/src/Impostor.Api/Innersloth/Language.cs b/src/Impostor.Api/Innersloth/Language.cs
new file mode 100644 (file)
index 0000000..b17e5ad
--- /dev/null
@@ -0,0 +1,11 @@
+namespace Impostor.Api.Innersloth
+{
+    public enum Language
+    {
+        English,
+        Spanish,
+        Portuguese,
+        Korean,
+        Russian,
+    }
+}
diff --git a/src/Impostor.Api/Innersloth/MapSpawn.cs b/src/Impostor.Api/Innersloth/MapSpawn.cs
new file mode 100644 (file)
index 0000000..61dea0f
--- /dev/null
@@ -0,0 +1,45 @@
+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));
+        }
+    }
+}
index 48efeda42ca97ec19fa168bea8066e6793242591..1ba594470ef932587f4b283e73bf324f144b32d3 100644 (file)
@@ -2,6 +2,7 @@
 using System.Threading.Tasks;
 using Impostor.Api.Innersloth;
 using Impostor.Api.Net.Messages;
+using Impostor.Api.Reactor;
 
 namespace Impostor.Api.Net
 {
@@ -27,6 +28,11 @@ 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>
@@ -55,6 +61,8 @@ namespace Impostor.Api.Net
         /// </summary>
         IClientPlayer? Player { get; }
 
+        ValueTask<bool> ReportCheatAsync(CheatContext context, string message);
+
         ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
 
         ValueTask HandleDisconnectAsync(string reason);
index 6d867e7936d75d370feb847f88c74c30158b00b8..2dd470947d3aa16947194e8c6c4640e0f91af977 100644 (file)
@@ -5,6 +5,16 @@ namespace Impostor.Api.Net.Inner.Objects.Components
 {
     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>
index 04558b96e5c1e18968d6be22d8f8bdd455d1e50c..c0ecf855d56eeef91144c7d13c456825202da16e 100644 (file)
@@ -41,48 +41,32 @@ namespace Impostor.Api.Net.Inner.Objects
         ///     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>
@@ -106,11 +90,13 @@ namespace Impostor.Api.Net.Inner.Objects
         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);
     }
 }
index 6cb33024afd6837bc1abc79408fc51d9b2daa1b4..310755ed2fdb454001db443bf5a33a57eb9a3cf1 100644 (file)
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.Customization;
 
 namespace Impostor.Api.Net.Inner.Objects
 {
@@ -14,22 +15,22 @@ 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.
diff --git a/src/Impostor.Api/Net/Inner/RpcCalls.cs b/src/Impostor.Api/Net/Inner/RpcCalls.cs
new file mode 100644 (file)
index 0000000..f82d2e8
--- /dev/null
@@ -0,0 +1,38 @@
+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
diff --git a/src/Impostor.Api/Net/Messages/Announcements/Message00UseCache.cs b/src/Impostor.Api/Net/Messages/Announcements/Message00UseCache.cs
new file mode 100644 (file)
index 0000000..3787ef6
--- /dev/null
@@ -0,0 +1,15 @@
+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)
+        {
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Announcements/Message01Update.cs b/src/Impostor.Api/Net/Messages/Announcements/Message01Update.cs
new file mode 100644 (file)
index 0000000..b8bc284
--- /dev/null
@@ -0,0 +1,19 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Announcements/Message02SetFreeWeekend.cs b/src/Impostor.Api/Net/Messages/Announcements/Message02SetFreeWeekend.cs
new file mode 100644 (file)
index 0000000..054f313
--- /dev/null
@@ -0,0 +1,19 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Announcements/MessageHello.cs b/src/Impostor.Api/Net/Messages/Announcements/MessageHello.cs
new file mode 100644 (file)
index 0000000..c695037
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/AnnouncementsMessageFlags.cs b/src/Impostor.Api/Net/Messages/AnnouncementsMessageFlags.cs
new file mode 100644 (file)
index 0000000..498d3e4
--- /dev/null
@@ -0,0 +1,9 @@
+namespace Impostor.Api.Net.Messages
+{
+    public class AnnouncementsMessageFlags
+    {
+        public const byte UseCache = 0;
+        public const byte SetUpdate = 1;
+        public const byte SetFreeWeekend = 2;
+    }
+}
index 4f5b39ce7bca57743b0d3bedf078ff5238437093..f90d7f53f7a3d990d57ec19b56e2fd850f403396 100644 (file)
@@ -1,5 +1,4 @@
-using System.IO;
-using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth;
 
 namespace Impostor.Api.Net.Messages.C2S
 {
@@ -8,14 +7,7 @@ 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();
         }
 
@@ -24,4 +16,4 @@ namespace Impostor.Api.Net.Messages.C2S
             return GameOptionsData.DeserializeCreate(reader);
         }
     }
-}
\ No newline at end of file
+}
index 87c06c4488648f3d6a4c925a7bc2817bc78282f5..1eb82d1f3c4bc76817e4187fd9491d0d34b00267 100644 (file)
@@ -1,4 +1,7 @@
 using System;
+using System.Numerics;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner;
 
 namespace Impostor.Api.Net.Messages
 {
@@ -47,6 +50,8 @@ namespace Impostor.Api.Net.Messages
 
         float ReadSingle();
 
+        string ReadString(int length);
+
         string ReadString();
 
         ReadOnlyMemory<byte> ReadBytesAndSize();
@@ -64,5 +69,10 @@ namespace Impostor.Api.Net.Messages
         void RemoveMessage(IMessageReader message);
 
         IMessageReader Copy(int offset = 0);
+
+        T? ReadNetObject<T>(IGame game)
+            where T : IInnerNetObject;
+
+        Vector2 ReadVector2();
     }
 }
index 4f6765bfe3e2c308e3cb6b634c9dc53bf3d75ea6..0f4b7faddb35aceacc57e1aca92897fc27f537f8 100644 (file)
@@ -1,6 +1,8 @@
 using System;
 using System.Net;
+using System.Numerics;
 using Impostor.Api.Games;
+using Impostor.Api.Net.Inner;
 
 namespace Impostor.Api.Net.Messages
 {
@@ -107,6 +109,10 @@ 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>
@@ -124,4 +130,4 @@ namespace Impostor.Api.Net.Messages
         /// <param name="type">New type of the message.</param>
         void Clear(MessageType type);
     }
-}
\ No newline at end of file
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc00PlayAnimation.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc00PlayAnimation.cs
new file mode 100644 (file)
index 0000000..f7f9183
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc01CompleteTask.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc01CompleteTask.cs
new file mode 100644 (file)
index 0000000..dfe7ec0
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs
new file mode 100644 (file)
index 0000000..047d21f
--- /dev/null
@@ -0,0 +1,17 @@
+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());
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc03SetInfected.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc03SetInfected.cs
new file mode 100644 (file)
index 0000000..27a11e9
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc04Exiled.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc04Exiled.cs
new file mode 100644 (file)
index 0000000..7dba967
--- /dev/null
@@ -0,0 +1,13 @@
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+    public static class Rpc04Exiled
+    {
+        public static void Serialize(IMessageWriter writer)
+        {
+        }
+
+        public static void Deserialize(IMessageReader reader)
+        {
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc05CheckName.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc05CheckName.cs
new file mode 100644 (file)
index 0000000..0147075
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs
new file mode 100644 (file)
index 0000000..e2e8ae4
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc07CheckColor.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc07CheckColor.cs
new file mode 100644 (file)
index 0000000..d33875a
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs
new file mode 100644 (file)
index 0000000..50ea3d6
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc09SetHat.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc09SetHat.cs
new file mode 100644 (file)
index 0000000..caa9ff7
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc10SetSkin.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc10SetSkin.cs
new file mode 100644 (file)
index 0000000..e57eeff
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc11ReportDeadBody.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc11ReportDeadBody.cs
new file mode 100644 (file)
index 0000000..a8707a9
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc12MurderPlayer.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc12MurderPlayer.cs
new file mode 100644 (file)
index 0000000..82f55f6
--- /dev/null
@@ -0,0 +1,18 @@
+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);
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc13SendChat.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc13SendChat.cs
new file mode 100644 (file)
index 0000000..dcce229
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc14StartMeeting.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc14StartMeeting.cs
new file mode 100644 (file)
index 0000000..8001e43
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc15SetScanner.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc15SetScanner.cs
new file mode 100644 (file)
index 0000000..f46adea
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc16SendChatNote.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc16SendChatNote.cs
new file mode 100644 (file)
index 0000000..4def9d1
--- /dev/null
@@ -0,0 +1,19 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc17SetPet.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc17SetPet.cs
new file mode 100644 (file)
index 0000000..be67df5
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc18SetStartCounter.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc18SetStartCounter.cs
new file mode 100644 (file)
index 0000000..cb3c2d1
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc19EnterVent.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc19EnterVent.cs
new file mode 100644 (file)
index 0000000..eca9f13
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc20ExitVent.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc20ExitVent.cs
new file mode 100644 (file)
index 0000000..ddfaf44
--- /dev/null
@@ -0,0 +1,15 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc21SnapTo.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc21SnapTo.cs
new file mode 100644 (file)
index 0000000..576a911
--- /dev/null
@@ -0,0 +1,19 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc22Close.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc22Close.cs
new file mode 100644 (file)
index 0000000..00caaf3
--- /dev/null
@@ -0,0 +1,13 @@
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+    public static class Rpc22Close
+    {
+        public static void Serialize(IMessageWriter writer)
+        {
+        }
+
+        public static void Deserialize(IMessageReader reader)
+        {
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc23VotingComplete.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc23VotingComplete.cs
new file mode 100644 (file)
index 0000000..bf94827
--- /dev/null
@@ -0,0 +1,21 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc24CastVote.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc24CastVote.cs
new file mode 100644 (file)
index 0000000..1c51bc1
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc25ClearVote.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc25ClearVote.cs
new file mode 100644 (file)
index 0000000..5a89331
--- /dev/null
@@ -0,0 +1,13 @@
+namespace Impostor.Api.Net.Messages.Rpcs
+{
+    public static class Rpc25ClearVote
+    {
+        public static void Serialize(IMessageWriter writer)
+        {
+        }
+
+        public static void Deserialize(IMessageReader reader)
+        {
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc26AddVote.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc26AddVote.cs
new file mode 100644 (file)
index 0000000..f665acc
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc27CloseDoorsOfType.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc27CloseDoorsOfType.cs
new file mode 100644 (file)
index 0000000..4e3c822
--- /dev/null
@@ -0,0 +1,17 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc28RepairSystem.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc28RepairSystem.cs
new file mode 100644 (file)
index 0000000..411602d
--- /dev/null
@@ -0,0 +1,23 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs
new file mode 100644 (file)
index 0000000..aba9a71
--- /dev/null
@@ -0,0 +1,19 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc30UpdateGameData.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc30UpdateGameData.cs
new file mode 100644 (file)
index 0000000..4403cf6
--- /dev/null
@@ -0,0 +1,18 @@
+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();
+        }
+    }
+}
index 4ba23c2fcf68519c6a0f6d67bb3f49e6dd8d0593..51021f56f3231dabf46f4b765a4339ac9226c82b 100644 (file)
@@ -9,7 +9,8 @@
         <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" />
diff --git a/src/Impostor.Api/Reactor/Mod.cs b/src/Impostor.Api/Reactor/Mod.cs
new file mode 100644 (file)
index 0000000..2654c5f
--- /dev/null
@@ -0,0 +1,21 @@
+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})";
+        }
+    }
+}
diff --git a/src/Impostor.Api/Reactor/ModList.cs b/src/Impostor.Api/Reactor/ModList.cs
new file mode 100644 (file)
index 0000000..047c8b4
--- /dev/null
@@ -0,0 +1,24 @@
+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));
+            }
+        }
+    }
+}
diff --git a/src/Impostor.Api/Reactor/ModdedHandshakeC2S.cs b/src/Impostor.Api/Reactor/ModdedHandshakeC2S.cs
new file mode 100644 (file)
index 0000000..a796712
--- /dev/null
@@ -0,0 +1,23 @@
+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;
+            }
+        }
+    }
+}
diff --git a/src/Impostor.Api/Reactor/ModdedHandshakeS2C.cs b/src/Impostor.Api/Reactor/ModdedHandshakeS2C.cs
new file mode 100644 (file)
index 0000000..07f4f9c
--- /dev/null
@@ -0,0 +1,14 @@
+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();
+        }
+    }
+}
diff --git a/src/Impostor.Api/Reactor/PluginSide.cs b/src/Impostor.Api/Reactor/PluginSide.cs
new file mode 100644 (file)
index 0000000..30c588c
--- /dev/null
@@ -0,0 +1,23 @@
+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,
+    }
+}
index 4b034176de4fbde5bc46d1627ebeea4795243d64..f796decdfb1dc0133960f883d0182e807589f5f7 100644 (file)
         ///   <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
+}
index 3e035fbe96ebd8ea56e10adbef1e4a095674dee4..00997697c8109d0db4e4c5e3a121f967ee6c5c84 100644 (file)
@@ -4,7 +4,6 @@
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         <TargetFramework>net5.0</TargetFramework>
         <DefineConstants>HAZEL_BAG</DefineConstants>
-        <Version>1.0.0</Version>
     </PropertyGroup>
 
     <ItemGroup>
index 986d0b02463a0171e568efae45748f7bcc26db92..35933298d6a4135ab49218f66f125943bde39fb8 100644 (file)
@@ -1,10 +1,14 @@
 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
@@ -123,14 +127,18 @@ 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();
@@ -239,6 +247,19 @@ namespace Impostor.Hazel
             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()
         {
index 5b7342a31b879396aa0217acf17567e51e84c381..a7b57e78e7c3e3ea599dc824d6d959f0efda6364 100644 (file)
@@ -4,7 +4,10 @@ using Impostor.Api.Net.Messages;
 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
 {
@@ -89,6 +92,24 @@ 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)
         {
index a7a4309e9cbb50a1a61b3fe7bffbf198c53014c1..7f439269d2001d8c280b661e987914402343fed8 100644 (file)
@@ -309,9 +309,6 @@ namespace Impostor.Hazel.Udp
             //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...)
              * 
@@ -337,6 +334,8 @@ namespace Impostor.Hazel.Udp
              * So...
              */
             
+            var result = true;
+
             lock (reliableDataPacketsMissing)
             {
                 //Calculate overwritePointer
@@ -379,12 +378,15 @@ namespace Impostor.Hazel.Udp
                     //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>
index 95f552480d5be790eade8455af6d1c24125880ff..7c1ced2630fabcb2ec846e00fd78a767ddbe1b59 100644 (file)
@@ -143,7 +143,10 @@ namespace Impostor.Patcher.Shared
                 return false;
             }
 
-            return WriteIp(ipAddress, port);
+            var result = WriteIp(ipAddress, port);
+            OnSaved(ip, port);
+
+            return result;
         }
 
         /// <summary>
@@ -176,7 +179,6 @@ namespace Impostor.Patcher.Shared
 
                 region.Serialize(writer);
 
-                OnSaved(ip, port);
                 return true;
             }
         }
index e480870c8860e4fb92a2a6e93b1afbb37c0f7a08..8d71e56cfa04f2185efe5f19f3f27d594c26bcce 100644 (file)
@@ -2,7 +2,6 @@
 
     <PropertyGroup>
         <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
-        <Version>1.0.0</Version>
     </PropertyGroup>
 
     <ItemGroup>
index 936f15e25fc9a768461c167cd9b7e1e3b5e353b5..673026daac3d8e5fab5220cba0bdf74c1f971f9c 100644 (file)
@@ -17,6 +17,7 @@ namespace Impostor.Plugins.Example
             services.AddSingleton<IEventListener, GameEventListener>();
             services.AddSingleton<IEventListener, PlayerEventListener>();
             services.AddSingleton<IEventListener, MeetingEventListener>();
+            services.AddSingleton<IEventListener, AnnouncementsListener>();
         }
     }
 }
diff --git a/src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs b/src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs
new file mode 100644 (file)
index 0000000..42ca3f2
--- /dev/null
@@ -0,0 +1,26 @@
+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!");
+            }
+        }
+    }
+}
diff --git a/src/Impostor.Server/Config/AnnouncementsServerConfig.cs b/src/Impostor.Server/Config/AnnouncementsServerConfig.cs
new file mode 100644 (file)
index 0000000..5d61ac0
--- /dev/null
@@ -0,0 +1,22 @@
+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);
+        }
+    }
+}
index f4807e7f1dcfa2ed1ec96c3eeb64d971bf97a19c..ee1f62a620cfb254cf0977ea56a48da55dcf3981 100644 (file)
@@ -4,6 +4,8 @@
     {
         public const string Section = "AntiCheat";
 
+        public bool Enabled { get; set; } = true;
+
         public bool BanIpFromGame { get; set; } = true;
     }
-}
\ No newline at end of file
+}
diff --git a/src/Impostor.Server/Events/Announcements/AnnouncementRequestEvent.cs b/src/Impostor.Server/Events/Announcements/AnnouncementRequestEvent.cs
new file mode 100644 (file)
index 0000000..0f5b552
--- /dev/null
@@ -0,0 +1,29 @@
+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;
+        }
+    }
+}
index 5625c4dd94373fc58de95112e63900455e40775e..0cc6e541efb424ecd16cd7dbdfe222f061169547 100644 (file)
@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
 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;
@@ -91,10 +90,6 @@ namespace Impostor.Server.Events
                     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);
index 7b7eb22fed8905db660fcbe42db6f55b9db53085..32c156145b78d814d911c2c9aa88bc3fccfe112b 100644 (file)
@@ -1,11 +1,12 @@
-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)
         {
@@ -22,5 +23,7 @@ namespace Impostor.Server.Events.Player
         public IInnerPlayerControl PlayerControl { get; }
 
         public string Message { get; }
+
+        public bool IsCancelled { get; set; }
     }
 }
index 330135d16587a4342a1edc8877b4a46227fdb6fa..00e2b2c8f46140d31d84c6b781f497e863db3647 100644 (file)
@@ -1,6 +1,5 @@
 using Impostor.Api.Events.Player;
 using Impostor.Api.Games;
-using Impostor.Api.Innersloth;
 using Impostor.Api.Net;
 using Impostor.Api.Net.Inner.Objects;
 
index 31ac388614f955035fb0155f742fa2b1582493f5..60dbc12150048730030c3d9202075c6209faa819 100644 (file)
@@ -2,23 +2,46 @@
 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;
+            }
+        }
     }
 }
diff --git a/src/Impostor.Server/Extensions/MessageReaderExtensions.cs b/src/Impostor.Server/Extensions/MessageReaderExtensions.cs
deleted file mode 100644 (file)
index 5f25e89..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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
diff --git a/src/Impostor.Server/Extensions/ServiceProviderExtensions.cs b/src/Impostor.Server/Extensions/ServiceProviderExtensions.cs
new file mode 100644 (file)
index 0000000..eee1480
--- /dev/null
@@ -0,0 +1,22 @@
+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);
+            });
+        }
+    }
+}
index ad5a6db05ab02927ab0413fb964d829e35cce28b..663f7af43bade6e32ee0b3174bb27d02ab202701 100644 (file)
@@ -16,7 +16,6 @@
     <AssemblyTitle>Impostor.Server</AssemblyTitle>
     <Product>Impostor.Server</Product>
     <Copyright>Copyright © AeonLucid 2020</Copyright>
-    <Version>1.0.0</Version>
   </PropertyGroup>
 
   <ItemGroup>
@@ -35,6 +34,8 @@
       <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>
@@ -55,4 +56,4 @@
     </Content>
   </ItemGroup>
 
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/Impostor.Server/Net/AnnouncementsService.cs b/src/Impostor.Server/Net/AnnouncementsService.cs
new file mode 100644 (file)
index 0000000..d205045
--- /dev/null
@@ -0,0 +1,98 @@
+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);
+        }
+    }
+}
index 87a1bb40e967bd208c82e532445b013aabdb9c63..143a0d1032418f810772d404f4f58195f2d45d78 100644 (file)
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 using Impostor.Api;
 using Impostor.Api.Games;
@@ -7,6 +8,7 @@ using Impostor.Api.Net;
 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;
@@ -22,8 +24,8 @@ namespace Impostor.Server.Net
         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;
@@ -31,6 +33,25 @@ namespace Impostor.Server.Net
             _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;
@@ -150,38 +171,25 @@ namespace Impostor.Server.Net
                     // 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;
                 }
@@ -196,7 +204,7 @@ namespace Impostor.Server.Net
                     Message08EndGameC2S.Deserialize(
                         reader,
                         out var gameOverReason);
-                    
+
                     await Player.Game.HandleEndGame(reader, gameOverReason);
                     break;
                 }
index 527919256e16aebf027deec65f725abd10a011d9..bae1fb33f72b77b91557ebf62bb827c4d66988d1 100644 (file)
@@ -1,11 +1,14 @@
 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;
 
@@ -13,17 +16,34 @@ namespace Impostor.Server.Net
 {
     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; }
@@ -32,6 +52,11 @@ namespace Impostor.Server.Net
 
         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);
index ad1fdc778e95cef00a3910599008ecd1ed3f4900..ab6eedf615b49c353ca38deac3633cef2861834b 100644 (file)
@@ -1,5 +1,7 @@
 using System;
+using System.Collections.Generic;
 using Impostor.Api.Net;
+using Impostor.Api.Reactor;
 using Microsoft.Extensions.DependencyInjection;
 
 namespace Impostor.Server.Net.Factories
@@ -14,9 +16,9 @@ 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;
         }
index 6859ae3c950dc9dc3cdc1ec2b956fdf5f95a1693..bec1b79ba77d1af27f00f5884ec17822da4d84bc 100644 (file)
@@ -1,9 +1,11 @@
-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
diff --git a/src/Impostor.Server/Net/Inner/InnerNetObject.Anticheat.cs b/src/Impostor.Server/Net/Inner/InnerNetObject.Anticheat.cs
new file mode 100644 (file)
index 0000000..28b3457
--- /dev/null
@@ -0,0 +1,98 @@
+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;
+        }
+    }
+}
index 78f1a55cda6e92647e3b430e146cb7017f37c610..9579fe0ad7d8f27864d685947c1ca7f203bf73c4 100644 (file)
@@ -1,4 +1,5 @@
-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;
@@ -6,7 +7,7 @@ using Impostor.Server.Net.State;
 
 namespace Impostor.Server.Net.Inner
 {
-    internal abstract class InnerNetObject : GameObject, IInnerNetObject
+    internal abstract partial class InnerNetObject : GameObject, IInnerNetObject
     {
         private const int HostInheritId = -2;
 
@@ -16,16 +17,31 @@ namespace Impostor.Server.Net.Inner
 
         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
+}
index 6cdd6b91fd81174a501dbedc82791074d6cd056d..56ff96e5940c4a8c6b7387cd065bb377ddc61f9e 100644 (file)
@@ -11,12 +11,12 @@ namespace Impostor.Server.Net.Inner.Objects.Components
             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);
             }
index d261c11e047a0b091ecbc00d0395cafc6e82cc8c..80dd6adf68c98f225eed7e13cd31a9c2b507d02f 100644 (file)
-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))
@@ -128,21 +87,48 @@ namespace Impostor.Server.Net.Inner.Objects.Components
                 }
 
                 _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);
         }
     }
 }
index 29bc9962d71794e0ea41b6c905d125b9e6ea4d28..69f8dcd81a7071b00c8750c111e5ad5bc8acf7ba 100644 (file)
@@ -1,10 +1,11 @@
 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;
@@ -26,45 +27,45 @@ namespace Impostor.Server.Net.Inner.Objects.Components
             _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;
         }
     }
 }
index 58b9b54a2c63f1608e3043978b13fc9b22bb6d3b..117f78e6dd8df5ca361a527bfbc65ff622a95203 100644 (file)
@@ -5,6 +5,7 @@ using Impostor.Api;
 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;
 
@@ -21,42 +22,16 @@ namespace Impostor.Server.Net.Inner.Objects.Components
             _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;
@@ -84,5 +59,30 @@ namespace Impostor.Server.Net.Inner.Objects.Components
                 }
             }
         }
+
+        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
+}
index ed68038881abb361a63e28b4870be8b440d66441..0b2dbf4a52a05ea350a9c7869ba036237db1d2e9 100644 (file)
@@ -3,10 +3,10 @@ using System.Collections.Concurrent;
 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;
@@ -44,41 +44,59 @@ namespace Impostor.Server.Net.Inner.Objects
             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();
@@ -103,49 +121,14 @@ namespace Impostor.Server.Net.Inner.Objects
                     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)
index 63448edd2d935625e1bcb085c415469bb009b065..78be5bd23f8e79c0e05a711f1d895b08d90a86d3 100644 (file)
@@ -18,19 +18,19 @@ namespace Impostor.Server.Net.Inner.Objects
             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
+}
index c2bcb9d9d98d6125cd1ceda69c1e8cd33c0a24ad..96d829fb87fa718a703abd8859495ccba9f5e76c 100644 (file)
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using Impostor.Api;
@@ -6,6 +7,7 @@ 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.Meeting;
 using Impostor.Server.Events.Player;
 using Impostor.Server.Net.State;
@@ -46,131 +48,141 @@ namespace Impostor.Server.Net.Inner.Objects
                 .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;
         }
     }
 }
index 0a7997d5c7149e8035015e0f50b874c1a3db8c43..07c21267cef632a4b648f4c39a6cc13d0ed19c69 100644 (file)
@@ -2,9 +2,9 @@ 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.Inner.Objects.Components;
+using Impostor.Api.Net.Messages.Rpcs;
 using Impostor.Server.Events.Player;
 
 namespace Impostor.Server.Net.Inner.Objects
@@ -26,62 +26,42 @@ 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);
@@ -101,38 +81,30 @@ namespace Impostor.Server.Net.Inner.Objects
             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));
         }
     }
 }
index 1fe1dc833c2c46e695ee10c03e6e55b0d94352bf..c2759139ae8985ac8246552498fbe28f267c5aff 100644 (file)
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using Impostor.Api;
@@ -6,10 +7,13 @@ using Impostor.Api.Events.Managers;
 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;
 
@@ -20,12 +24,14 @@ namespace Impostor.Server.Net.Inner.Objects
         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);
@@ -47,521 +53,543 @@ namespace Impostor.Server.Net.Inner.Objects
 
         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;
         }
     }
 }
index f1409f9386c3bfc01a751724e957a465da18d08f..410b4af610e8381092702f485f1b0b4efbcfe33f 100644 (file)
@@ -2,6 +2,7 @@
 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
@@ -19,17 +20,13 @@ 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; }
 
@@ -61,10 +58,10 @@ namespace Impostor.Server.Net.Inner.Objects
         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;
index b1a3f18f9b08864cacc33b7a92dec78ba2f4540d..3725c8faa5fe08563b9d918b9e5187c468c6f9f0 100644 (file)
@@ -6,6 +6,7 @@ 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.Systems;
 using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus;
 using Impostor.Server.Net.State;
@@ -37,82 +38,22 @@ namespace Impostor.Server.Net.Inner.Objects
 
             _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)
@@ -143,5 +84,47 @@ namespace Impostor.Server.Net.Inner.Objects
                 }
             }
         }
+
+        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
+}
diff --git a/src/Impostor.Server/Net/Inner/RpcCalls.cs b/src/Impostor.Server/Net/Inner/RpcCalls.cs
deleted file mode 100644 (file)
index ce48965..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-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
index 51e22d81d111e69e13751143452587e2793b6a6f..cc7e46cd4a249924277597fd7f0d81729fda38fa 100644 (file)
@@ -7,9 +7,11 @@ 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.Config;
 using Impostor.Server.Net.Factories;
+using Impostor.Server.Utils;
 using Microsoft.Extensions.Logging;
 
 namespace Impostor.Server.Net.Manager
@@ -23,6 +25,8 @@ 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;
@@ -53,7 +57,7 @@ namespace Impostor.Server.Net.Manager
             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))
             {
@@ -79,12 +83,16 @@ namespace Impostor.Server.Net.Manager
                 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)
index 64ece55f0abc3bd622440dba6ed5a2ea01471a2c..e700703cadd28bd6efbbbe08375c5c6eaea7d322 100644 (file)
@@ -2,6 +2,7 @@
 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;
@@ -54,13 +55,12 @@ namespace Impostor.Server.Net
         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);
         }
     }
 }
index 3dad4b34d9badb0fea62d6434ef7d2f7bccb9f00..bda07fda2e3641dcb32e76b097c91f442c1048eb 100644 (file)
@@ -1,8 +1,10 @@
-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;
@@ -23,10 +25,11 @@ namespace Impostor.Server.Net.Redirector
         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;
index f395be29234b3a12c86a4eed8dc4694a14dbd0bf..1a623be9653a3c5a791a903c49dbcbabebe9fb24 100644 (file)
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-using System.IO;
+using System.IO;
 using System.Net;
 using System.Threading.Tasks;
 using Impostor.Api;
@@ -7,7 +6,6 @@ using Impostor.Api.Games;
 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
@@ -46,25 +44,5 @@ 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);
-            }
-        }
     }
 }
index a84d9b59c9dd308cf3ac0e5861e74e8d8070c9ed..bb386d6fca0085d95e08f6e0398f04765b6fd0dc 100644 (file)
@@ -4,6 +4,7 @@ using System.Threading;
 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;
@@ -231,7 +232,7 @@ namespace Impostor.Server.Net.State
                         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
                         {
@@ -246,7 +247,10 @@ namespace Impostor.Server.Net.State
                         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
                         {
@@ -261,7 +265,10 @@ namespace Impostor.Server.Net.State
                         // 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();
@@ -322,7 +329,7 @@ namespace Impostor.Server.Net.State
                                 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);
@@ -435,15 +442,15 @@ namespace Impostor.Server.Net.State
             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;
         }
     }
 }
index 4bf1c43c0b06f16405c8091711bc86e3de21b6e8..547224db2bb166d0b6b0342465b67f0751d12afd 100644 (file)
@@ -1,10 +1,12 @@
 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;
@@ -79,6 +81,25 @@ namespace Impostor.Server.Net.State
                 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)
index e311776078ebb97b8c01a4f2408b427237bf0598..e24e798a99be0eef94774389822008a0b034019a 100644 (file)
@@ -102,6 +102,12 @@ namespace Impostor.Server.Net.State
                 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);
 
@@ -133,4 +139,4 @@ namespace Impostor.Server.Net.State
             }
         }
     }
-}
\ No newline at end of file
+}
index 19198675e17bdb78e0a6d602ec95cec20b975327..a1aeb48a66b45849bd3e8f8dc10ae15039babf1f 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 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;
@@ -71,7 +72,7 @@ namespace Impostor.Server.Net.State
 
         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);
 
@@ -92,16 +93,20 @@ namespace Impostor.Server.Net.State
             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()
index 4e728868f3bc628b52e8c77a0cc34c01ac46e9d3..f753bd13570bf2b50a67dd30032f86cd21c55e80 100644 (file)
@@ -5,7 +5,6 @@ using System.Linq;
 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;
index d11694f7131cf4a1d13d08a3b5831de06ebaeee9..2af8d55043a38787c6cd2669d50372ef975b2a7b 100644 (file)
@@ -112,9 +112,16 @@ namespace Impostor.Server
                         .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)
@@ -191,12 +198,18 @@ namespace Impostor.Server
                         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()
index 3654bc34c0c7d6dd026c590f8177c2403575e7d7..fd6daac021f0eb18d84b28b8b467cb36276a8bd6 100644 (file)
@@ -10,6 +10,7 @@
     </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" />
index 5763c70b1ce7c49a1b4672991d2e67af34b32d26..868aa0a4cb1b5c594ed940a54475ce34743d993c 100644 (file)
@@ -1,5 +1,7 @@
-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;
@@ -16,8 +18,8 @@ namespace Impostor.Server.Recorder
         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;
diff --git a/src/Impostor.Server/Utils/ServerEnvironment.cs b/src/Impostor.Server/Utils/ServerEnvironment.cs
new file mode 100644 (file)
index 0000000..9c9836a
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Impostor.Server.Utils
+{
+    public class ServerEnvironment
+    {
+        public bool IsReplay { get; init; }
+    }
+}
index dfee1bcbfc7d5b373b4834e0c40ac3d6c52c41cc..edf7716ca36f055ab50029ac1176944e17bf3c98 100644 (file)
@@ -1,11 +1,17 @@
-{
+{
   "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": {
@@ -26,4 +32,4 @@
     "GameRecorderEnabled": true,
     "GameRecorderPath": ""
   }
-}
\ No newline at end of file
+}
index 5aaa954cd90f788134ca2c5ac848b153c021b6bc..1f5e49452a021b46beae5beb8658d60128dd940d 100644 (file)
@@ -13,12 +13,14 @@ using Impostor.Api.Net.Messages;
 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;
@@ -82,6 +84,11 @@ namespace Impostor.Tools.ServerReplay
         {
             var services = new ServiceCollection();
 
+            services.AddSingleton(new ServerEnvironment
+            {
+                IsReplay = true
+            });
+
             services.AddLogging(builder =>
             {
                 builder.ClearProviders();
@@ -99,6 +106,7 @@ namespace Impostor.Tools.ServerReplay
             services.AddSingleton<INodeLocator, NodeLocatorNoOp>();
             services.AddSingleton<IEventManager, EventManager>();
 
+            services.AddEventPools();
             services.AddHazel();
 
             return services.BuildServiceProvider();
@@ -121,7 +129,7 @@ namespace Impostor.Tools.ServerReplay
 
         private static async Task ParsePacket(BinaryReader reader)
         {
-            var dataType = (RecordedPacketType) reader.ReadByte();
+            var dataType = (RecordedPacketType)reader.ReadByte();
 
             // Read client id.
             var clientId = reader.ReadInt32();
@@ -139,7 +147,7 @@ namespace Impostor.Tools.ServerReplay
                     // 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);