From 87a062c1b3f5fecf6686bd6be4fe64047a583902 Mon Sep 17 00:00:00 2001 From: js6pak Date: Wed, 3 Mar 2021 08:53:53 +0000 Subject: [PATCH] Sync Impostor with 6pak fork (#327) Co-authored-by: DatGuy1 Co-authored-by: miniduikboot <5243971+miniduikboot@users.noreply.github.com> --- .github/stale.yml | 17 - appveyor.yml | 5 +- build.cake | 92 +-- src/.editorconfig | 4 +- src/.gitignore | 149 ---- src/Directory.Build.props | 7 + src/Impostor.Api/CheatContext.cs | 20 + .../IAnnouncementRequestEvent.cs | 43 ++ .../Game/Player/IPlayerMovementEvent.cs | 6 + .../Exceptions/ImpostorCheatException.cs | 24 - src/Impostor.Api/Games/IGame.cs | 11 +- src/Impostor.Api/Impostor.Api.csproj | 4 +- src/Impostor.Api/Innersloth/Announcement.cs | 14 + .../Innersloth/Customization/HatType.cs | 11 +- .../Innersloth/Customization/PetType.cs | 2 +- .../Innersloth/Customization/SkinType.cs | 2 +- src/Impostor.Api/Innersloth/FloatRange.cs | 22 - .../Innersloth/FreeWeekendState.cs | 12 + .../Innersloth/GameOptionsData.cs | 10 +- src/Impostor.Api/Innersloth/Language.cs | 11 + src/Impostor.Api/Innersloth/MapSpawn.cs | 45 ++ src/Impostor.Api/Net/IClient.cs | 8 + .../IInnerCustomNetworkTransform.cs | 10 + .../Net/Inner/Objects/IInnerPlayerControl.cs | 34 +- .../Net/Inner/Objects/IInnerPlayerInfo.cs | 9 +- .../Net/Inner/RpcCalls.cs | 1 + .../Announcements/Message00UseCache.cs | 15 + .../Messages/Announcements/Message01Update.cs | 19 + .../Announcements/Message02SetFreeWeekend.cs | 19 + .../Messages/Announcements/MessageHello.cs | 15 + .../Net/Messages/AnnouncementsMessageFlags.cs | 9 + .../Net/Messages/C2S/Message00HostGameC2S.cs | 14 +- .../Net/Messages/IMessageReader.cs | 10 + .../Net/Messages/IMessageWriter.cs | 8 +- .../Net/Messages/Rpcs/Rpc00PlayAnimation.cs | 17 + .../Net/Messages/Rpcs/Rpc01CompleteTask.cs | 15 + .../Net/Messages/Rpcs/Rpc02SyncSettings.cs | 17 + .../Net/Messages/Rpcs/Rpc03SetInfected.cs | 17 + .../Net/Messages/Rpcs/Rpc04Exiled.cs | 13 + .../Net/Messages/Rpcs/Rpc05CheckName.cs | 15 + .../Net/Messages/Rpcs/Rpc06SetName.cs | 15 + .../Net/Messages/Rpcs/Rpc07CheckColor.cs | 17 + .../Net/Messages/Rpcs/Rpc08SetColor.cs | 17 + .../Net/Messages/Rpcs/Rpc09SetHat.cs | 17 + .../Net/Messages/Rpcs/Rpc10SetSkin.cs | 17 + .../Net/Messages/Rpcs/Rpc11ReportDeadBody.cs | 15 + .../Net/Messages/Rpcs/Rpc12MurderPlayer.cs | 18 + .../Net/Messages/Rpcs/Rpc13SendChat.cs | 15 + .../Net/Messages/Rpcs/Rpc14StartMeeting.cs | 15 + .../Net/Messages/Rpcs/Rpc15SetScanner.cs | 17 + .../Net/Messages/Rpcs/Rpc16SendChatNote.cs | 19 + .../Net/Messages/Rpcs/Rpc17SetPet.cs | 17 + .../Net/Messages/Rpcs/Rpc18SetStartCounter.cs | 17 + .../Net/Messages/Rpcs/Rpc19EnterVent.cs | 15 + .../Net/Messages/Rpcs/Rpc20ExitVent.cs | 15 + .../Net/Messages/Rpcs/Rpc21SnapTo.cs | 19 + .../Net/Messages/Rpcs/Rpc22Close.cs | 13 + .../Net/Messages/Rpcs/Rpc23VotingComplete.cs | 21 + .../Net/Messages/Rpcs/Rpc24CastVote.cs | 17 + .../Net/Messages/Rpcs/Rpc25ClearVote.cs | 13 + .../Net/Messages/Rpcs/Rpc26AddVote.cs | 17 + .../Messages/Rpcs/Rpc27CloseDoorsOfType.cs | 17 + .../Net/Messages/Rpcs/Rpc28RepairSystem.cs | 23 + .../Net/Messages/Rpcs/Rpc29SetTasks.cs | 19 + .../Net/Messages/Rpcs/Rpc30UpdateGameData.cs | 18 + src/Impostor.Api/ProjectRules.ruleset | 3 +- src/Impostor.Api/Reactor/Mod.cs | 21 + src/Impostor.Api/Reactor/ModList.cs | 24 + .../Reactor/ModdedHandshakeC2S.cs | 23 + .../Reactor/ModdedHandshakeS2C.cs | 14 + src/Impostor.Api/Reactor/PluginSide.cs | 23 + src/Impostor.Api/Unity/Mathf.cs | 7 +- src/Impostor.Hazel/Impostor.Hazel.csproj | 1 - src/Impostor.Hazel/MessageReader.cs | 29 +- src/Impostor.Hazel/MessageWriter.cs | 21 + .../Udp/UdpConnection.Reliable.cs | 12 +- .../AmongUsModifier.cs | 6 +- .../Impostor.Patcher.Shared.csproj | 1 - .../ExamplePluginStartup.cs | 1 + .../Handlers/AnnouncementsListener.cs | 26 + .../Config/AnnouncementsServerConfig.cs | 22 + src/Impostor.Server/Config/AntiCheatConfig.cs | 4 +- .../Announcements/AnnouncementRequestEvent.cs | 29 + src/Impostor.Server/Events/EventManager.cs | 5 - .../Events/Game/Player/PlayerChatEvent.cs | 7 +- .../Game/Player/PlayerCompletedTaskEvent.cs | 1 - .../Events/Game/Player/PlayerMovementEvent.cs | 35 +- .../Extensions/MessageReaderExtensions.cs | 15 - .../Extensions/ServiceProviderExtensions.cs | 22 + src/Impostor.Server/Impostor.Server.csproj | 5 +- .../Net/AnnouncementsService.cs | 98 +++ src/Impostor.Server/Net/Client.cs | 66 +- src/Impostor.Server/Net/ClientBase.cs | 27 +- .../Net/Factories/ClientFactory.cs | 6 +- .../Net/Factories/IClientFactory.cs | 6 +- .../Net/Inner/InnerNetObject.Anticheat.cs | 98 +++ .../Net/Inner/InnerNetObject.cs | 34 +- .../InnerCustomNetworkTransform.Api.cs | 4 +- .../Components/InnerCustomNetworkTransform.cs | 138 ++-- .../Objects/Components/InnerPlayerPhysics.cs | 57 +- .../Objects/Components/InnerVoteBanSystem.cs | 62 +- .../Net/Inner/Objects/InnerGameData.cs | 107 ++- .../Net/Inner/Objects/InnerLobbyBehaviour.cs | 8 +- .../Net/Inner/Objects/InnerMeetingHud.cs | 160 ++-- .../Inner/Objects/InnerPlayerControl.Api.cs | 76 +- .../Net/Inner/Objects/InnerPlayerControl.cs | 706 +++++++++--------- .../Net/Inner/Objects/InnerPlayerInfo.cs | 21 +- .../Net/Inner/Objects/InnerShipStatus.cs | 115 ++- .../Net/Manager/ClientManager.cs | 12 +- src/Impostor.Server/Net/Matchmaker.cs | 6 +- .../Net/Redirector/ClientRedirector.cs | 7 +- src/Impostor.Server/Net/State/Game.Api.cs | 24 +- src/Impostor.Server/Net/State/Game.Data.cs | 23 +- .../Net/State/Game.Incoming.cs | 21 + src/Impostor.Server/Net/State/Game.State.cs | 8 +- src/Impostor.Server/Net/State/Game.cs | 15 +- src/Impostor.Server/Plugins/PluginLoader.cs | 1 - src/Impostor.Server/Program.cs | 13 + src/Impostor.Server/ProjectRules.ruleset | 1 + .../Recorder/ClientRecorder.cs | 8 +- .../Utils/ServerEnvironment.cs | 7 + src/Impostor.Server/config-full.json | 10 +- src/Impostor.Tools.ServerReplay/Program.cs | 12 +- 123 files changed, 2290 insertions(+), 1223 deletions(-) delete mode 100644 .github/stale.yml create mode 100644 src/Directory.Build.props create mode 100644 src/Impostor.Api/CheatContext.cs create mode 100644 src/Impostor.Api/Events/Announcements/IAnnouncementRequestEvent.cs create mode 100644 src/Impostor.Api/Events/Game/Player/IPlayerMovementEvent.cs delete mode 100644 src/Impostor.Api/Exceptions/ImpostorCheatException.cs create mode 100644 src/Impostor.Api/Innersloth/Announcement.cs delete mode 100644 src/Impostor.Api/Innersloth/FloatRange.cs create mode 100644 src/Impostor.Api/Innersloth/FreeWeekendState.cs create mode 100644 src/Impostor.Api/Innersloth/Language.cs create mode 100644 src/Impostor.Api/Innersloth/MapSpawn.cs rename src/{Impostor.Server => Impostor.Api}/Net/Inner/RpcCalls.cs (96%) create mode 100644 src/Impostor.Api/Net/Messages/Announcements/Message00UseCache.cs create mode 100644 src/Impostor.Api/Net/Messages/Announcements/Message01Update.cs create mode 100644 src/Impostor.Api/Net/Messages/Announcements/Message02SetFreeWeekend.cs create mode 100644 src/Impostor.Api/Net/Messages/Announcements/MessageHello.cs create mode 100644 src/Impostor.Api/Net/Messages/AnnouncementsMessageFlags.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc00PlayAnimation.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc01CompleteTask.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc03SetInfected.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc04Exiled.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc05CheckName.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc07CheckColor.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc09SetHat.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc10SetSkin.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc11ReportDeadBody.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc12MurderPlayer.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc13SendChat.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc14StartMeeting.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc15SetScanner.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc16SendChatNote.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc17SetPet.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc18SetStartCounter.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc19EnterVent.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc20ExitVent.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc21SnapTo.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc22Close.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc23VotingComplete.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc24CastVote.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc25ClearVote.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc26AddVote.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc27CloseDoorsOfType.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc28RepairSystem.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc30UpdateGameData.cs create mode 100644 src/Impostor.Api/Reactor/Mod.cs create mode 100644 src/Impostor.Api/Reactor/ModList.cs create mode 100644 src/Impostor.Api/Reactor/ModdedHandshakeC2S.cs create mode 100644 src/Impostor.Api/Reactor/ModdedHandshakeS2C.cs create mode 100644 src/Impostor.Api/Reactor/PluginSide.cs create mode 100644 src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs create mode 100644 src/Impostor.Server/Config/AnnouncementsServerConfig.cs create mode 100644 src/Impostor.Server/Events/Announcements/AnnouncementRequestEvent.cs delete mode 100644 src/Impostor.Server/Extensions/MessageReaderExtensions.cs create mode 100644 src/Impostor.Server/Extensions/ServiceProviderExtensions.cs create mode 100644 src/Impostor.Server/Net/AnnouncementsService.cs create mode 100644 src/Impostor.Server/Net/Inner/InnerNetObject.Anticheat.cs create mode 100644 src/Impostor.Server/Utils/ServerEnvironment.cs diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index e556fa985..000000000 --- a/.github/stale.yml +++ /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 diff --git a/appveyor.yml b/appveyor.yml index e0c82f688..ce3b999ae 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 diff --git a/build.cake b/build.cake index 7b2869857..e8d9519d9 100644 --- a/build.cake +++ b/build.cake @@ -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\>", 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>", "" + buildVersion + ""); - ReplaceRegexInFiles("./src/**/*.props", @".*?<\/Version>", "" + buildVersion + ""); - }); - 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") diff --git a/src/.editorconfig b/src/.editorconfig index ef7041d32..af97d16c2 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -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 diff --git a/src/.gitignore b/src/.gitignore index e0838bae9..1810abe12 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -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 index 000000000..58fd8ce9a --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,7 @@ + + + 1.2.2 + dev + + + diff --git a/src/Impostor.Api/CheatContext.cs b/src/Impostor.Api/CheatContext.cs new file mode 100644 index 000000000..45a9956e6 --- /dev/null +++ b/src/Impostor.Api/CheatContext.cs @@ -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 index 000000000..b4eaa0152 --- /dev/null +++ b/src/Impostor.Api/Events/Announcements/IAnnouncementRequestEvent.cs @@ -0,0 +1,43 @@ +using Impostor.Api.Innersloth; + +namespace Impostor.Api.Events.Announcements +{ + /// + /// Event fired after client requests a announcement. + /// + public interface IAnnouncementRequestEvent : IEvent + { + public interface IResponse + { + /// + /// Gets or sets FreeWeekendState, currently unused by the client. + /// + public FreeWeekendState FreeWeekendState { get; set; } + + /// + /// Gets or sets a value indicating whether announcement should be loaded from client's cache, can save some bytes. + /// + public bool UseCached { get; set; } + + /// + /// Gets or sets announcement, should be null when is set to true. + /// + public Announcement? Announcement { get; set; } + } + + /// + /// Gets client's last announcement id. + /// + public int Id { get; } + + /// + /// Gets client's language. + /// + public Language Language { get; } + + /// + /// Gets or sets plugin made response. + /// + 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 index 000000000..816684d91 --- /dev/null +++ b/src/Impostor.Api/Events/Game/Player/IPlayerMovementEvent.cs @@ -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 index 8eb72f8b6..000000000 --- a/src/Impostor.Api/Exceptions/ImpostorCheatException.cs +++ /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 diff --git a/src/Impostor.Api/Games/IGame.cs b/src/Impostor.Api/Games/IGame.cs index ad719867e..7c6140c09 100644 --- a/src/Impostor.Api/Games/IGame.cs +++ b/src/Impostor.Api/Games/IGame.cs @@ -4,7 +4,6 @@ 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 @@ public interface IGame IClientPlayer GetClientPlayer(int clientId); + T? FindObjectByNetId(uint netId) + where T : IInnerNetObject; + /// /// Adds an to the ban list of this game. /// Prevents all future joins from this . @@ -53,13 +55,6 @@ public interface IGame /// A representing the asynchronous operation. ValueTask SyncSettingsAsync(); - /// - /// Sets the specified list as Impostor on all connected players. - /// - /// List of players to be Impostor. - /// A representing the asynchronous operation. - ValueTask SetInfectedAsync(IEnumerable players); - /// /// Send the message to all players. /// diff --git a/src/Impostor.Api/Impostor.Api.csproj b/src/Impostor.Api/Impostor.Api.csproj index 8ad258288..188f2a677 100644 --- a/src/Impostor.Api/Impostor.Api.csproj +++ b/src/Impostor.Api/Impostor.Api.csproj @@ -1,12 +1,11 @@ - netstandard2.0 + netstandard2.1 ProjectRules.ruleset 9 true enable - 1.0.0 true snupkg Impostor.Api @@ -22,7 +21,6 @@ - diff --git a/src/Impostor.Api/Innersloth/Announcement.cs b/src/Impostor.Api/Innersloth/Announcement.cs new file mode 100644 index 000000000..fca060191 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Announcement.cs @@ -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; + } + } +} diff --git a/src/Impostor.Api/Innersloth/Customization/HatType.cs b/src/Impostor.Api/Innersloth/Customization/HatType.cs index 5e0a3efe0..c4af44c98 100644 --- a/src/Impostor.Api/Innersloth/Customization/HatType.cs +++ b/src/Impostor.Api/Innersloth/Customization/HatType.cs @@ -1,6 +1,6 @@ namespace Impostor.Api.Innersloth.Customization { - public enum HatType + public enum HatType : uint { NoHat = 0, Astronaut = 1, @@ -45,8 +45,8 @@ public enum HatType ThirdEyeHat = 40, ToiletPaperHat = 41, Toppat = 42, - Fedora = 43, - Goggles2 = 44, + BlackFedora = 43, + SkiGoggles = 44, Headphones = 45, MaskHat = 46, PaperMask = 47, @@ -58,7 +58,7 @@ public enum HatType Cheese = 53, Cherry = 54, Egg = 55, - Fedora2 = 56, + GreenFedora = 56, Flamingo = 57, FlowerPin = 58, Helmet = 59, @@ -95,6 +95,7 @@ public enum HatType MiniCrewmate = 90, NinjaMask = 91, RamHorns = 92, - Snowman2 = 93, + SnowCrewmate = 93, + GeoffHat = 94, } } diff --git a/src/Impostor.Api/Innersloth/Customization/PetType.cs b/src/Impostor.Api/Innersloth/Customization/PetType.cs index 456e3274f..f8753f273 100644 --- a/src/Impostor.Api/Innersloth/Customization/PetType.cs +++ b/src/Impostor.Api/Innersloth/Customization/PetType.cs @@ -1,6 +1,6 @@ namespace Impostor.Api.Innersloth.Customization { - public enum PetType + public enum PetType : uint { NoPet = 0, Alien = 1, diff --git a/src/Impostor.Api/Innersloth/Customization/SkinType.cs b/src/Impostor.Api/Innersloth/Customization/SkinType.cs index 35da31209..9f0aa2297 100644 --- a/src/Impostor.Api/Innersloth/Customization/SkinType.cs +++ b/src/Impostor.Api/Innersloth/Customization/SkinType.cs @@ -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 index c8a0824df..000000000 --- a/src/Impostor.Api/Innersloth/FloatRange.cs +++ /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 index 000000000..2effda854 --- /dev/null +++ b/src/Impostor.Api/Innersloth/FreeWeekendState.cs @@ -0,0 +1,12 @@ +using System; + +namespace Impostor.Api.Innersloth +{ + [Flags] + public enum FreeWeekendState : byte + { + NotFree, + FreeMIRA, + FreePolus, + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptionsData.cs b/src/Impostor.Api/Innersloth/GameOptionsData.cs index d18efd8c5..7f7e0c5ef 100644 --- a/src/Impostor.Api/Innersloth/GameOptionsData.cs +++ b/src/Impostor.Api/Innersloth/GameOptionsData.cs @@ -161,7 +161,7 @@ public static GameOptionsData DeserializeCreate(IMessageReader reader) /// /// The stream to write the message to. /// The version of the game. - 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 @@ public void Serialize(BinaryWriter writer, byte version) } } + public void Serialize(IMessageWriter writer) + { + using var memory = new MemoryStream(); + using var writerBin = new BinaryWriter(memory); + Serialize(writerBin); + writer.WriteBytesAndSize(memory.ToArray()); + } + /// /// Deserialize a ReadOnlyMemory object to this instance of the GameOptionsData object. /// diff --git a/src/Impostor.Api/Innersloth/Language.cs b/src/Impostor.Api/Innersloth/Language.cs new file mode 100644 index 000000000..b17e5ad8c --- /dev/null +++ b/src/Impostor.Api/Innersloth/Language.cs @@ -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 index 000000000..61dea0f6d --- /dev/null +++ b/src/Impostor.Api/Innersloth/MapSpawn.cs @@ -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 Maps { get; } = new Dictionary + { + [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)); + } + } +} diff --git a/src/Impostor.Api/Net/IClient.cs b/src/Impostor.Api/Net/IClient.cs index 48efeda42..1ba594470 100644 --- a/src/Impostor.Api/Net/IClient.cs +++ b/src/Impostor.Api/Net/IClient.cs @@ -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 @@ public interface IClient /// string Name { get; } + /// + /// Gets mods sent by client in modded handshake. + /// + ISet Mods { get; } + /// /// Gets the connection of the client. /// @@ -55,6 +61,8 @@ public interface IClient /// IClientPlayer? Player { get; } + ValueTask ReportCheatAsync(CheatContext context, string message); + ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType); ValueTask HandleDisconnectAsync(string reason); diff --git a/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs b/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs index 6d867e793..2dd470947 100644 --- a/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs +++ b/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs @@ -5,6 +5,16 @@ namespace Impostor.Api.Net.Inner.Objects.Components { public interface IInnerCustomNetworkTransform : IInnerNetObject { + /// + /// Gets position where the object thinks it is (not interpolated). + /// + Vector2 Position { get; } + + /// + /// Gets current object's velocity. + /// + Vector2 Velocity { get; } + /// /// Snaps the current to the given position . /// diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs index 04558b96e..c0ecf855d 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs @@ -41,48 +41,32 @@ public interface IInnerPlayerControl : IInnerNetObject /// Sets the color of the current . /// Visible to all players. /// - /// A color for the player. - /// Task that must be awaited. - ValueTask SetColorAsync(byte colorId); - /// A color for the player. - /// + /// Task that must be awaited. ValueTask SetColorAsync(ColorType colorType); /// /// Sets the hat of the current . /// Visible to all players. /// - /// An hat for the player. - /// Task that must be awaited. - ValueTask SetHatAsync(uint hatId); - /// An hat for the player. - /// + /// Task that must be awaited. ValueTask SetHatAsync(HatType hatType); /// /// Sets the pet of the current . /// Visible to all players. /// - /// A pet for the player. - /// Task that must be awaited. - ValueTask SetPetAsync(uint petId); - /// A pet for the player. - /// + /// Task that must be awaited. ValueTask SetPetAsync(PetType petType); /// /// Sets the skin of the current . /// Visible to all players. /// - /// A skin for the player. - /// Task that must be awaited. - ValueTask SetSkinAsync(uint skinId); - /// A skin for the player. - /// + /// Task that must be awaited. ValueTask SetSkinAsync(SkinType skinType); /// @@ -106,11 +90,13 @@ public interface IInnerPlayerControl : IInnerNetObject ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null); /// - /// Sets the current to be murdered by an impostor . - /// Visible to all players. + /// Murder player. /// - /// /// The Impostor who kill. + /// Target player to murder. + /// Thrown when player is not the impostor. + /// Thrown when player is dead. + /// Thrown when target is dead. /// Task that must be awaited. - ValueTask SetMurderedByAsync(IClientPlayer impostor); + ValueTask MurderPlayerAsync(IInnerPlayerControl target); } } diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs index 6cb33024a..310755ed2 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs @@ -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 @@ public interface IInnerPlayerInfo /// /// Gets the color of the player. /// - byte ColorId { get; } + ColorType Color { get; } /// /// Gets the hat of the player. /// - uint HatId { get; } + HatType Hat { get; } /// /// Gets the pet of the player. /// - uint PetId { get; } + PetType Pet { get; } /// /// Gets the skin of the player. /// - uint SkinId { get; } + SkinType Skin { get; } /// /// Gets a value indicating whether the player is an impostor. diff --git a/src/Impostor.Server/Net/Inner/RpcCalls.cs b/src/Impostor.Api/Net/Inner/RpcCalls.cs similarity index 96% rename from src/Impostor.Server/Net/Inner/RpcCalls.cs rename to src/Impostor.Api/Net/Inner/RpcCalls.cs index ce4896537..f82d2e89e 100644 --- a/src/Impostor.Server/Net/Inner/RpcCalls.cs +++ b/src/Impostor.Api/Net/Inner/RpcCalls.cs @@ -33,5 +33,6 @@ public enum RpcCalls : byte 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 index 000000000..3787ef63c --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Announcements/Message00UseCache.cs @@ -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 index 000000000..b8bc28439 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Announcements/Message01Update.cs @@ -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 index 000000000..054f313e5 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Announcements/Message02SetFreeWeekend.cs @@ -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 index 000000000..c695037ec --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Announcements/MessageHello.cs @@ -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 index 000000000..498d3e412 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/AnnouncementsMessageFlags.cs @@ -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; + } +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs index 4f5b39ce7..f90d7f53f 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs @@ -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 @@ public static class Message00HostGameC2S 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 @@ public static GameOptionsData Deserialize(IMessageReader reader) return GameOptionsData.DeserializeCreate(reader); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/IMessageReader.cs b/src/Impostor.Api/Net/Messages/IMessageReader.cs index 87c06c448..1eb82d1f3 100644 --- a/src/Impostor.Api/Net/Messages/IMessageReader.cs +++ b/src/Impostor.Api/Net/Messages/IMessageReader.cs @@ -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 @@ public interface IMessageReader : IDisposable float ReadSingle(); + string ReadString(int length); + string ReadString(); ReadOnlyMemory ReadBytesAndSize(); @@ -64,5 +69,10 @@ public interface IMessageReader : IDisposable void RemoveMessage(IMessageReader message); IMessageReader Copy(int offset = 0); + + T? ReadNetObject(IGame game) + where T : IInnerNetObject; + + Vector2 ReadVector2(); } } diff --git a/src/Impostor.Api/Net/Messages/IMessageWriter.cs b/src/Impostor.Api/Net/Messages/IMessageWriter.cs index 4f6765bfe..0f4b7fadd 100644 --- a/src/Impostor.Api/Net/Messages/IMessageWriter.cs +++ b/src/Impostor.Api/Net/Messages/IMessageWriter.cs @@ -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 @@ public interface IMessageWriter : IDisposable void WriteBytesAndSize(byte[] bytes, int offset, int length); + void Write(IInnerNetObject innerNetObject); + + void Write(Vector2 vector); + /// /// Starts a new message. /// @@ -124,4 +130,4 @@ public interface IMessageWriter : IDisposable /// New type of the message. 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 index 000000000..f7f91835c --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc00PlayAnimation.cs @@ -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 index 000000000..dfe7ec0d8 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc01CompleteTask.cs @@ -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 index 000000000..047d21faf --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs @@ -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 index 000000000..27a11e9a1 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc03SetInfected.cs @@ -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 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 index 000000000..7dba967aa --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc04Exiled.cs @@ -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 index 000000000..014707513 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc05CheckName.cs @@ -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 index 000000000..e2e8ae488 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs @@ -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 index 000000000..d33875ad1 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc07CheckColor.cs @@ -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 index 000000000..50ea3d695 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs @@ -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 index 000000000..caa9ff752 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc09SetHat.cs @@ -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 index 000000000..e57eeffff --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc10SetSkin.cs @@ -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 index 000000000..a8707a91e --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc11ReportDeadBody.cs @@ -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 index 000000000..82f55f6c7 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc12MurderPlayer.cs @@ -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(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 index 000000000..dcce22930 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc13SendChat.cs @@ -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 index 000000000..8001e43f2 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc14StartMeeting.cs @@ -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 index 000000000..f46adea82 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc15SetScanner.cs @@ -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 index 000000000..4def9d1b0 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc16SendChatNote.cs @@ -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 index 000000000..be67df58c --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc17SetPet.cs @@ -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 index 000000000..cb3c2d1e3 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc18SetStartCounter.cs @@ -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 index 000000000..eca9f1395 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc19EnterVent.cs @@ -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 index 000000000..ddfaf4465 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc20ExitVent.cs @@ -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 index 000000000..576a911dc --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc21SnapTo.cs @@ -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 index 000000000..00caaf396 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc22Close.cs @@ -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 index 000000000..bf9482738 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc23VotingComplete.cs @@ -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 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 index 000000000..1c51bc17e --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc24CastVote.cs @@ -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 index 000000000..5a89331b1 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc25ClearVote.cs @@ -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 index 000000000..f665accbe --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc26AddVote.cs @@ -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 index 000000000..4e3c822de --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc27CloseDoorsOfType.cs @@ -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 index 000000000..411602d7f --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc28RepairSystem.cs @@ -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(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 index 000000000..aba9a7158 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs @@ -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 taskTypeIds) + { + writer.Write(playerId); + writer.Write(taskTypeIds); + } + + public static void Deserialize(IMessageReader reader, out byte playerId, out ReadOnlyMemory 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 index 000000000..4403cf66d --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc30UpdateGameData.cs @@ -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(); + } + } +} diff --git a/src/Impostor.Api/ProjectRules.ruleset b/src/Impostor.Api/ProjectRules.ruleset index 4ba23c2fc..51021f56f 100644 --- a/src/Impostor.Api/ProjectRules.ruleset +++ b/src/Impostor.Api/ProjectRules.ruleset @@ -9,7 +9,8 @@ - + + diff --git a/src/Impostor.Api/Reactor/Mod.cs b/src/Impostor.Api/Reactor/Mod.cs new file mode 100644 index 000000000..2654c5fd3 --- /dev/null +++ b/src/Impostor.Api/Reactor/Mod.cs @@ -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 index 000000000..047c8b49f --- /dev/null +++ b/src/Impostor.Api/Reactor/ModList.cs @@ -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 mods) + { + var length = reader.ReadPackedInt32(); + + mods = new HashSet(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 index 000000000..a79671206 --- /dev/null +++ b/src/Impostor.Api/Reactor/ModdedHandshakeC2S.cs @@ -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? 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 index 000000000..07f4f9c88 --- /dev/null +++ b/src/Impostor.Api/Reactor/ModdedHandshakeS2C.cs @@ -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 index 000000000..30c588c7e --- /dev/null +++ b/src/Impostor.Api/Reactor/PluginSide.cs @@ -0,0 +1,23 @@ +namespace Impostor.Api.Reactor +{ + /// + /// Plugin side used in modded handshake + /// + public enum PluginSide : byte + { + /// + /// Required by both sides, reject connection if missing on the other side + /// + Both, + + /// + /// Required only by client + /// + ClientOnly, + + /// + /// Required only by server + /// + ServerOnly, + } +} diff --git a/src/Impostor.Api/Unity/Mathf.cs b/src/Impostor.Api/Unity/Mathf.cs index 4b034176d..f796decdf 100644 --- a/src/Impostor.Api/Unity/Mathf.cs +++ b/src/Impostor.Api/Unity/Mathf.cs @@ -50,5 +50,10 @@ public static float Clamp01(float value) /// The interpolated float result between the two float values. /// 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 +} diff --git a/src/Impostor.Hazel/Impostor.Hazel.csproj b/src/Impostor.Hazel/Impostor.Hazel.csproj index 3e035fbe9..00997697c 100644 --- a/src/Impostor.Hazel/Impostor.Hazel.csproj +++ b/src/Impostor.Hazel/Impostor.Hazel.csproj @@ -4,7 +4,6 @@ true net5.0 HAZEL_BAG - 1.0.0 diff --git a/src/Impostor.Hazel/MessageReader.cs b/src/Impostor.Hazel/MessageReader.cs index 986d0b024..35933298d 100644 --- a/src/Impostor.Hazel/MessageReader.cs +++ b/src/Impostor.Hazel/MessageReader.cs @@ -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 @@ public unsafe float ReadSingle() 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 ReadBytesAndSize() { var len = ReadPackedInt32(); @@ -239,6 +247,19 @@ public IMessageReader Copy(int offset = 0) return reader; } + public T ReadNetObject(IGame game) where T : IInnerNetObject + { + return game.FindObjectByNetId(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() { diff --git a/src/Impostor.Hazel/MessageWriter.cs b/src/Impostor.Hazel/MessageWriter.cs index 5b7342a31..a7b57e78e 100644 --- a/src/Impostor.Hazel/MessageWriter.cs +++ b/src/Impostor.Hazel/MessageWriter.cs @@ -4,7 +4,10 @@ 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 @@ public void Write(GameCode value) 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) { diff --git a/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs b/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs index a7a4309e9..7f439269d 100644 --- a/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs +++ b/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs @@ -309,9 +309,6 @@ private async ValueTask ProcessReliableReceive(ReadOnlyMemory bytes, //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 @@ private async ValueTask ProcessReliableReceive(ReadOnlyMemory bytes, * So... */ + var result = true; + lock (reliableDataPacketsMissing) { //Calculate overwritePointer @@ -379,12 +378,15 @@ private async ValueTask ProcessReliableReceive(ReadOnlyMemory bytes, //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; } /// diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs index 95f552480..7c1ced263 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs @@ -143,7 +143,10 @@ public async Task SaveIpAsync(string input) return false; } - return WriteIp(ipAddress, port); + var result = WriteIp(ipAddress, port); + OnSaved(ip, port); + + return result; } /// @@ -176,7 +179,6 @@ private bool WriteIp(IPAddress ipAddress, ushort port) region.Serialize(writer); - OnSaved(ip, port); return true; } } diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj b/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj index e480870c8..8d71e56cf 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj @@ -2,7 +2,6 @@ netstandard2.0;netstandard2.1 - 1.0.0 diff --git a/src/Impostor.Plugins.Example/ExamplePluginStartup.cs b/src/Impostor.Plugins.Example/ExamplePluginStartup.cs index 936f15e25..673026daa 100644 --- a/src/Impostor.Plugins.Example/ExamplePluginStartup.cs +++ b/src/Impostor.Plugins.Example/ExamplePluginStartup.cs @@ -17,6 +17,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } } } diff --git a/src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs b/src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs new file mode 100644 index 000000000..42ca3f28d --- /dev/null +++ b/src/Impostor.Plugins.Example/Handlers/AnnouncementsListener.cs @@ -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 index 000000000..5d61ac04c --- /dev/null +++ b/src/Impostor.Server/Config/AnnouncementsServerConfig.cs @@ -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); + } + } +} diff --git a/src/Impostor.Server/Config/AntiCheatConfig.cs b/src/Impostor.Server/Config/AntiCheatConfig.cs index f4807e7f1..ee1f62a62 100644 --- a/src/Impostor.Server/Config/AntiCheatConfig.cs +++ b/src/Impostor.Server/Config/AntiCheatConfig.cs @@ -4,6 +4,8 @@ public class AntiCheatConfig { 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 index 000000000..0f5b55291 --- /dev/null +++ b/src/Impostor.Server/Events/Announcements/AnnouncementRequestEvent.cs @@ -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; + } + } +} diff --git a/src/Impostor.Server/Events/EventManager.cs b/src/Impostor.Server/Events/EventManager.cs index 5625c4dd9..0cc6e541e 100644 --- a/src/Impostor.Server/Events/EventManager.cs +++ b/src/Impostor.Server/Events/EventManager.cs @@ -3,7 +3,6 @@ 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 @@ public async ValueTask CallAsync(T @event) 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); diff --git a/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs index 7b7eb22fe..32c156145 100644 --- a/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs +++ b/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs @@ -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 @@ public PlayerChatEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerContr public IInnerPlayerControl PlayerControl { get; } public string Message { get; } + + public bool IsCancelled { get; set; } } } diff --git a/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs index 330135d16..00e2b2c8f 100644 --- a/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs +++ b/src/Impostor.Server/Events/Game/Player/PlayerCompletedTaskEvent.cs @@ -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; diff --git a/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs index 31ac38861..60dbc1215 100644 --- a/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs +++ b/src/Impostor.Server/Events/Game/Player/PlayerMovementEvent.cs @@ -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 + { + 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 index 5f25e899b..000000000 --- a/src/Impostor.Server/Extensions/MessageReaderExtensions.cs +++ /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(this IMessageReader reader, Game game) - where T : InnerNetObject - { - return game.FindObjectByNetId(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 index 000000000..eee14806d --- /dev/null +++ b/src/Impostor.Server/Extensions/ServiceProviderExtensions.cs @@ -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(new DefaultObjectPoolProvider()); + + services.AddSingleton(serviceProvider => + { + var provider = serviceProvider.GetRequiredService(); + var policy = ActivatorUtilities.CreateInstance(serviceProvider); + return provider.Create(policy); + }); + } + } +} diff --git a/src/Impostor.Server/Impostor.Server.csproj b/src/Impostor.Server/Impostor.Server.csproj index ad5a6db05..663f7af43 100644 --- a/src/Impostor.Server/Impostor.Server.csproj +++ b/src/Impostor.Server/Impostor.Server.csproj @@ -16,7 +16,6 @@ Impostor.Server Impostor.Server Copyright © AeonLucid 2020 - 1.0.0 @@ -35,6 +34,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -55,4 +56,4 @@ - \ No newline at end of file + diff --git a/src/Impostor.Server/Net/AnnouncementsService.cs b/src/Impostor.Server/Net/AnnouncementsService.cs new file mode 100644 index 000000000..d205045e7 --- /dev/null +++ b/src/Impostor.Server/Net/AnnouncementsService.cs @@ -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 _readerPool; + private readonly IEventManager _eventManager; + private UdpConnectionListener _connection; + + public AnnouncementsService(IOptions config, ObjectPool 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); + } + } +} diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index 87a1bb40e..143a0d103 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -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.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 @@ internal class Client : ClientBase private readonly ClientManager _clientManager; private readonly GameManager _gameManager; - public Client(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection) - : base(name, connection) + public Client(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection, ISet mods) + : base(name, connection, mods) { _logger = logger; _antiCheatConfig = antiCheatOptions.Value; @@ -31,6 +33,25 @@ public Client(ILogger logger, IOptions antiCheatOptions _gameManager = gameManager; } + public override async ValueTask 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 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag // 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 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag Message08EndGameC2S.Deserialize( reader, out var gameOverReason); - + await Player.Game.HandleEndGame(reader, gameOverReason); break; } diff --git a/src/Impostor.Server/Net/ClientBase.cs b/src/Impostor.Server/Net/ClientBase.cs index 527919256..bae1fb33f 100644 --- a/src/Impostor.Server/Net/ClientBase.cs +++ b/src/Impostor.Server/Net/ClientBase.cs @@ -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 mods) { Name = name; Connection = connection; + Mods = mods; Items = new ConcurrentDictionary(); + + ModIdMap = new Dictionary(); + + 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 Mods { get; } + + public Dictionary ModIdMap { get; } + public IHazelConnection Connection { get; } public IDictionary Items { get; } @@ -32,6 +52,11 @@ protected ClientBase(string name, IHazelConnection connection) IClientPlayer? IClient.Player => Player; + public virtual ValueTask ReportCheatAsync(CheatContext context, string message) + { + return new ValueTask(false); + } + public abstract ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType); public abstract ValueTask HandleDisconnectAsync(string reason); diff --git a/src/Impostor.Server/Net/Factories/ClientFactory.cs b/src/Impostor.Server/Net/Factories/ClientFactory.cs index ad1fdc778..ab6eedf61 100644 --- a/src/Impostor.Server/Net/Factories/ClientFactory.cs +++ b/src/Impostor.Server/Net/Factories/ClientFactory.cs @@ -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 @@ public ClientFactory(IServiceProvider serviceProvider) _serviceProvider = serviceProvider; } - public ClientBase Create(IHazelConnection connection, string name, int clientVersion) + public ClientBase Create(IHazelConnection connection, string name, int clientVersion, ISet mods) { - var client = ActivatorUtilities.CreateInstance(_serviceProvider, name, connection); + var client = ActivatorUtilities.CreateInstance(_serviceProvider, name, connection, mods); connection.Client = client; return client; } diff --git a/src/Impostor.Server/Net/Factories/IClientFactory.cs b/src/Impostor.Server/Net/Factories/IClientFactory.cs index 6859ae3c9..bec1b79ba 100644 --- a/src/Impostor.Server/Net/Factories/IClientFactory.cs +++ b/src/Impostor.Server/Net/Factories/IClientFactory.cs @@ -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 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 index 000000000..28b3457e4 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/InnerNetObject.Anticheat.cs @@ -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 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 ValidateHost(CheatContext context, IClientPlayer sender) + { + if (!sender.IsHost) + { + if (await sender.Client.ReportCheatAsync(context, "Failed host check")) + { + return false; + } + } + + return true; + } + + protected async ValueTask 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 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 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 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 UnregisteredCall(CheatContext context, IClientPlayer sender) + { + if (await sender.Client.ReportCheatAsync(context, "Client sent unregistered call")) + { + return false; + } + + return true; + } + } +} diff --git a/src/Impostor.Server/Net/Inner/InnerNetObject.cs b/src/Impostor.Server/Net/Inner/InnerNetObject.cs index 78f1a55cd..9579fe0ad 100644 --- a/src/Impostor.Server/Net/Inner/InnerNetObject.cs +++ b/src/Impostor.Server/Net/Inner/InnerNetObject.cs @@ -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 @@ 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 @@ internal abstract class InnerNetObject : GameObject, IInnerNetObject 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 SerializeAsync(IMessageWriter writer, bool initialState); + + public abstract ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState); + + public abstract ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader); + + protected ValueTask 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 +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs index 6cdd6b91f..56ff96e59 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs @@ -11,12 +11,12 @@ public async ValueTask SnapToAsync(Vector2 position) 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); } diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs index d261c11e0..80dd6adf6 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs @@ -1,125 +1,84 @@ -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 _logger; private readonly InnerPlayerControl _playerControl; private readonly Game _game; + private readonly IEventManager _eventManager; + private readonly ObjectPool _pool; private ushort _lastSequenceId; - private Vector2 _targetSyncPosition; - private Vector2 _targetSyncVelocity; - public InnerCustomNetworkTransform(ILogger logger, InnerPlayerControl playerControl, Game game) + public Vector2 Position { get; private set; } + + public Vector2 Velocity { get; private set; } + + public InnerCustomNetworkTransform(ILogger logger, InnerPlayerControl playerControl, Game game, IEventManager eventManager, ObjectPool 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 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(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(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 @@ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IM } _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 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); } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs index 29bc9962d..69f8dcd81 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs @@ -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 @@ public InnerPlayerPhysics(ILogger logger, InnerPlayerControl _game = game; } - public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + public override ValueTask 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 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; } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs index 58b9b54a2..117f78e6d 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs @@ -5,6 +5,7 @@ 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 @@ public InnerVoteBanSystem(ILogger logger) _votes = new Dictionary(); } - 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 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 @@ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IM } } } + + public override async ValueTask 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 +} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs index ed6803888..0b2dbf4a5 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs @@ -3,10 +3,10 @@ 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 @@ public InnerGameData(ILogger logger, Game game, IServiceProvider return _allPlayers.TryGetValue(id, out var player) ? player : null; } - public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + public override ValueTask 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 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 @@ public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, R 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) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs b/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs index 63448edd2..78be5bd23 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs @@ -18,19 +18,19 @@ public InnerLobbyBehaviour(IGame game) Components.Add(this); } - public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + public override ValueTask 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 HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) { throw new System.NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs index c2bcb9d9d..96d829fb8 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs @@ -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.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 @@ private void PopulateButtons(byte reporter) .ToArray(); } - public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + public override ValueTask 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 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 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 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; } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs index 0a7997d5c..07c21267c 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -2,9 +2,9 @@ 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 @@ public async ValueTask SetNameAsync(string name) 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 @@ public async ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? p 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)); } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 1fe1dc833..c2759139a 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -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.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 @@ internal partial class InnerPlayerControl : InnerNetObject private readonly ILogger _logger; private readonly IEventManager _eventManager; private readonly Game _game; + private readonly ServerEnvironment _serverEnvironment; - public InnerPlayerControl(ILogger logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game) + public InnerPlayerControl(ILogger logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game, ServerEnvironment serverEnvironment) { _logger = logger; _eventManager = eventManager; _game = game; + _serverEnvironment = serverEnvironment; Physics = ActivatorUtilities.CreateInstance(serviceProvider, this, _eventManager, _game); NetworkTransform = ActivatorUtilities.CreateInstance(serviceProvider, this, _game); @@ -47,521 +53,543 @@ public InnerPlayerControl(ILogger logger, IServiceProvider s public InnerPlayerInfo PlayerInfo { get; internal set; } - public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + internal Queue RequestedPlayerName { get; } = new Queue(); + + internal Queue RequestedColorId { get; } = new Queue(); + + public override ValueTask 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 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().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().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 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 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 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(_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().Length; - case RpcCalls.StartMeeting: + private async ValueTask 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 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 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 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 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 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 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 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; } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index f1409f938..410b4af61 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -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 @@ public InnerPlayerInfo(byte playerId) 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 @@ public void Serialize(IMessageWriter writer) 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; diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs index b1a3f18f9..3725c8faa 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs @@ -6,6 +6,7 @@ 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 @@ public InnerShipStatus(ILogger logger, Game game) _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(_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 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 @@ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IM } } } + + public override async ValueTask 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/Manager/ClientManager.cs b/src/Impostor.Server/Net/Manager/ClientManager.cs index 51e22d81d..cc7e46cd4 100644 --- a/src/Impostor.Server/Net/Manager/ClientManager.cs +++ b/src/Impostor.Server/Net/Manager/ClientManager.cs @@ -7,9 +7,11 @@ 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 @@ internal partial class ClientManager GameVersion.GetVersion(2020, 11, 17), // 2020.11.17 }; + private static string ServerBrand { get; } = $"Impostor {DotnetUtils.GetVersion()}"; + private readonly ILogger _logger; private readonly ConcurrentDictionary _clients; private readonly IClientFactory _clientFactory; @@ -53,7 +57,7 @@ public int NextId() return clientId; } - public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion) + public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion, ISet? mods) { if (!SupportedVersions.Contains(clientVersion)) { @@ -79,12 +83,16 @@ public async ValueTask RegisterConnectionAsync(IHazelConnection connection, stri return; } - var client = _clientFactory.Create(connection, name, clientVersion); + var client = _clientFactory.Create(connection, name, clientVersion, mods ?? new HashSet(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) diff --git a/src/Impostor.Server/Net/Matchmaker.cs b/src/Impostor.Server/Net/Matchmaker.cs index 64ece55f0..e700703ca 100644 --- a/src/Impostor.Server/Net/Matchmaker.cs +++ b/src/Impostor.Server/Net/Matchmaker.cs @@ -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 @@ public async ValueTask StopAsync() 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); } } } diff --git a/src/Impostor.Server/Net/Redirector/ClientRedirector.cs b/src/Impostor.Server/Net/Redirector/ClientRedirector.cs index 3dad4b34d..bda07fda2 100644 --- a/src/Impostor.Server/Net/Redirector/ClientRedirector.cs +++ b/src/Impostor.Server/Net/Redirector/ClientRedirector.cs @@ -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 @@ internal class ClientRedirector : ClientBase public ClientRedirector( string name, HazelConnection connection, + ISet mods, ClientManager clientManager, INodeProvider nodeProvider, INodeLocator nodeLocator) - : base(name, connection) + : base(name, connection, mods) { _clientManager = clientManager; _nodeProvider = nodeProvider; diff --git a/src/Impostor.Server/Net/State/Game.Api.cs b/src/Impostor.Server/Net/State/Game.Api.cs index f395be292..1a623be96 100644 --- a/src/Impostor.Server/Net/State/Game.Api.cs +++ b/src/Impostor.Server/Net/State/Game.Api.cs @@ -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.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 @@ await using (var writerBin = new BinaryWriter(memory)) await FinishRpcAsync(writer); } } - - public async ValueTask SetInfectedAsync(IEnumerable 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); - } - } } } diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index a84d9b59c..bb386d6fc 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -4,6 +4,7 @@ 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 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl 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 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl 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 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl // 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 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl 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 @@ private void RemoveNetObject(InnerNetObject obj) obj.NetId = uint.MaxValue; } - public T FindObjectByNetId(uint netId) - where T : InnerNetObject + public T? FindObjectByNetId(uint netId) + where T : IInnerNetObject { if (_allObjectsFast.TryGetValue(netId, out var obj)) { - return (T) obj; + return (T)(IInnerNetObject)obj; } - return null; + return default; } } } diff --git a/src/Impostor.Server/Net/State/Game.Incoming.cs b/src/Impostor.Server/Net/State/Game.Incoming.cs index 4bf1c43c0..547224db2 100644 --- a/src/Impostor.Server/Net/State/Game.Incoming.cs +++ b/src/Impostor.Server/Net/State/Game.Incoming.cs @@ -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 @@ private async ValueTask AddClientSafeAsync(ClientBase client) 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) diff --git a/src/Impostor.Server/Net/State/Game.State.cs b/src/Impostor.Server/Net/State/Game.State.cs index e31177607..e24e798a9 100644 --- a/src/Impostor.Server/Net/State/Game.State.cs +++ b/src/Impostor.Server/Net/State/Game.State.cs @@ -102,6 +102,12 @@ private async ValueTask MigrateHost() 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 @@ private async ValueTask CheckLimboPlayers() } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/State/Game.cs b/src/Impostor.Server/Net/State/Game.cs index 19198675e..a1aeb48a6 100644 --- a/src/Impostor.Server/Net/State/Game.cs +++ b/src/Impostor.Server/Net/State/Game.cs @@ -3,6 +3,7 @@ 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 @@ internal partial class Game public int PlayerCount => _players.Count; - public ClientPlayer Host => _players[HostId]; + public ClientPlayer? Host => _players.GetValueOrDefault(HostId); public IEnumerable Players => _players.Select(p => p.Value); @@ -92,16 +93,20 @@ public IClientPlayer GetClientPlayer(int clientId) 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() diff --git a/src/Impostor.Server/Plugins/PluginLoader.cs b/src/Impostor.Server/Plugins/PluginLoader.cs index 4e728868f..f753bd135 100644 --- a/src/Impostor.Server/Plugins/PluginLoader.cs +++ b/src/Impostor.Server/Plugins/PluginLoader.cs @@ -5,7 +5,6 @@ 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; diff --git a/src/Impostor.Server/Program.cs b/src/Impostor.Server/Program.cs index d11694f71..2af8d5504 100644 --- a/src/Impostor.Server/Program.cs +++ b/src/Impostor.Server/Program.cs @@ -112,9 +112,16 @@ private static IHostBuilder CreateHostBuilder(string[] args) .GetSection(ServerRedirectorConfig.Section) .Get() ?? new ServerRedirectorConfig(); + var announcementsServer = host.Configuration + .GetSection(AnnouncementsServerConfig.Section) + .Get() ?? new AnnouncementsServerConfig(); + + services.AddSingleton(); + services.Configure(host.Configuration.GetSection(DebugConfig.Section)); services.Configure(host.Configuration.GetSection(AntiCheatConfig.Section)); services.Configure(host.Configuration.GetSection(ServerConfig.Section)); + services.Configure(host.Configuration.GetSection(AnnouncementsServerConfig.Section)); services.Configure(host.Configuration.GetSection(ServerRedirectorConfig.Section)); if (redirector.Enabled) @@ -191,12 +198,18 @@ private static IHostBuilder CreateHostBuilder(string[] args) services.AddSingleton(p => p.GetRequiredService()); } + services.AddEventPools(); services.AddHazel(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddHostedService(); + + if (announcementsServer.Enabled) + { + services.AddHostedService(); + } }) .UseSerilog() .UseConsoleLifetime() diff --git a/src/Impostor.Server/ProjectRules.ruleset b/src/Impostor.Server/ProjectRules.ruleset index 3654bc34c..fd6daac02 100644 --- a/src/Impostor.Server/ProjectRules.ruleset +++ b/src/Impostor.Server/ProjectRules.ruleset @@ -10,6 +10,7 @@ + diff --git a/src/Impostor.Server/Recorder/ClientRecorder.cs b/src/Impostor.Server/Recorder/ClientRecorder.cs index 5763c70b1..868aa0a4c 100644 --- a/src/Impostor.Server/Recorder/ClientRecorder.cs +++ b/src/Impostor.Server/Recorder/ClientRecorder.cs @@ -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 @@ internal class ClientRecorder : Client private bool _createdGame; private bool _recordAfter; - public ClientRecorder(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, PacketRecorder recorder) - : base(logger, antiCheatOptions, clientManager, gameManager, name, connection) + public ClientRecorder(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, ISet 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 index 000000000..9c9836aa1 --- /dev/null +++ b/src/Impostor.Server/Utils/ServerEnvironment.cs @@ -0,0 +1,7 @@ +namespace Impostor.Server.Utils +{ + public class ServerEnvironment + { + public bool IsReplay { get; init; } + } +} diff --git a/src/Impostor.Server/config-full.json b/src/Impostor.Server/config-full.json index dfee1bcbf..edf7716ca 100644 --- a/src/Impostor.Server/config-full.json +++ b/src/Impostor.Server/config-full.json @@ -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 +} diff --git a/src/Impostor.Tools.ServerReplay/Program.cs b/src/Impostor.Tools.ServerReplay/Program.cs index 5aaa954cd..1f5e49452 100644 --- a/src/Impostor.Tools.ServerReplay/Program.cs +++ b/src/Impostor.Tools.ServerReplay/Program.cs @@ -13,12 +13,14 @@ 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 @@ private static ServiceProvider BuildServices() { var services = new ServiceCollection(); + services.AddSingleton(new ServerEnvironment + { + IsReplay = true + }); + services.AddLogging(builder => { builder.ClearProviders(); @@ -99,6 +106,7 @@ private static ServiceProvider BuildServices() services.AddSingleton(); services.AddSingleton(); + services.AddEventPools(); services.AddHazel(); return services.BuildServiceProvider(); @@ -121,7 +129,7 @@ await using (var stream = new MemoryStream(data)) 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 @@ private static async Task ParsePacket(BinaryReader reader) // 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);