diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 727dfd760..6cf141eb6 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "0.38.5", + "version": "1.1.0", "commands": [ "dotnet-cake" ] 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/.github/workflows/docker.yml b/.github/workflows/docker.yml index b31207599..51b61260a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,7 +2,7 @@ name: Docker on: push: - branches: dev + branches: master paths: - 'src/Impostor.Server/**' - 'src/Impostor.Shared/**' @@ -33,15 +33,16 @@ jobs: run: | DOCKER_IMAGE=aeonlucid/impostor VERSION=noop + VERSIONSUFFIX=docker if [[ $GITHUB_REF == refs/tags/* ]]; then VERSION=${GITHUB_REF#refs/tags/} + VERSIONSUFFIX=none elif [[ $GITHUB_REF == refs/heads/* ]]; then - VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g') - if [ "${{ github.event.repository.default_branch }}" = "dev" ]; then - VERSION=nightly - fi + VERSION=nightly + VERSIONSUFFIX=docker.${{ github.run_number }} elif [[ $GITHUB_REF == refs/pull/* ]]; then VERSION=pr-${{ github.event.number }} + VERSIONSUFFIX=pr.${{ github.event.number }} fi TAGS="${DOCKER_IMAGE}:${VERSION}" if [[ $VERSION =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then @@ -49,6 +50,7 @@ jobs: fi echo ::set-output name=version::${VERSION} echo ::set-output name=tags::${TAGS} + echo ::set-output name=versionsuffix::${VERSIONSUFFIX} echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') - name: Set up QEMU uses: docker/setup-qemu-action@v1 @@ -68,3 +70,5 @@ jobs: platforms: linux/amd64,linux/arm/v7,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.prep.outputs.tags }} + build-args: | + VERSIONSUFFIX=${{ steps.prep.outputs.versionsuffix }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..598f78b6a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/Impostor.Hazel"] + path = src/Impostor.Hazel + url = https://github.com/Impostor/Impostor.Hazel diff --git a/Dockerfile b/Dockerfile index 8f386da60..ae6b0706e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,14 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:5.0 AS build # https://github.com/containerd/containerd/blob/master/platforms/platforms.go#L17 ARG TARGETARCH +ARG VERSIONSUFFIX="docker" + WORKDIR /source # Copy csproj and restore. COPY src/Impostor.Server/Impostor.Server.csproj ./src/Impostor.Server/Impostor.Server.csproj COPY src/Impostor.Api/Impostor.Api.csproj ./src/Impostor.Api/Impostor.Api.csproj -COPY src/Impostor.Hazel/Impostor.Hazel.csproj ./src/Impostor.Hazel/Impostor.Hazel.csproj +COPY src/Impostor.Hazel/Hazel/Hazel.csproj ./src/Impostor.Hazel/Hazel/Hazel.csproj RUN case "$TARGETARCH" in \ amd64) NETCORE_PLATFORM='linux-x64';; \ @@ -19,7 +21,7 @@ RUN case "$TARGETARCH" in \ esac && \ dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Server/Impostor.Server.csproj && \ dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Api/Impostor.Api.csproj && \ - dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Hazel/Impostor.Hazel.csproj + dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Hazel/Hazel/Hazel.csproj # Copy everything else. COPY src/. ./src/ @@ -29,7 +31,8 @@ RUN case "$TARGETARCH" in \ arm) NETCORE_PLATFORM='linux-arm';; \ *) echo "unsupported architecture"; exit 1 ;; \ esac && \ - dotnet publish -c release -o /app -r "$NETCORE_PLATFORM" --no-restore ./src/Impostor.Server/Impostor.Server.csproj + [[ $VERSIONSUFFIX = "none" ]] && VERSIONSUFFIX=; \ + dotnet publish -c release -o /app -r "$NETCORE_PLATFORM" -p:VersionSuffix="$VERSIONSUFFIX" --no-restore ./src/Impostor.Server/Impostor.Server.csproj # Final image. FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/runtime:5.0 diff --git a/README.md b/README.md index 1ce3f9494..878ff9bef 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Impostor [![Discord](https://img.shields.io/badge/Discord-chat-blue?style=flat-square)](https://discord.gg/Mk3w6Tb) -[![AppVeyor](https://img.shields.io/appveyor/build/Impostor/Impostor/dev?style=flat-square)](https://ci.appveyor.com/project/Impostor/Impostor/branch/dev) +[![AppVeyor](https://img.shields.io/appveyor/build/Impostor/Impostor/master?style=flat-square)](https://ci.appveyor.com/project/Impostor/Impostor/branch/master) Impostor is one of the first **Among Us** private servers, written in C#. -We support Steam, Itch, Android and iOS. The latest version supported is `2020.9.22`, the `dev` build currently supports `2020.11.17`. - | Impostor version | Among Us version | Experimental | Download | |-|-|-|-| | 1.1.0 | 2020.09.07 - 2020.09.22 | No | [![Download](https://img.shields.io/badge/Download-v1.1.0-blue?style=flat-square)](https://github.com/Impostor/Impostor/releases/tag/v1.1.0) | -| 1.2.2 | 2020.09.22 - 2020.11.17 | Yes | [![Download](https://img.shields.io/badge/Download-v1.2.2-blue?style=flat-square)](https://ci.appveyor.com/project/Impostor/Impostor/branch/dev/artifacts) | +| 1.2.2 | 2020.09.22 - 2020.11.17 | No | [![Download](https://img.shields.io/badge/Download-v1.2.2-blue?style=flat-square)](https://github.com/Impostor/Impostor/releases/tag/v1.2.2) | +| 1.3.0 | 2021.3.5 | No | [![Download](https://img.shields.io/badge/Download-v1.3.0-blue?style=flat-square)](https://github.com/Impostor/Impostor/releases/tag/v1.3.0) | +| 1.4.0 | 2021.3.31 - 2021.4.2 | Yes | [![Download](https://img.shields.io/badge/Download-master-blue?style=flat-square)](https://ci.appveyor.com/project/Impostor/Impostor/branch/master/artifacts) | ## Features @@ -27,9 +27,9 @@ If you just want to play on a server hosted by someone else, you need to follow #### Windows 1. Find the [latest release](https://github.com/AeonLucid/Impostor/releases/latest). -2. Download `Impostor-Client-win-x64.zip`. +2. Download `Impostor-Patcher-win-x64.zip`. 3. Extract the zip. -4. Run `Impostor.Client.exe`. +4. Run `Impostor.Patcher.exe`. 5. Follow the instructions inside the application. ![Client](docs/images/client.jpg) 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..d274c0bde 100644 --- a/build.cake +++ b/build.cake @@ -1,20 +1,25 @@ -#addin "nuget:?package=SharpZipLib&Version=1.3.0" -#addin "nuget:?package=Cake.Compression&Version=0.2.4" -#addin "nuget:?package=Cake.FileHelpers&Version=3.3.0" +#addin "nuget:?package=SharpZipLib&Version=1.3.1" +#addin "nuget:?package=Cake.Compression&Version=0.2.6" +#addin "nuget:?package=Cake.FileHelpers&Version=4.0.1" - -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) +{ + msbuildSettings.Properties["Version"] = new[] { buildVersion }; +} +else if (buildId != null) +{ + msbuildSettings.Properties["VersionSuffix"] = new[] { "ci." + buildId }; + buildVersion += "-ci." + buildId; } ////////////////////////////////////////////////////////////////////// @@ -34,7 +39,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) { @@ -62,7 +68,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 +93,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 +107,6 @@ Task("Replay") Task("Build") .IsDependentOn("Clean") - .IsDependentOn("Patch") .IsDependentOn("Restore") .IsDependentOn("Replay") .Does(() => { @@ -116,44 +115,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/docs/Building-from-source.md b/docs/Building-from-source.md index cf3632f5f..2254f16ff 100644 --- a/docs/Building-from-source.md +++ b/docs/Building-from-source.md @@ -1,29 +1,20 @@ # Building from source -The solution contains two main projects, the Impostor client and server. The client is built using [.NET Framework 4.7.2](https://dotnet.microsoft.com/download/dotnet-framework/net472) and the server with [.NET 5](https://dotnet.microsoft.com/download/dotnet/5.0). - -Currently .NET 5 is not yet officially released, so in order to build using Visual Studio, you should have Visual Studio 2019 **Preview** installed. -This documentation will go over building both the [Server](#building-the-server) and the [Client](#building-the-client) and their requirements. +The solution contains two main projects, the Impostor server and patcher. The server is built using [.NET 5](https://dotnet.microsoft.com/download/dotnet/5.0) and the winforms patcher is with [.NET Framework 4.7.2](https://dotnet.microsoft.com/download/dotnet-framework/net472). ## Cloning Impostor -You need to clone Impostor with all submodules. - -```bash -git clone --recursive https://github.com/AeonLucid/Impostor.git -``` - -If you already have cloned Impostor but have errors related to Hazel, run the following. +You need to clone Impostor using git. ```bash -git submodule update --init +git clone https://github.com/AeonLucid/Impostor.git ``` ## Building the server ### Dependencies - [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) -- [Visual Studio Preview](https://visualstudio.microsoft.com/vs/preview/) (Optional, only if you want the full IDE experience) +- [Rider](https://www.jetbrains.com/rider/) or [Visual Studio](https://visualstudio.microsoft.com/vs/) (Optional, only if you want the full IDE experience) ### Build using the CLI @@ -33,13 +24,13 @@ dotnet build ``` To setup the server, please look at [Running the server](Running-the-server.md). -## Building the client +## Building the winforms patcher ### Dependencies -* [.NET Framework 4.7.2 Developer Pack](https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer) +* [.NET Framework 4.7.2 Developer Pack](https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer) or [Mono](https://www.mono-project.com/download/) ### Build using the CLI ```bash -cd src/Impostor.Client/Impostor.Client.WinForms +cd src/Impostor.Patcher/Impostor.Patcher.WinForms dotnet build -``` \ No newline at end of file +``` diff --git a/docs/Server-configuration.md b/docs/Server-configuration.md index 5841ca7f9..75996a335 100644 --- a/docs/Server-configuration.md +++ b/docs/Server-configuration.md @@ -17,6 +17,7 @@ Some information about all the possible configurations. Click [here](https://git | Key | Default | Value | |-|-|-| +| **Enabled** | `true` | Whether the anticheat should be enabled. | | **BanIpFromGame** | `true` | When a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. **(Setting this to false does not disable the anti-cheat!)** | ### ServerRedirector @@ -45,6 +46,7 @@ Server:PublicIp=127.0.0.1 Server:PublicPort=22023 Server:ListenIp=0.0.0.0 Server:ListenPort=22023 +AntiCheat:Enabled=true AntiCheat:BanIpFromGame=true ServerRedirector:Enabled=false ServerRedirector:Master=true @@ -65,6 +67,7 @@ IMPOSTOR_Server__PublicIp=127.0.0.1 IMPOSTOR_Server__PublicPort=22023 IMPOSTOR_Server__ListenIp=0.0.0.0 IMPOSTOR_Server__ListenPort=22023 +IMPOSTOR_AntiCheat__Enabled=true IMPOSTOR_AntiCheat__BanIpFromGame=true IMPOSTOR_ServerRedirector__Enabled=false IMPOSTOR_ServerRedirector__Master=true diff --git a/src/.editorconfig b/src/.editorconfig index ef7041d32..88f363eae 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 @@ -163,7 +163,7 @@ csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true +# csharp_new_line_before_members_in_object_initializers = true TODO seems like Rider/ReSharper has the value inverted, uncomment when its fixed csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true @@ -171,7 +171,7 @@ csharp_new_line_between_query_expression_clauses = true csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true +csharp_indent_case_contents_when_block = false csharp_indent_switch_labels = true csharp_indent_labels = flush_left @@ -228,3 +228,6 @@ csharp_preserve_single_line_statements = true # warning RS0037: PublicAPI.txt is missing '#nullable enable' dotnet_diagnostic.RS0037.severity = none + +# ReSharper properties +resharper_trailing_comma_in_multiline_lists = true 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..ffebcb926 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,11 @@ + + + 1.4.0 + dev + + + + + + + diff --git a/src/Impostor.Api/CheatContext.cs b/src/Impostor.Api/CheatContext.cs new file mode 100644 index 000000000..7508a7877 --- /dev/null +++ b/src/Impostor.Api/CheatContext.cs @@ -0,0 +1,20 @@ +using Impostor.Api.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..a0e54452d --- /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/Attributes/EventListenerAttribute.cs b/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs index b31d2d1a7..da1e48960 100644 --- a/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs +++ b/src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs @@ -17,18 +17,18 @@ public EventListenerAttribute(Type @event, EventPriority priority = EventPriorit } /// - /// The priority of the event listener. + /// Gets or sets the priority of the event listener. /// public EventPriority Priority { get; set; } /// - /// The events that the listener is listening to. + /// Gets or sets the event that the listener is listening to. /// public Type? Event { get; set; } /// - /// If set to true, the listener will be called regardless of the . + /// Gets or sets a value indicating whether the listener will be called regardless of the . /// public bool IgnoreCancelled { get; set; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs b/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs index ef45f1dbe..48c8d7cca 100644 --- a/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs +++ b/src/Impostor.Api/Events/Game/IGameCreatedEvent.cs @@ -3,7 +3,7 @@ namespace Impostor.Api.Events { /// - /// Called whenever a new is created. + /// Called whenever a new is created. /// public interface IGameCreatedEvent : IGameEvent { diff --git a/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs b/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs index 7ff7b460d..e598aaea4 100644 --- a/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs +++ b/src/Impostor.Api/Events/Game/IGameDestroyedEvent.cs @@ -3,7 +3,7 @@ namespace Impostor.Api.Events { /// - /// Called whenever a new is destroyed. + /// Called whenever a new is destroyed. /// public interface IGameDestroyedEvent : IGameEvent { diff --git a/src/Impostor.Api/Events/Game/IGameEvent.cs b/src/Impostor.Api/Events/Game/IGameEvent.cs index c9ae5790d..c188af3fe 100644 --- a/src/Impostor.Api/Events/Game/IGameEvent.cs +++ b/src/Impostor.Api/Events/Game/IGameEvent.cs @@ -5,7 +5,7 @@ namespace Impostor.Api.Events public interface IGameEvent : IEvent { /// - /// Gets the this event belongs to. + /// Gets the this event belongs to. /// IGame Game { get; } } diff --git a/src/Impostor.Api/Events/Game/IGameHostChangedEvent.cs b/src/Impostor.Api/Events/Game/IGameHostChangedEvent.cs new file mode 100644 index 000000000..44709783a --- /dev/null +++ b/src/Impostor.Api/Events/Game/IGameHostChangedEvent.cs @@ -0,0 +1,11 @@ +using Impostor.Api.Net; + +namespace Impostor.Api.Events +{ + public interface IGameHostChangedEvent : IGameEvent + { + IClientPlayer PreviousHost { get; } + + IClientPlayer? NewHost { get; } + } +} diff --git a/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs b/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs index 921568e5f..280d397d9 100644 --- a/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs +++ b/src/Impostor.Api/Events/Game/IGamePlayerJoinedEvent.cs @@ -1,6 +1,9 @@ -namespace Impostor.Api.Events +using Impostor.Api.Net; + +namespace Impostor.Api.Events { public interface IGamePlayerJoinedEvent : IGameEvent { + IClientPlayer Player { get; } } } diff --git a/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs b/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs index 21d8b7c22..4568a602c 100644 --- a/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs +++ b/src/Impostor.Api/Events/Game/IGamePlayerLeftEvent.cs @@ -1,6 +1,11 @@ -namespace Impostor.Api.Events +using Impostor.Api.Net; + +namespace Impostor.Api.Events { public interface IGamePlayerLeftEvent : IGameEvent { + IClientPlayer Player { get; } + + bool IsBan { get; } } } diff --git a/src/Impostor.Api/Events/Game/IGameStartingEvent.cs b/src/Impostor.Api/Events/Game/IGameStartingEvent.cs index 5998bf249..3ba450ea5 100644 --- a/src/Impostor.Api/Events/Game/IGameStartingEvent.cs +++ b/src/Impostor.Api/Events/Game/IGameStartingEvent.cs @@ -3,7 +3,7 @@ /// /// Called when the game is going to start. /// When this is called, not all players are initialized properly yet. - /// If you want to get correct player states, use . + /// If you want to get correct player states, use . /// public interface IGameStartingEvent : IGameEvent { diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs index 52efe9653..914701025 100644 --- a/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs +++ b/src/Impostor.Api/Events/Game/Player/IPlayerChatEvent.cs @@ -1,6 +1,6 @@ namespace Impostor.Api.Events.Player { - public interface IPlayerChatEvent : IPlayerEvent + public interface IPlayerChatEvent : IPlayerEvent, IEventCancelable { /// /// Gets the message sent by the player. diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs index 78ccd2d6a..a5e322855 100644 --- a/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs +++ b/src/Impostor.Api/Events/Game/Player/IPlayerCompletedTaskEvent.cs @@ -1,5 +1,4 @@ -using Impostor.Api.Innersloth; -using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Inner.Objects; namespace Impostor.Api.Events.Player { diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerEnterVentEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerEnterVentEvent.cs new file mode 100644 index 000000000..fac832b7d --- /dev/null +++ b/src/Impostor.Api/Events/Game/Player/IPlayerEnterVentEvent.cs @@ -0,0 +1,15 @@ +using Impostor.Api.Innersloth; + +namespace Impostor.Api.Events.Player +{ + /// + /// Called whenever a player enters a vent. + /// + public interface IPlayerEnterVentEvent : IPlayerEvent + { + /// + /// Gets the entered vent. + /// + public IVent Vent { get; } + } +} diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs index 247fe645b..d8269ffbb 100644 --- a/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs +++ b/src/Impostor.Api/Events/Game/Player/IPlayerEvent.cs @@ -6,14 +6,14 @@ namespace Impostor.Api.Events.Player public interface IPlayerEvent : IGameEvent { /// - /// Gets the that triggered this . + /// Gets the that triggered this . /// IClientPlayer ClientPlayer { get; } /// - /// Gets the networked that triggered this . - /// This belongs to the . + /// Gets the networked that triggered this . + /// This belongs to the . /// IInnerPlayerControl PlayerControl { get; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerExitVentEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerExitVentEvent.cs new file mode 100644 index 000000000..84dc1afcc --- /dev/null +++ b/src/Impostor.Api/Events/Game/Player/IPlayerExitVentEvent.cs @@ -0,0 +1,15 @@ +using Impostor.Api.Innersloth; + +namespace Impostor.Api.Events.Player +{ + /// + /// Called whenever a player exits a vent. + /// + public interface IPlayerExitVentEvent : IPlayerEvent + { + /// + /// Gets the exited vent. + /// + public IVent Vent { get; } + } +} 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/Events/Game/Player/IPlayerStartMeetingEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs index 1a28115eb..016d131d2 100644 --- a/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs +++ b/src/Impostor.Api/Events/Game/Player/IPlayerStartMeetingEvent.cs @@ -5,7 +5,7 @@ namespace Impostor.Api.Events.Player public interface IPlayerStartMeetingEvent : IPlayerEvent { /// - /// Gets the player who's body got reported. Is null when the meeting started by Emergency call button + /// Gets the player who's body got reported. Is null when the meeting started by Emergency call button. /// IInnerPlayerControl? Body { get; } } diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs index 81f178bc7..959e75342 100644 --- a/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs +++ b/src/Impostor.Api/Events/Game/Player/IPlayerVentEvent.cs @@ -2,16 +2,14 @@ namespace Impostor.Api.Events.Player { + /// + /// Called whenever a player moves to another vent. + /// public interface IPlayerVentEvent : IPlayerEvent { /// - /// Gets get the id of the used vent. + /// Gets the vent player moved to. /// - public VentLocation VentId { get; } - - /// - /// Gets a value indicating whether the vent was entered or exited. - /// - public bool VentEnter { get; } + public IVent NewVent { get; } } } diff --git a/src/Impostor.Api/Events/IEvent.cs b/src/Impostor.Api/Events/IEvent.cs index 796898e1a..3bdb3a670 100644 --- a/src/Impostor.Api/Events/IEvent.cs +++ b/src/Impostor.Api/Events/IEvent.cs @@ -3,4 +3,4 @@ public interface IEvent { } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Events/IEventCancelable.cs b/src/Impostor.Api/Events/IEventCancelable.cs index 319f02a22..1902284ee 100644 --- a/src/Impostor.Api/Events/IEventCancelable.cs +++ b/src/Impostor.Api/Events/IEventCancelable.cs @@ -3,8 +3,8 @@ public interface IEventCancelable : IEvent { /// - /// True if the event was cancelled. + /// Gets or sets a value indicating whether the event was cancelled. /// bool IsCancelled { get; set; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Events/IEventListener.cs b/src/Impostor.Api/Events/IEventListener.cs index 76392fcc5..f3e8e4102 100644 --- a/src/Impostor.Api/Events/IEventListener.cs +++ b/src/Impostor.Api/Events/IEventListener.cs @@ -3,4 +3,4 @@ public interface IEventListener { } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Events/IManualEventListener.cs b/src/Impostor.Api/Events/IManualEventListener.cs index b5c140ef8..1b054f750 100644 --- a/src/Impostor.Api/Events/IManualEventListener.cs +++ b/src/Impostor.Api/Events/IManualEventListener.cs @@ -4,10 +4,10 @@ namespace Impostor.Api.Events { public interface IManualEventListener : IEventListener { + EventPriority Priority { get; set; } + public bool CanExecute(); public ValueTask Execute(IEvent @event); - - EventPriority Priority { get; set; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Events/Managers/IEventManager.cs b/src/Impostor.Api/Events/Managers/IEventManager.cs index 07a7f7cf1..3d73768a1 100644 --- a/src/Impostor.Api/Events/Managers/IEventManager.cs +++ b/src/Impostor.Api/Events/Managers/IEventManager.cs @@ -16,20 +16,20 @@ IDisposable RegisterListener(TListener listener, Func, Tas where TListener : IEventListener; /// - /// Returns true if an event with the type is registered. + /// Returns true if an event with the type is registered. /// - /// True if the is registered. + /// True if the is registered. /// Type of the event. bool IsRegistered() where TEvent : IEvent; /// - /// Call all the event listeners for the type . + /// Call all the event listeners for the type . /// /// The event argument. /// Type of the event. - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. ValueTask CallAsync(TEvent @event) where TEvent : IEvent; } -} \ No newline at end of file +} 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/Exceptions/ImpostorConfigException.cs b/src/Impostor.Api/Exceptions/ImpostorConfigException.cs index 1e59a9baf..01b20d4c6 100644 --- a/src/Impostor.Api/Exceptions/ImpostorConfigException.cs +++ b/src/Impostor.Api/Exceptions/ImpostorConfigException.cs @@ -9,15 +9,15 @@ public ImpostorConfigException() { } - protected ImpostorConfigException(SerializationInfo info, StreamingContext context) : base(info, context) + public ImpostorConfigException(string? message) : base(message) { } - public ImpostorConfigException(string? message) : base(message) + public ImpostorConfigException(string? message, Exception? innerException) : base(message, innerException) { } - public ImpostorConfigException(string? message, Exception? innerException) : base(message, innerException) + protected ImpostorConfigException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/src/Impostor.Api/Exceptions/ImpostorException.cs b/src/Impostor.Api/Exceptions/ImpostorException.cs index 188c50e85..03891942d 100644 --- a/src/Impostor.Api/Exceptions/ImpostorException.cs +++ b/src/Impostor.Api/Exceptions/ImpostorException.cs @@ -9,16 +9,16 @@ public ImpostorException() { } - protected ImpostorException(SerializationInfo info, StreamingContext context) : base(info, context) + public ImpostorException(string? message) : base(message) { } - public ImpostorException(string? message) : base(message) + public ImpostorException(string? message, Exception? innerException) : base(message, innerException) { } - public ImpostorException(string? message, Exception? innerException) : base(message, innerException) + protected ImpostorException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs b/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs index 864602d04..513e590e4 100644 --- a/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs +++ b/src/Impostor.Api/Exceptions/ImpostorProtocolException.cs @@ -9,15 +9,15 @@ public ImpostorProtocolException() { } - protected ImpostorProtocolException(SerializationInfo info, StreamingContext context) : base(info, context) + public ImpostorProtocolException(string? message) : base(message) { } - public ImpostorProtocolException(string? message) : base(message) + public ImpostorProtocolException(string? message, Exception? innerException) : base(message, innerException) { } - public ImpostorProtocolException(string? message, Exception? innerException) : base(message, innerException) + protected ImpostorProtocolException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/src/Impostor.Api/Extensions/DictionaryExtensions.cs b/src/Impostor.Api/Extensions/DictionaryExtensions.cs new file mode 100644 index 000000000..39a19ec95 --- /dev/null +++ b/src/Impostor.Api/Extensions/DictionaryExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Impostor.Api +{ + public static class DictionaryExtensions + { + /// Returns a read-only wrapper for the dictionary. + /// An to create a from. + /// The type of the keys of . + /// The type of the values of . + /// An object that acts as a read-only wrapper around the . + public static ReadOnlyDictionary AsReadOnly(this IDictionary dictionary) + where TKey : notnull + { + return new ReadOnlyDictionary(dictionary); + } + } +} diff --git a/src/Impostor.Api/Extensions/SpanReaderExtensions.cs b/src/Impostor.Api/Extensions/SpanReaderExtensions.cs index c103ee7bc..05b891ab4 100644 --- a/src/Impostor.Api/Extensions/SpanReaderExtensions.cs +++ b/src/Impostor.Api/Extensions/SpanReaderExtensions.cs @@ -5,7 +5,7 @@ namespace Impostor.Api { /// - /// Priovides a StreamReader-like api throught extensions + /// Priovides a StreamReader-like api throught extensions. /// public static class SpanReaderExtensions { @@ -35,9 +35,7 @@ public static float ReadSingle(this ref ReadOnlySpan input) { var original = Advance(ref input); - // BitConverter.Int32BitsToSingle - // Doesn't exist in net 2.0 for some reason - return Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(original)); + return BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(original)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -46,18 +44,12 @@ public static bool ReadBoolean(this ref ReadOnlySpan input) return input.ReadByte() != 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe float Int32BitsToSingle(int value) - { - return *((float*)&value); - } - /// - /// Advances the position of by the size of . + /// Advances the position of by the size of . /// /// Type that will be read. /// input "stream"/span. - /// The original input + /// The original input. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe ReadOnlySpan Advance(ref ReadOnlySpan input) where T : unmanaged @@ -67,4 +59,4 @@ private static unsafe ReadOnlySpan Advance(ref ReadOnlySpan input return original; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Extensions/SystemTypesExtensions.cs b/src/Impostor.Api/Extensions/SystemTypesExtensions.cs index 68f1efda1..4238b4d0d 100644 --- a/src/Impostor.Api/Extensions/SystemTypesExtensions.cs +++ b/src/Impostor.Api/Extensions/SystemTypesExtensions.cs @@ -9,4 +9,4 @@ public static string GetFriendlyName(this SystemTypes type) return SystemTypeHelpers.Names[(int)type]; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/Extensions/GameExtensions.cs b/src/Impostor.Api/Games/Extensions/GameExtensions.cs index a73697815..e30050f94 100644 --- a/src/Impostor.Api/Games/Extensions/GameExtensions.cs +++ b/src/Impostor.Api/Games/Extensions/GameExtensions.cs @@ -39,4 +39,4 @@ public static ValueTask SendToAsync(this IGame game, IMessageWriter writer, ICli return game.SendToAsync(writer, player.Client); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs b/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs index 9a5a2b4f1..de9a06692 100644 --- a/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs +++ b/src/Impostor.Api/Games/Extensions/GameManagerExtensions.cs @@ -8,7 +8,7 @@ public static class GameManagerExtensions { public static int GetGameCount(this IGameManager manager, MapFlags map) { - return manager.Games.Count(game => map.HasFlag((MapFlags)(1 << game.Options.MapId))); + return manager.Games.Count(game => map.HasFlag((MapFlags)(1 << (byte)game.Options.Map))); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/GameCode.cs b/src/Impostor.Api/Games/GameCode.cs index 2c30e7e9d..e24c28a48 100644 --- a/src/Impostor.Api/Games/GameCode.cs +++ b/src/Impostor.Api/Games/GameCode.cs @@ -48,19 +48,19 @@ public static GameCode Create() public static GameCode From(string value) => new GameCode(value); - /// + /// public bool Equals(GameCode other) { return Code == other.Code && Value == other.Value; } - /// + /// public override bool Equals(object? obj) { return obj is GameCode other && Equals(other); } - /// + /// public override int GetHashCode() { return HashCode.Combine(Code, Value); @@ -71,4 +71,4 @@ public override string ToString() return Code; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/GameJoinError.cs b/src/Impostor.Api/Games/GameJoinError.cs index 4889ea965..7fcb0f946 100644 --- a/src/Impostor.Api/Games/GameJoinError.cs +++ b/src/Impostor.Api/Games/GameJoinError.cs @@ -41,8 +41,8 @@ public enum GameJoinError /// Custom error by a plugin. /// /// - /// A custom message can be set in . + /// A custom message can be set in . /// Custom, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/GameJoinResult.cs b/src/Impostor.Api/Games/GameJoinResult.cs index b33a2b669..4b25c8f31 100644 --- a/src/Impostor.Api/Games/GameJoinResult.cs +++ b/src/Impostor.Api/Games/GameJoinResult.cs @@ -45,4 +45,4 @@ public static GameJoinResult FromError(GameJoinError error) return new GameJoinResult(error); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/IGame.cs b/src/Impostor.Api/Games/IGame.cs index ad719867e..ecc11f811 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 @@ -25,47 +24,54 @@ public interface IGame int PlayerCount { get; } - IClientPlayer Host { get; } + IClientPlayer? Host { get; } bool IsPublic { get; } + /// + /// Gets or sets display name on game list. + /// + string? DisplayName { get; set; } + IDictionary Items { get; } int HostId { get; } - IClientPlayer GetClientPlayer(int clientId); + 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 . - /// - /// This does not kick the player with that from the lobby. + /// Adds an to the ban list of this game. + /// Prevents all future joins from this . + /// This does not kick the player with that from the lobby. /// /// - /// The to ban. + /// The to ban. /// void BanIp(IPAddress ipAddress); /// - /// Syncs the internal to all players. + /// Syncs the internal to all players. /// Necessary to do if you modified it, otherwise it won't be used. /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. ValueTask SyncSettingsAsync(); /// - /// Sets the specified list as Impostor on all connected players. + /// Sets game's privacy. /// - /// List of players to be Impostor. - /// A representing the asynchronous operation. - ValueTask SetInfectedAsync(IEnumerable players); + /// Privacy to set. + /// A representing the asynchronous operation. + ValueTask SetPrivacyAsync(bool isPublic); /// /// Send the message to all players. /// /// Message to send. /// Required limbo state of the player. - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. ValueTask SendToAllAsync(IMessageWriter writer, LimboStates states = LimboStates.NotLimbo); /// @@ -74,7 +80,7 @@ public interface IGame /// Message to send. /// The player to exclude from sending the message. /// Required limbo state of the player. - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. ValueTask SendToAllExceptAsync(IMessageWriter writer, int senderId, LimboStates states = LimboStates.NotLimbo); /// @@ -82,7 +88,7 @@ public interface IGame /// /// Message to send. /// ID of the client. - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. ValueTask SendToAsync(IMessageWriter writer, int id); } } diff --git a/src/Impostor.Api/Games/IGameCodeFactory.cs b/src/Impostor.Api/Games/IGameCodeFactory.cs index f264fe004..38f047fb5 100644 --- a/src/Impostor.Api/Games/IGameCodeFactory.cs +++ b/src/Impostor.Api/Games/IGameCodeFactory.cs @@ -4,4 +4,4 @@ public interface IGameCodeFactory { GameCode Create(); } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Games/Managers/IGameManager.cs b/src/Impostor.Api/Games/Managers/IGameManager.cs index 10a05f077..cdf8558cf 100644 --- a/src/Impostor.Api/Games/Managers/IGameManager.cs +++ b/src/Impostor.Api/Games/Managers/IGameManager.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Threading.Tasks; +using Impostor.Api.Innersloth; namespace Impostor.Api.Games.Managers { @@ -7,5 +9,7 @@ public interface IGameManager IEnumerable Games { get; } IGame? Find(GameCode code); + + ValueTask CreateAsync(GameOptionsData options); } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/IDateTimeProvider.cs b/src/Impostor.Api/IDateTimeProvider.cs new file mode 100644 index 000000000..95b3dee9e --- /dev/null +++ b/src/Impostor.Api/IDateTimeProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace Impostor.Api +{ + public interface IDateTimeProvider + { + DateTimeOffset UtcNow { get; } + } +} diff --git a/src/Impostor.Api/Impostor.Api.csproj b/src/Impostor.Api/Impostor.Api.csproj index 8ad258288..4cd5e425f 100644 --- a/src/Impostor.Api/Impostor.Api.csproj +++ b/src/Impostor.Api/Impostor.Api.csproj @@ -1,12 +1,11 @@ - netstandard2.0 - ProjectRules.ruleset + netstandard2.1 + ../ProjectRules.ruleset 9 true enable - 1.0.0 true snupkg Impostor.Api @@ -16,13 +15,18 @@ Impostor.Api Among Us;Impostor;Impostor Plugin https://raw.githubusercontent.com/Impostor/Impostor/dev/docs/images/logo_64.png + logo_64.png https://github.com/Impostor/Impostor git https://github.com/Impostor/Impostor + true - + + + + @@ -35,4 +39,4 @@ - \ No newline at end of file + diff --git a/src/Impostor.Api/Innersloth/AlterGameTags.cs b/src/Impostor.Api/Innersloth/AlterGameTags.cs index 46d1b2ee0..a1d9c425d 100644 --- a/src/Impostor.Api/Innersloth/AlterGameTags.cs +++ b/src/Impostor.Api/Innersloth/AlterGameTags.cs @@ -4,4 +4,4 @@ public enum AlterGameTags : byte { ChangePrivacy = 1, } -} \ No newline at end of file +} 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/ChatNoteType.cs b/src/Impostor.Api/Innersloth/ChatNoteType.cs index c16360177..697b0311f 100644 --- a/src/Impostor.Api/Innersloth/ChatNoteType.cs +++ b/src/Impostor.Api/Innersloth/ChatNoteType.cs @@ -4,4 +4,4 @@ public enum ChatNoteType : byte { DidVote = 0, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/Customization/ColorType.cs b/src/Impostor.Api/Innersloth/Customization/ColorType.cs index fc7be4cfa..d9e43a68a 100644 --- a/src/Impostor.Api/Innersloth/Customization/ColorType.cs +++ b/src/Impostor.Api/Innersloth/Customization/ColorType.cs @@ -1,6 +1,6 @@ namespace Impostor.Api.Innersloth.Customization { - public enum ColorType : byte + public enum ColorType { Red = 0, Blue = 1, 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/DeathReason.cs b/src/Impostor.Api/Innersloth/DeathReason.cs index a07ac0566..8b8a430ff 100644 --- a/src/Impostor.Api/Innersloth/DeathReason.cs +++ b/src/Impostor.Api/Innersloth/DeathReason.cs @@ -6,4 +6,4 @@ public enum DeathReason Kill = 1, Disconnect = 2, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/DisconnectReason.cs b/src/Impostor.Api/Innersloth/DisconnectReason.cs index 9526f58b1..21cdc944c 100644 --- a/src/Impostor.Api/Innersloth/DisconnectReason.cs +++ b/src/Impostor.Api/Innersloth/DisconnectReason.cs @@ -3,52 +3,66 @@ public enum DisconnectReason { ExitGame = 0, + Destroy = 16, + // The game you tried to join is full. // Check with the host to see if you can join next round. GameFull = 1, + // The game you tried to join already started. // Check with the host to see if you can join next round. GameStarted = 2, - // Could not find the game you're looking for. + + // Could not find the game you're looking for.. GameMissing = 3, IncorrectGame = 18, - // For these a message can be given, specifying an empty message shows + + // For this a message can be given, specifying an empty message shows // "An unknown error disconnected you from the server." - CustomMessage1 = 4, + // 4, 12, 13, 14, 15 also count as Custom Custom = 8, - // CustomMessage3 = 11, - // CustomMessage4 = 12, - // CustomMessage5 = 13, - // CustomMessage6 = 14, - // CustomMessage7 = 15, + // You are running an older version of the game. // Please update to play with others. IncorrectVersion = 5, + + // You were banned from { GameCode ?? "the room" } // You cannot rejoin that room. - // You were banned Banned = 6, - // You can rejoin if the room hasn't started - // You were kicked + + // You were kicked from { GameCode ?? "the room" } + // You can rejoin if the room hasn't started. Kicked = 7, + // You were banned for hacking. // Please stop. Hacking = 10, - Destroy = 16, + + // GameModes.LocalGame: // You disconnected from the host. // If this happens often, check your WiFi strength. // + // GameModes.OnlineGame: // You disconnected from the server. // If this happens often, check your network strength. // This may also be a server issue. Error = 17, + // The server stopped this game. Possibly due to inactivity. ServerRequest = 19, + // The Among Us servers are overloaded. // Sorry! Please try again later! ServerFull = 20, + FocusLostBackground = 207, + + // You may not join another game for another { BanMinutesLeft } minutes after intentionally disconnecting. IntentionalLeaving = 208, + + // You were disconnected because Among Us was suspended by another app. FocusLost = 209, + NewConnection = 210, } -} \ No newline at end of file +} 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/GameCodeParser.cs b/src/Impostor.Api/Innersloth/GameCodeParser.cs index 9717cff23..f4743c564 100644 --- a/src/Impostor.Api/Innersloth/GameCodeParser.cs +++ b/src/Impostor.Api/Innersloth/GameCodeParser.cs @@ -9,36 +9,11 @@ namespace Impostor.Api.Innersloth public static class GameCodeParser { private const string V2 = "QWXRTYLPESDFGHUJKZOCVBINMA"; - private static readonly int[] V2Map = { - 25, - 21, - 19, - 10, - 8, - 11, - 12, - 13, - 22, - 15, - 16, - 6, - 24, - 23, - 18, - 7, - 0, - 3, - 9, - 4, - 14, - 20, - 1, - 2, - 5, - 17 - }; + + private static readonly int[] V2Map = Enumerable.Range(65, 26).Select(v => V2.IndexOf((char)v)).ToArray(); + private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider(); - + public static string IntToGameName(int input) { // V2. @@ -57,22 +32,6 @@ public static string IntToGameName(int input) #endif } - private static string IntToGameNameV2(int input) - { - var a = input & 0x3FF; - var b = (input >> 10) & 0xFFFFF; - - return new string(new [] - { - V2[a % 26], - V2[a / 26], - V2[b % 26], - V2[b / 26 % 26], - V2[b / (26 * 26) % 26], - V2[b / (26 * 26 * 26) % 26] - }); - } - public static int GameNameToInt(string code) { var upper = code.ToUpperInvariant(); @@ -80,7 +39,7 @@ public static int GameNameToInt(string code) { return -1; } - + var len = code.Length; if (len == 6) { @@ -91,23 +50,8 @@ public static int GameNameToInt(string code) { return code[0] | ((code[1] | ((code[2] | (code[3] << 8)) << 8)) << 8); } - - return -1; - } - - private static int GameNameToIntV2(string code) - { - var a = V2Map[code[0] - 65]; - var b = V2Map[code[1] - 65]; - var c = V2Map[code[2] - 65]; - var d = V2Map[code[3] - 65]; - var e = V2Map[code[4] - 65]; - var f = V2Map[code[5] - 65]; - - var one = (a + 26 * b) & 0x3FF; - var two = (c + 26 * (d + 26 * (e + 26 * f))); - return (int) (one | ((two << 10) & 0x3FFFFC00) | 0x80000000); + return -1; } public static int GenerateCode(int len) @@ -116,7 +60,7 @@ public static int GenerateCode(int len) { throw new ArgumentException("should be 4 or 6", nameof(len)); } - + // Generate random bytes. #if NETSTANDARD2_0 var data = new byte[len]; @@ -124,7 +68,7 @@ public static int GenerateCode(int len) Span data = stackalloc byte[len]; #endif Random.GetBytes(data); - + // Convert to their char representation. Span dataChar = stackalloc char[len]; for (var i = 0; i < len; i++) @@ -138,5 +82,36 @@ public static int GenerateCode(int len) return GameNameToInt(new string(dataChar)); #endif } + + private static string IntToGameNameV2(int input) + { + var a = input & 0x3FF; + var b = (input >> 10) & 0xFFFFF; + + return new string(new[] + { + V2[a % 26], + V2[a / 26], + V2[b % 26], + V2[(b / 26) % 26], + V2[(b / (26 * 26)) % 26], + V2[(b / (26 * 26 * 26)) % 26], + }); + } + + private static int GameNameToIntV2(string code) + { + var a = V2Map[code[0] - 65]; + var b = V2Map[code[1] - 65]; + var c = V2Map[code[2] - 65]; + var d = V2Map[code[3] - 65]; + var e = V2Map[code[4] - 65]; + var f = V2Map[code[5] - 65]; + + var one = (a + (26 * b)) & 0x3FF; + var two = c + (26 * (d + (26 * (e + (26 * f))))); + + return (int)(one | ((two << 10) & 0x3FFFFC00) | 0x80000000); + } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/GameKeywords.cs b/src/Impostor.Api/Innersloth/GameKeywords.cs index bbb463f7f..4b3c67055 100644 --- a/src/Impostor.Api/Innersloth/GameKeywords.cs +++ b/src/Impostor.Api/Innersloth/GameKeywords.cs @@ -16,4 +16,4 @@ public enum GameKeywords : uint Polish = 128, English = 256, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/GameOptionsData.cs b/src/Impostor.Api/Innersloth/GameOptionsData.cs index d18efd8c5..5530c8878 100644 --- a/src/Impostor.Api/Innersloth/GameOptionsData.cs +++ b/src/Impostor.Api/Innersloth/GameOptionsData.cs @@ -7,145 +7,130 @@ namespace Impostor.Api.Innersloth public class GameOptionsData { /// - /// The latest major version of the game client. + /// The latest major version of the game client. /// public const int LatestVersion = 4; /// - /// Gets or sets host's version of the game. + /// Gets or sets host's version of the game. /// - public byte Version { get; set; } + public byte Version { get; set; } = LatestVersion; /// - /// Gets or sets the maximum amount of players for this lobby. + /// Gets or sets the maximum amount of players for this lobby. /// - public byte MaxPlayers { get; set; } + public byte MaxPlayers { get; set; } = 10; /// - /// Gets or sets the language of the lobby as per enum. + /// Gets or sets the language of the lobby as per enum. /// - public GameKeywords Keywords { get; set; } + public GameKeywords Keywords { get; set; } = GameKeywords.English; /// - /// Gets or sets the MapId selected for this lobby + /// Gets or sets the Map selected for this lobby. /// - /// - /// Skeld = 0, MiraHQ = 1, Polus = 2. - /// - internal byte MapId { get; set; } - - /// - /// Gets or sets the map selected for this lobby - /// - public MapTypes Map - { - get => (MapTypes)MapId; - set => MapId = (byte)value; - } + public MapTypes Map { get; set; } = MapTypes.Skeld; /// - /// Gets or sets the Player speed modifier. + /// Gets or sets the Player speed modifier. /// - public float PlayerSpeedMod { get; set; } + public float PlayerSpeedMod { get; set; } = 1f; /// - /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value. + /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value. /// - public float CrewLightMod { get; set; } + public float CrewLightMod { get; set; } = 1f; /// - /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value. + /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value. /// - public float ImpostorLightMod { get; set; } + public float ImpostorLightMod { get; set; } = 1f; /// - /// Gets or sets the Impostor cooldown to kill in seconds. + /// Gets or sets the Impostor cooldown to kill in seconds. /// - public float KillCooldown { get; set; } + public float KillCooldown { get; set; } = 15f; /// - /// Gets or sets the number of common tasks. + /// Gets or sets the number of common tasks. /// - public int NumCommonTasks { get; set; } + public int NumCommonTasks { get; set; } = 1; /// - /// Gets or sets the number of long tasks. + /// Gets or sets the number of long tasks. /// - public int NumLongTasks { get; set; } + public int NumLongTasks { get; set; } = 1; /// - /// Gets or sets the number of short tasks. + /// Gets or sets the number of short tasks. /// - public int NumShortTasks { get; set; } + public int NumShortTasks { get; set; } = 2; /// - /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds. + /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds. /// - public int NumEmergencyMeetings { get; set; } + public int NumEmergencyMeetings { get; set; } = 1; /// - /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds. + /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds. /// - public int EmergencyCooldown { get; set; } + public int EmergencyCooldown { get; set; } = 15; /// - /// Gets or sets the number of impostors for this lobby. + /// Gets or sets the number of impostors for this lobby. /// - public int NumImpostors { get; set; } + public int NumImpostors { get; set; } = 1; /// - /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks. + /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks. /// - public bool GhostsDoTasks { get; set; } + public bool GhostsDoTasks { get; set; } = true; /// - /// Gets or sets the Kill as per values in . + /// Gets or sets the Kill as per values in . /// - /// - /// Short = 0, Normal = 1, Long = 2. - /// - public KillDistances KillDistance { get; set; } + public KillDistances KillDistance { get; set; } = KillDistances.Normal; /// - /// Gets or sets the time for discussion before voting time in seconds. + /// Gets or sets the time for discussion before voting time in seconds. /// - public int DiscussionTime { get; set; } + public int DiscussionTime { get; set; } = 15; /// - /// Gets or sets the time for voting in seconds. + /// Gets or sets the time for voting in seconds. /// - public int VotingTime { get; set; } + public int VotingTime { get; set; } = 120; /// - /// Gets or sets a value indicating whether an ejected player is an impostor or not. + /// Gets or sets a value indicating whether an ejected player is an impostor or not. /// - public bool ConfirmImpostor { get; set; } + public bool ConfirmImpostor { get; set; } = true; /// - /// Gets or sets a value indicating whether players are able to see tasks being performed by other players. + /// Gets or sets a value indicating whether players are able to see tasks being performed by other players. /// /// - /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players. + /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players. /// - public bool VisualTasks { get; set; } + public bool VisualTasks { get; set; } = true; /// - /// Gets or sets a value indicating whether the vote is anonymous. + /// Gets or sets a value indicating whether the vote is anonymous. /// public bool AnonymousVotes { get; set; } /// - /// Gets or sets the task bar update mode as per values in . + /// Gets or sets the task bar update mode as per values in . /// - public TaskBarUpdate TaskBarUpdate { get; set; } + public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always; /// - /// Gets or sets a value indicating whether the GameOptions are the default ones. + /// Gets or sets a value indicating whether the GameOptions are the default ones. /// - public bool IsDefaults { get; set; } + public bool IsDefaults { get; set; } = true; /// - /// Deserialize a packet/message to a new GameOptionsData object. + /// Deserialize a packet/message to a new GameOptionsData object. /// /// Message reader object containing the raw message. /// GameOptionsData object. @@ -157,16 +142,16 @@ public static GameOptionsData DeserializeCreate(IMessageReader reader) } /// - /// Serializes this instance of GameOptionsData object to a specified BinaryWriter. + /// Serializes this instance of GameOptionsData object to a specified BinaryWriter. /// /// 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); writer.Write((uint)Keywords); - writer.Write((byte)MapId); + writer.Write((byte)Map); writer.Write((float)PlayerSpeedMod); writer.Write((float)CrewLightMod); writer.Write((float)ImpostorLightMod); @@ -204,8 +189,16 @@ 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. + /// Deserialize a ReadOnlyMemory object to this instance of the GameOptionsData object. /// /// Memory containing the message/packet. public void Deserialize(ReadOnlyMemory memory) @@ -215,7 +208,7 @@ public void Deserialize(ReadOnlyMemory memory) Version = bytes.ReadByte(); MaxPlayers = bytes.ReadByte(); Keywords = (GameKeywords)bytes.ReadUInt32(); - MapId = bytes.ReadByte(); + Map = (MapTypes)bytes.ReadByte(); PlayerSpeedMod = bytes.ReadSingle(); CrewLightMod = bytes.ReadSingle(); diff --git a/src/Impostor.Api/Innersloth/GameOverReason.cs b/src/Impostor.Api/Innersloth/GameOverReason.cs index 6a95d3747..5c30036c5 100644 --- a/src/Impostor.Api/Innersloth/GameOverReason.cs +++ b/src/Impostor.Api/Innersloth/GameOverReason.cs @@ -7,9 +7,7 @@ public enum GameOverReason : byte ImpostorByVote = 2, ImpostorByKill = 3, ImpostorBySabotage = 4, - - // Unused (?) ImpostorDisconnect = 5, HumansDisconnect = 6, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/GameStates.cs b/src/Impostor.Api/Innersloth/GameStates.cs index f5aaa9cc5..8e791de7d 100644 --- a/src/Impostor.Api/Innersloth/GameStates.cs +++ b/src/Impostor.Api/Innersloth/GameStates.cs @@ -8,4 +8,4 @@ public enum GameStates : byte Ended = 3, Destroyed = 4, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/GameVersion.cs b/src/Impostor.Api/Innersloth/GameVersion.cs index c11933ee3..501558959 100644 --- a/src/Impostor.Api/Innersloth/GameVersion.cs +++ b/src/Impostor.Api/Innersloth/GameVersion.cs @@ -7,4 +7,4 @@ public static int GetVersion(int year, int month, int day, int rev = 0) return (year * 25000) + (month * 1800) + (day * 50) + rev; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/IVent.cs b/src/Impostor.Api/Innersloth/IVent.cs new file mode 100644 index 000000000..4c697f624 --- /dev/null +++ b/src/Impostor.Api/Innersloth/IVent.cs @@ -0,0 +1,19 @@ +using System.Numerics; + +namespace Impostor.Api.Innersloth +{ + public interface IVent + { + int Id { get; } + + string Name { get; } + + Vector2 Position { get; } + + IVent? Left { get; } + + IVent? Center { get; } + + IVent? Right { get; } + } +} 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/MapFlags.cs b/src/Impostor.Api/Innersloth/MapFlags.cs index ad462a273..9b0d8f02a 100644 --- a/src/Impostor.Api/Innersloth/MapFlags.cs +++ b/src/Impostor.Api/Innersloth/MapFlags.cs @@ -9,4 +9,4 @@ public enum MapFlags MiraHQ = 2, Polus = 4, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/MapTypes.cs b/src/Impostor.Api/Innersloth/MapTypes.cs index 8dc07b5d1..5de0adb65 100644 --- a/src/Impostor.Api/Innersloth/MapTypes.cs +++ b/src/Impostor.Api/Innersloth/MapTypes.cs @@ -1,9 +1,10 @@ namespace Impostor.Api.Innersloth { - public enum MapTypes + public enum MapTypes : byte { Skeld = 0, MiraHQ = 1, Polus = 2, + Airship = 4, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/Maps/AirshipData.cs b/src/Impostor.Api/Innersloth/Maps/AirshipData.cs new file mode 100644 index 000000000..46504c9eb --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/AirshipData.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Impostor.Api.Innersloth.Maps.Vents; + +namespace Impostor.Api.Innersloth.Maps +{ + public class AirshipData : IMapData + { + private readonly IReadOnlyDictionary _vents; + + public AirshipData() + { + var vents = new[] + { + new AirshipVent(this, AirshipVent.Ids.Vault, new Vector2(-12.6322f, 8.4735f), left: AirshipVent.Ids.Cockpit), + new AirshipVent(this, AirshipVent.Ids.Cockpit, new Vector2(-22.099f, -1.512f), left: AirshipVent.Ids.Vault, right: AirshipVent.Ids.ViewingDeck), + new AirshipVent(this, AirshipVent.Ids.ViewingDeck, new Vector2(-15.659f, -11.6991f), left: AirshipVent.Ids.Cockpit), + new AirshipVent(this, AirshipVent.Ids.EngineRoom, new Vector2(0.203f, -2.5361f), left: AirshipVent.Ids.Kitchen, right: AirshipVent.Ids.MainHallBottom), + new AirshipVent(this, AirshipVent.Ids.Kitchen, new Vector2(-2.6019f, -9.338f), left: AirshipVent.Ids.EngineRoom, right: AirshipVent.Ids.MainHallBottom), + new AirshipVent(this, AirshipVent.Ids.MainHallBottom, new Vector2(7.021f, -3.730999f), left: AirshipVent.Ids.EngineRoom, right: AirshipVent.Ids.Kitchen), + new AirshipVent(this, AirshipVent.Ids.GapRight, new Vector2(9.814f, 3.206f), left: AirshipVent.Ids.MainHallTop, right: AirshipVent.Ids.GapLeft), + new AirshipVent(this, AirshipVent.Ids.GapLeft, new Vector2(12.663f, 5.922f), left: AirshipVent.Ids.MainHallTop, right: AirshipVent.Ids.GapRight), + new AirshipVent(this, AirshipVent.Ids.MainHallTop, new Vector2(3.605f, 6.923f), left: AirshipVent.Ids.GapLeft, right: AirshipVent.Ids.GapRight), + new AirshipVent(this, AirshipVent.Ids.Showers, new Vector2(23.9869f, -1.386f), left: AirshipVent.Ids.Records, right: AirshipVent.Ids.CargoBay), + new AirshipVent(this, AirshipVent.Ids.Records, new Vector2(23.2799f, 8.259998f), left: AirshipVent.Ids.Showers, right: AirshipVent.Ids.CargoBay), + new AirshipVent(this, AirshipVent.Ids.CargoBay, new Vector2(30.4409f, -3.577f), left: AirshipVent.Ids.Showers, right: AirshipVent.Ids.Records), + }; + + Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly(); + _vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly(); + } + + public IReadOnlyDictionary Vents { get; } + + IReadOnlyDictionary IMapData.Vents => _vents; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/IMapData.cs b/src/Impostor.Api/Innersloth/Maps/IMapData.cs new file mode 100644 index 000000000..8f94b38cd --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/IMapData.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Impostor.Api.Innersloth.Maps +{ + public interface IMapData + { + public static IReadOnlyDictionary Maps { get; } = new Dictionary + { + [MapTypes.Skeld] = new SkeldData(), + [MapTypes.MiraHQ] = new MiraData(), + [MapTypes.Polus] = new PolusData(), + [MapTypes.Airship] = new AirshipData(), + }.AsReadOnly(); + + IReadOnlyDictionary Vents { get; } + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/MiraData.cs b/src/Impostor.Api/Innersloth/Maps/MiraData.cs new file mode 100644 index 000000000..37ed2451f --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/MiraData.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Impostor.Api.Innersloth.Maps.Vents; + +namespace Impostor.Api.Innersloth.Maps +{ + public class MiraData : IMapData + { + private readonly IReadOnlyDictionary _vents; + + public MiraData() + { + var vents = new[] + { + new MiraVent(this, MiraVent.Ids.Balcony, new Vector2(23.77f, -1.94f), left: MiraVent.Ids.Medbay, right: MiraVent.Ids.Cafeteria), + new MiraVent(this, MiraVent.Ids.Cafeteria, new Vector2(23.9f, 7.18f), left: MiraVent.Ids.Admin, right: MiraVent.Ids.Balcony), + new MiraVent(this, MiraVent.Ids.Reactor, new Vector2(0.4800001f, 10.697f), left: MiraVent.Ids.Laboratory, center: MiraVent.Ids.Decontamination, right: MiraVent.Ids.Launchpad), + new MiraVent(this, MiraVent.Ids.Laboratory, new Vector2(11.606f, 13.816f), left: MiraVent.Ids.Reactor, center: MiraVent.Ids.Decontamination, right: MiraVent.Ids.Office), + new MiraVent(this, MiraVent.Ids.Office, new Vector2(13.28f, 20.13f), left: MiraVent.Ids.Laboratory, center: MiraVent.Ids.Admin, right: MiraVent.Ids.Greenhouse), + new MiraVent(this, MiraVent.Ids.Admin, new Vector2(22.39f, 17.23f), left: MiraVent.Ids.Greenhouse, center: MiraVent.Ids.Cafeteria, right: MiraVent.Ids.Office), + new MiraVent(this, MiraVent.Ids.Greenhouse, new Vector2(17.85f, 25.23f), left: MiraVent.Ids.Admin, right: MiraVent.Ids.Office), + new MiraVent(this, MiraVent.Ids.Medbay, new Vector2(15.41f, -1.82f), left: MiraVent.Ids.Balcony, right: MiraVent.Ids.LockerRoom), + new MiraVent(this, MiraVent.Ids.Decontamination, new Vector2(6.83f, 3.145f), left: MiraVent.Ids.Reactor, center: MiraVent.Ids.LockerRoom, right: MiraVent.Ids.Laboratory), + new MiraVent(this, MiraVent.Ids.LockerRoom, new Vector2(4.29f, 0.5299997f), left: MiraVent.Ids.Medbay, center: MiraVent.Ids.Launchpad, right: MiraVent.Ids.Decontamination), + new MiraVent(this, MiraVent.Ids.Launchpad, new Vector2(-6.18f, 3.56f), left: MiraVent.Ids.Reactor, right: MiraVent.Ids.LockerRoom), + }; + + Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly(); + _vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly(); + } + + public IReadOnlyDictionary Vents { get; } + + IReadOnlyDictionary IMapData.Vents => _vents; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/PolusData.cs b/src/Impostor.Api/Innersloth/Maps/PolusData.cs new file mode 100644 index 000000000..76bc97043 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/PolusData.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Impostor.Api.Innersloth.Maps.Vents; + +namespace Impostor.Api.Innersloth.Maps +{ + public class PolusData : IMapData + { + private readonly IReadOnlyDictionary _vents; + + public PolusData() + { + var vents = new[] + { + new PolusVent(this, PolusVent.Ids.Security, new Vector2(1.929f, -9.558001f), left: PolusVent.Ids.O2, right: PolusVent.Ids.Electrical), + new PolusVent(this, PolusVent.Ids.Electrical, new Vector2(6.9f, -14.41f), left: PolusVent.Ids.O2, right: PolusVent.Ids.Security), + new PolusVent(this, PolusVent.Ids.O2, new Vector2(3.51f, -16.58f), left: PolusVent.Ids.Electrical, right: PolusVent.Ids.Security), + new PolusVent(this, PolusVent.Ids.Communications, new Vector2(12.304f, -18.898f), left: PolusVent.Ids.Storage, right: PolusVent.Ids.Office), + new PolusVent(this, PolusVent.Ids.Office, new Vector2(16.379f, -19.599f), left: PolusVent.Ids.Communications, right: PolusVent.Ids.Storage), + new PolusVent(this, PolusVent.Ids.Admin, new Vector2(20.089f, -25.517f), left: PolusVent.Ids.OutsideAdmin, right: PolusVent.Ids.Lava), + new PolusVent(this, PolusVent.Ids.Laboratory, new Vector2(32.963f, -9.526f), right: PolusVent.Ids.Lava), + new PolusVent(this, PolusVent.Ids.Lava, new Vector2(30.907f, -11.86f), left: PolusVent.Ids.Laboratory, right: PolusVent.Ids.Admin), + new PolusVent(this, PolusVent.Ids.Storage, new Vector2(22f, -12.19f), left: PolusVent.Ids.Communications, right: PolusVent.Ids.Office), + new PolusVent(this, PolusVent.Ids.RightStabilizer, new Vector2(24.02f, -8.39f), left: PolusVent.Ids.LeftStabilizer), + new PolusVent(this, PolusVent.Ids.LeftStabilizer, new Vector2(9.64f, -7.72f), left: PolusVent.Ids.RightStabilizer), + new PolusVent(this, PolusVent.Ids.OutsideAdmin, new Vector2(18.93f, -24.85f), right: PolusVent.Ids.Admin), + }; + + Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly(); + _vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly(); + } + + public IReadOnlyDictionary Vents { get; } + + IReadOnlyDictionary IMapData.Vents => _vents; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/SkeldData.cs b/src/Impostor.Api/Innersloth/Maps/SkeldData.cs new file mode 100644 index 000000000..6555ba1b9 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/SkeldData.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Impostor.Api.Innersloth.Maps.Vents; + +namespace Impostor.Api.Innersloth.Maps +{ + public class SkeldData : IMapData + { + private readonly IReadOnlyDictionary _vents; + + public SkeldData() + { + var vents = new[] + { + new SkeldVent(this, SkeldVent.Ids.Admin, new Vector2(2.544f, -9.955201f), left: SkeldVent.Ids.Cafeteria, right: SkeldVent.Ids.RightHallway), + new SkeldVent(this, SkeldVent.Ids.RightHallway, new Vector2(9.384f, -6.438f), left: SkeldVent.Ids.Admin, right: SkeldVent.Ids.Cafeteria), + new SkeldVent(this, SkeldVent.Ids.Cafeteria, new Vector2(4.2588f, -0.276f), left: SkeldVent.Ids.Admin, right: SkeldVent.Ids.RightHallway), + new SkeldVent(this, SkeldVent.Ids.Electrical, new Vector2(-9.7764f, -8.034f), left: SkeldVent.Ids.Security, right: SkeldVent.Ids.Medbay), + new SkeldVent(this, SkeldVent.Ids.UpperEngine, new Vector2(-15.288f, 2.52f), left: SkeldVent.Ids.UpperReactor), + new SkeldVent(this, SkeldVent.Ids.Security, new Vector2(-12.534f, -6.9492f), left: SkeldVent.Ids.Medbay, right: SkeldVent.Ids.Electrical), + new SkeldVent(this, SkeldVent.Ids.Medbay, new Vector2(-10.608f, -4.176f), left: SkeldVent.Ids.Security, right: SkeldVent.Ids.Electrical), + new SkeldVent(this, SkeldVent.Ids.Weapons, new Vector2(8.820001f, 3.324f), right: SkeldVent.Ids.UpperNavigation), + new SkeldVent(this, SkeldVent.Ids.LowerReactor, new Vector2(-20.796f, -6.9528f), left: SkeldVent.Ids.LowerEngine), + new SkeldVent(this, SkeldVent.Ids.LowerEngine, new Vector2(-15.2508f, -13.656f), left: SkeldVent.Ids.LowerReactor), + new SkeldVent(this, SkeldVent.Ids.Shields, new Vector2(9.5232f, -14.3376f), left: SkeldVent.Ids.LowerNavigation), + new SkeldVent(this, SkeldVent.Ids.UpperReactor, new Vector2(-21.876f, -3.0516f), left: SkeldVent.Ids.UpperEngine), + new SkeldVent(this, SkeldVent.Ids.UpperNavigation, new Vector2(16.008f, -3.168f), right: SkeldVent.Ids.Weapons), + new SkeldVent(this, SkeldVent.Ids.LowerNavigation, new Vector2(16.008f, -6.384f), right: SkeldVent.Ids.Shields), + }; + + Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly(); + _vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly(); + } + + public IReadOnlyDictionary Vents { get; } + + IReadOnlyDictionary IMapData.Vents => _vents; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/Vents/AirshipVent.cs b/src/Impostor.Api/Innersloth/Maps/Vents/AirshipVent.cs new file mode 100644 index 000000000..331469244 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/Vents/AirshipVent.cs @@ -0,0 +1,61 @@ +using System; +using System.Numerics; + +namespace Impostor.Api.Innersloth.Maps.Vents +{ + public class AirshipVent : IVent + { + private readonly Lazy? _left; + + private readonly Lazy? _center; + + private readonly Lazy? _right; + + internal AirshipVent(AirshipData data, Ids id, Vector2 position, Ids? left = null, Ids? center = null, Ids? right = null) + { + Id = id; + Name = id.ToString(); + Position = position; + + _left = left == null ? null : new Lazy(() => data.Vents[left.Value]); + _center = center == null ? null : new Lazy(() => data.Vents[center.Value]); + _right = right == null ? null : new Lazy(() => data.Vents[right.Value]); + } + + public enum Ids + { + Vault = 0, + Cockpit = 1, + ViewingDeck = 2, + EngineRoom = 3, + Kitchen = 4, + MainHallBottom = 5, + GapRight = 6, + GapLeft = 7, + MainHallTop = 8, + Showers = 9, + Records = 10, + CargoBay = 11, + } + + public Ids Id { get; } + + int IVent.Id => (int)Id; + + public string Name { get; } + + public Vector2 Position { get; } + + public AirshipVent? Left => _left?.Value; + + IVent? IVent.Left => Left; + + public AirshipVent? Center => _center?.Value; + + IVent? IVent.Center => Center; + + public AirshipVent? Right => _right?.Value; + + IVent? IVent.Right => Right; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/Vents/MiraVent.cs b/src/Impostor.Api/Innersloth/Maps/Vents/MiraVent.cs new file mode 100644 index 000000000..81daddb18 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/Vents/MiraVent.cs @@ -0,0 +1,60 @@ +using System; +using System.Numerics; + +namespace Impostor.Api.Innersloth.Maps.Vents +{ + public class MiraVent : IVent + { + private readonly Lazy? _left; + + private readonly Lazy? _center; + + private readonly Lazy? _right; + + internal MiraVent(MiraData data, Ids id, Vector2 position, Ids? left = null, Ids? center = null, Ids? right = null) + { + Id = id; + Name = id.ToString(); + Position = position; + + _left = left == null ? null : new Lazy(() => data.Vents[left.Value]); + _center = center == null ? null : new Lazy(() => data.Vents[center.Value]); + _right = right == null ? null : new Lazy(() => data.Vents[right.Value]); + } + + public enum Ids + { + Balcony = 1, + Cafeteria = 2, + Reactor = 3, + Laboratory = 4, + Office = 5, + Admin = 6, + Greenhouse = 7, + Medbay = 8, + Decontamination = 9, + LockerRoom = 10, + Launchpad = 11, + } + + public Ids Id { get; } + + int IVent.Id => (int)Id; + + public string Name { get; } + + public Vector2 Position { get; } + + public MiraVent? Left => _left?.Value; + + IVent? IVent.Left => Left; + + public MiraVent? Center => _center?.Value; + + IVent? IVent.Center => Center; + + public MiraVent? Right => _right?.Value; + + IVent? IVent.Right => Right; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/Vents/PolusVent.cs b/src/Impostor.Api/Innersloth/Maps/Vents/PolusVent.cs new file mode 100644 index 000000000..54b542aff --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/Vents/PolusVent.cs @@ -0,0 +1,61 @@ +using System; +using System.Numerics; + +namespace Impostor.Api.Innersloth.Maps.Vents +{ + public class PolusVent : IVent + { + private readonly Lazy? _left; + + private readonly Lazy? _center; + + private readonly Lazy? _right; + + internal PolusVent(PolusData data, Ids id, Vector2 position, Ids? left = null, Ids? center = null, Ids? right = null) + { + Id = id; + Name = id.ToString(); + Position = position; + + _left = left == null ? null : new Lazy(() => data.Vents[left.Value]); + _center = center == null ? null : new Lazy(() => data.Vents[center.Value]); + _right = right == null ? null : new Lazy(() => data.Vents[right.Value]); + } + + public enum Ids + { + Security = 0, + Electrical = 1, + O2 = 2, + Communications = 3, + Office = 4, + Admin = 5, + Laboratory = 6, + Lava = 7, + Storage = 8, + RightStabilizer = 9, + LeftStabilizer = 10, + OutsideAdmin = 11, + } + + public Ids Id { get; } + + int IVent.Id => (int)Id; + + public string Name { get; } + + public Vector2 Position { get; } + + public PolusVent? Left => _left?.Value; + + IVent? IVent.Left => Left; + + public PolusVent? Center => _center?.Value; + + IVent? IVent.Center => Center; + + public PolusVent? Right => _right?.Value; + + IVent? IVent.Right => Right; + } +} diff --git a/src/Impostor.Api/Innersloth/Maps/Vents/SkeldVent.cs b/src/Impostor.Api/Innersloth/Maps/Vents/SkeldVent.cs new file mode 100644 index 000000000..747b6fdf9 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Maps/Vents/SkeldVent.cs @@ -0,0 +1,63 @@ +using System; +using System.Numerics; + +namespace Impostor.Api.Innersloth.Maps.Vents +{ + public class SkeldVent : IVent + { + private readonly Lazy? _left; + + private readonly Lazy? _center; + + private readonly Lazy? _right; + + internal SkeldVent(SkeldData data, Ids id, Vector2 position, Ids? left = null, Ids? center = null, Ids? right = null) + { + Id = id; + Name = id.ToString(); + Position = position; + + _left = left == null ? null : new Lazy(() => data.Vents[left.Value]); + _center = center == null ? null : new Lazy(() => data.Vents[center.Value]); + _right = right == null ? null : new Lazy(() => data.Vents[right.Value]); + } + + public enum Ids + { + Admin = 0, + RightHallway = 1, + Cafeteria = 2, + Electrical = 3, + UpperEngine = 4, + Security = 5, + Medbay = 6, + Weapons = 7, + LowerReactor = 8, + LowerEngine = 9, + Shields = 10, + UpperReactor = 11, + UpperNavigation = 12, + LowerNavigation = 13, + } + + public Ids Id { get; } + + int IVent.Id => (int)Id; + + public string Name { get; } + + public Vector2 Position { get; } + + public SkeldVent? Left => _left?.Value; + + IVent? IVent.Left => Left; + + public SkeldVent? Center => _center?.Value; + + IVent? IVent.Center => Center; + + public SkeldVent? Right => _right?.Value; + + IVent? IVent.Right => Right; + } +} diff --git a/src/Impostor.Api/Innersloth/Platforms.cs b/src/Impostor.Api/Innersloth/Platforms.cs new file mode 100644 index 000000000..2388e39e5 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Platforms.cs @@ -0,0 +1,15 @@ +namespace Impostor.Api.Innersloth +{ + public enum Platforms + { + Unknown = 0, + StandaloneEpicPC = 1, + StandaloneSteamPC = 2, + StandaloneMac = 3, + StandaloneWin10 = 4, + StandaloneItch = 5, + IPhone = 6, + Android = 7, + Switch = 8, + } +} diff --git a/src/Impostor.Api/Innersloth/QuickChatModes.cs b/src/Impostor.Api/Innersloth/QuickChatModes.cs new file mode 100644 index 000000000..192026474 --- /dev/null +++ b/src/Impostor.Api/Innersloth/QuickChatModes.cs @@ -0,0 +1,8 @@ +namespace Impostor.Api.Innersloth +{ + public enum QuickChatModes + { + FreeChatOrQuickChat = 1, + QuickChatOnly = 2, + } +} diff --git a/src/Impostor.Api/Innersloth/RegionInfo.cs b/src/Impostor.Api/Innersloth/RegionInfo.cs index c78978bd5..51580db1a 100644 --- a/src/Impostor.Api/Innersloth/RegionInfo.cs +++ b/src/Impostor.Api/Innersloth/RegionInfo.cs @@ -13,21 +13,10 @@ public RegionInfo(string name, string ping, IReadOnlyList servers) } public string Name { get; } - public string Ping { get; } - public IReadOnlyList Servers { get; } - public void Serialize(BinaryWriter writer) - { - writer.Write(0); - writer.Write(Name); - writer.Write(Ping); - writer.Write(Servers.Count); + public string Ping { get; } - foreach (var server in Servers) - { - server.Serialize(writer); - } - } + public IReadOnlyList Servers { get; } public static RegionInfo Deserialize(BinaryReader reader) { @@ -44,5 +33,18 @@ public static RegionInfo Deserialize(BinaryReader reader) return new RegionInfo(name, ping, servers); } + + public void Serialize(BinaryWriter writer) + { + writer.Write(0); + writer.Write(Name); + writer.Write(Ping); + writer.Write(Servers.Count); + + foreach (var server in Servers) + { + server.Serialize(writer); + } + } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/ServerInfo.cs b/src/Impostor.Api/Innersloth/ServerInfo.cs index 778582349..21ba1ea7c 100644 --- a/src/Impostor.Api/Innersloth/ServerInfo.cs +++ b/src/Impostor.Api/Innersloth/ServerInfo.cs @@ -5,24 +5,18 @@ namespace Impostor.Api.Innersloth { public class ServerInfo { - public string Name { get; } - public string Ip { get; } - public ushort Port { get; } - public ServerInfo(string name, string ip, ushort port) { Name = name; Ip = ip; Port = port; } - - public void Serialize(BinaryWriter writer) - { - writer.Write(Name); - writer.Write(IPAddress.Parse(Ip).GetAddressBytes()); - writer.Write(Port); - writer.Write(0); - } + + public string Name { get; } + + public string Ip { get; } + + public ushort Port { get; } public static ServerInfo Deserialize(BinaryReader reader) { @@ -30,8 +24,16 @@ public static ServerInfo Deserialize(BinaryReader reader) var ip = new IPAddress(reader.ReadBytes(4)).ToString(); var port = reader.ReadUInt16(); var unknown = reader.ReadInt32(); - + return new ServerInfo(name, ip, port); } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Name); + writer.Write(IPAddress.Parse(Ip).GetAddressBytes()); + writer.Write(Port); + writer.Write(0); + } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs b/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs index ad88c28af..e9ba29bec 100644 --- a/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs +++ b/src/Impostor.Api/Innersloth/SystemTypeHelpers.cs @@ -20,9 +20,9 @@ static SystemTypeHelpers() SystemTypes.LifeSupp => "O2", SystemTypes.LowerEngine => "Lower Engine", SystemTypes.LockerRoom => "Locker Room", - _ => x.ToString() + _ => x.ToString(), }; }).ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/SystemTypes.cs b/src/Impostor.Api/Innersloth/SystemTypes.cs index 7f91718b3..74affbfd1 100644 --- a/src/Impostor.Api/Innersloth/SystemTypes.cs +++ b/src/Impostor.Api/Innersloth/SystemTypes.cs @@ -3,40 +3,103 @@ public enum SystemTypes : byte { Hallway = 0, + Storage = 1, + Cafeteria = 2, + Reactor = 3, + UpperEngine = 4, + Nav = 5, + Admin = 6, + Electrical = 7, + LifeSupp = 8, + Shields = 9, + MedBay = 10, + Security = 11, + Weapons = 12, + LowerEngine = 13, + Comms = 14, + ShipTasks = 15, + Doors = 16, + Sabotage = 17, + /// - /// Decontam on Mira and bottom decontam on Polus + /// Decontam on Mira and bottom decontam on Polus /// Decontamination = 18, + Launchpad = 19, + LockerRoom = 20, + Laboratory = 21, + Balcony = 22, + Office = 23, + Greenhouse = 24, + Dropship = 25, + /// - /// Top decontam on Polus + /// Top decontam on Polus /// Decontamination2 = 26, + Outside = 27, + Specimens = 28, - BoilerRoom = 29 + + BoilerRoom = 29, + + VaultRoom = 30, + + Cockpit = 31, + + Armory = 32, + + Kitchen = 33, + + ViewingDeck = 34, + + HallOfPortraits = 35, + + CargoBay = 36, + + Ventilation = 37, + + Showers = 38, + + Engine = 39, + + Brig = 40, + + MeetingRoom = 41, + + Records = 42, + + Lounge = 43, + + GapRoom = 44, + + MainHall = 45, + + Medical = 46, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/TaskBarUpdate.cs b/src/Impostor.Api/Innersloth/TaskBarUpdate.cs index f4d7c1f69..ea9a5fc5b 100644 --- a/src/Impostor.Api/Innersloth/TaskBarUpdate.cs +++ b/src/Impostor.Api/Innersloth/TaskBarUpdate.cs @@ -4,6 +4,6 @@ public enum TaskBarUpdate : byte { Always = 0, Meetings = 1, - Never = 2 + Never = 2, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/TaskTypes.cs b/src/Impostor.Api/Innersloth/TaskTypes.cs index 8b15354a4..d4794796c 100644 --- a/src/Impostor.Api/Innersloth/TaskTypes.cs +++ b/src/Impostor.Api/Innersloth/TaskTypes.cs @@ -46,4 +46,4 @@ public enum TaskTypes : uint RecordTemperature = 41, RebootWifi = 42, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/TextBox.cs b/src/Impostor.Api/Innersloth/TextBox.cs index 9533d83ac..1928ce283 100644 --- a/src/Impostor.Api/Innersloth/TextBox.cs +++ b/src/Impostor.Api/Innersloth/TextBox.cs @@ -7,4 +7,4 @@ public static bool IsCharAllowed(char i) return i == ' ' || (i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || (i >= 'À' && i <= 'ÿ') || (i >= 'Ѐ' && i <= 'џ') || (i >= 'ㄱ' && i <= 'ㆎ') || (i >= '가' && i <= '힣'); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Innersloth/VentLocation.cs b/src/Impostor.Api/Innersloth/VentLocation.cs deleted file mode 100644 index f9b8567ac..000000000 --- a/src/Impostor.Api/Innersloth/VentLocation.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Impostor.Api.Innersloth -{ - public enum VentLocation : uint - { - // Skeld - SkeldAdmin = 0, - SkeldRightHallway = 1, - SkeldCafeteria = 2, - SkeldElectrical = 3, - SkeldUpperEngine = 4, - SkeldSecurity = 5, - SkeldMedbay = 6, - SkeldWeapons = 7, - SkeldLowerReactor = 8, - SkeldLowerEngine = 9, - SkeldShields = 10, - SkeldUpperReactor = 11, - SkeldUpperNavigation = 12, - SkeldLowerNavigation = 13, - - // Mira HQ - MiraBalcony = 1, - MiraCafeteria = 2, - MiraReactor = 3, - MiraLaboratory = 4, - MiraOffice = 5, - MiraAdmin = 6, - MiraGreenhouse = 7, - MiraMedbay = 8, - MiraDecontamination = 9, - MiraLockerRoom = 10, - MiraLaunchpad = 11, - - // Polus - PolusSecurity = 0, - PolusElectrical = 1, - PolusO2 = 2, - PolusCommunications = 3, - PolusOffice = 4, - PolusAdmin = 5, - PolusLaboratory = 6, - PolusLava = 7, - PolusStorage = 8, - PolusRightStabilizer = 9, - PolusLeftStabilizer = 10, - PolusOutsideAdmin = 11, - } -} diff --git a/src/Impostor.Api/Net/IClient.cs b/src/Impostor.Api/Net/IClient.cs index 48efeda42..66ab9c5ef 100644 --- a/src/Impostor.Api/Net/IClient.cs +++ b/src/Impostor.Api/Net/IClient.cs @@ -51,25 +51,27 @@ public interface IClient IDictionary Items { get; } /// - /// Gets or sets the current game data of the . + /// Gets the current game data of the . /// IClientPlayer? Player { get; } + ValueTask ReportCheatAsync(CheatContext context, string message); + ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType); ValueTask HandleDisconnectAsync(string reason); /// - /// Disconnect the client with a . + /// Disconnect the client with a . /// /// /// The message to show to the player. /// /// - /// Only used when is set to . + /// Only used when is set to . /// /// - /// A representing the asynchronous operation. + /// A representing the asynchronous operation. /// ValueTask DisconnectAsync(DisconnectReason reason, string? message = null); } diff --git a/src/Impostor.Api/Net/IClientPlayer.cs b/src/Impostor.Api/Net/IClientPlayer.cs index 60702101e..f11c87375 100644 --- a/src/Impostor.Api/Net/IClientPlayer.cs +++ b/src/Impostor.Api/Net/IClientPlayer.cs @@ -6,7 +6,7 @@ namespace Impostor.Api.Net { /// - /// Represents a player in . + /// Represents a player in . /// public interface IClientPlayer { @@ -16,7 +16,7 @@ public interface IClientPlayer IClient Client { get; } /// - /// Gets the game where the belongs to. + /// Gets the game where the belongs to. /// IGame Game { get; } @@ -30,10 +30,10 @@ public interface IClientPlayer public bool IsHost { get; } /// - /// Checks if the specified is owned by . + /// Checks if the specified is owned by . /// - /// The . - /// Returns true if owned by . + /// The . + /// Returns true if owned by . bool IsOwner(IInnerNetObject netObject); ValueTask KickAsync(); diff --git a/src/Impostor.Api/Net/IConnection.cs b/src/Impostor.Api/Net/IConnection.cs index 94f9b8be3..fa998414f 100644 --- a/src/Impostor.Api/Net/IConnection.cs +++ b/src/Impostor.Api/Net/IConnection.cs @@ -2,6 +2,5 @@ { public interface IConnection { - } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/IHazelConnection.cs b/src/Impostor.Api/Net/IHazelConnection.cs index 4e6c4b3fd..c3ce51e15 100644 --- a/src/Impostor.Api/Net/IHazelConnection.cs +++ b/src/Impostor.Api/Net/IHazelConnection.cs @@ -20,7 +20,7 @@ public interface IHazelConnection bool IsConnected { get; } /// - /// Gets the client of the connection. + /// Gets or sets the client of the connection. /// IClient? Client { get; set; } @@ -28,14 +28,15 @@ public interface IHazelConnection /// Sends a message writer to the connection. /// /// The message. - /// + /// Task that must be awaited. ValueTask SendAsync(IMessageWriter writer); /// /// Disconnects the client and invokes the disconnect handler. /// /// A reason. - /// - ValueTask DisconnectAsync(string? reason); + /// A message to send with disconnect packet. + /// Task that must be awaited. + ValueTask DisconnectAsync(string? reason, IMessageWriter? writer = null); } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Inner/IGameNet.cs b/src/Impostor.Api/Net/Inner/IGameNet.cs index 933a4de68..7b068d108 100644 --- a/src/Impostor.Api/Net/Inner/IGameNet.cs +++ b/src/Impostor.Api/Net/Inner/IGameNet.cs @@ -1,4 +1,5 @@ using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Inner.Objects.ShipStatus; namespace Impostor.Api.Net.Inner { @@ -7,12 +8,12 @@ namespace Impostor.Api.Net.Inner /// public interface IGameNet { - IInnerLobbyBehaviour LobbyBehaviour { get; } + IInnerLobbyBehaviour? LobbyBehaviour { get; } - IInnerGameData GameData { get; } + IInnerGameData? GameData { get; } - IInnerVoteBanSystem VoteBan { get; } + IInnerVoteBanSystem? VoteBan { get; } - IInnerShipStatus ShipStatus { get; } + IInnerShipStatus? ShipStatus { get; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Inner/IInnerNetObject.cs b/src/Impostor.Api/Net/Inner/IInnerNetObject.cs index c17137799..db868b63c 100644 --- a/src/Impostor.Api/Net/Inner/IInnerNetObject.cs +++ b/src/Impostor.Api/Net/Inner/IInnerNetObject.cs @@ -1,9 +1,13 @@ -namespace Impostor.Api.Net.Inner +using Impostor.Api.Games; + +namespace Impostor.Api.Net.Inner { public interface IInnerNetObject { public uint NetId { get; } public int OwnerId { get; } + + public IGame Game { get; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs b/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs index 6d867e793..5ad4763c7 100644 --- a/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs +++ b/src/Impostor.Api/Net/Inner/Objects/Components/IInnerCustomNetworkTransform.cs @@ -6,7 +6,17 @@ namespace Impostor.Api.Net.Inner.Objects.Components public interface IInnerCustomNetworkTransform : IInnerNetObject { /// - /// Snaps the current to the given position . + /// 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 . /// /// The target position. /// Task that must be awaited. diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs index 9c89d0590..6d8f10672 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerMeetingHud.cs @@ -1,6 +1,6 @@ namespace Impostor.Api.Net.Inner.Objects { - public interface IInnerMeetingHud + public interface IInnerMeetingHud : IInnerNetObject { } } diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs index 04558b96e..711f878b4 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerControl.cs @@ -7,30 +7,30 @@ namespace Impostor.Api.Net.Inner.Objects public interface IInnerPlayerControl : IInnerNetObject { /// - /// Gets the assigned by the client of the host of the game. + /// Gets the assigned by the client of the host of the game. /// byte PlayerId { get; } /// - /// Gets the of the . + /// Gets the of the . /// Contains vent logic. /// IInnerPlayerPhysics Physics { get; } /// - /// Gets the of the . + /// Gets the of the . /// Contains position data about the player. /// IInnerCustomNetworkTransform NetworkTransform { get; } /// - /// Gets the of the . + /// Gets the of the . /// Contains metadata about the player. /// IInnerPlayerInfo PlayerInfo { get; } /// - /// Sets the name of the current . + /// Sets the name of the current . /// Visible to all players. /// /// A name for the player. @@ -38,55 +38,39 @@ public interface IInnerPlayerControl : IInnerNetObject ValueTask SetNameAsync(string name); /// - /// Sets the color of the current . + /// 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 . + /// 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 . + /// 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 . + /// 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); /// - /// Send a chat message as the current . + /// Send a chat message as the current . /// Visible to all players. /// /// The message to send. @@ -94,7 +78,7 @@ public interface IInnerPlayerControl : IInnerNetObject ValueTask SendChatAsync(string text); /// - /// Send a chat message as the current . + /// Send a chat message as the current . /// Visible to only the current. /// /// The message to send. @@ -106,11 +90,20 @@ public interface IInnerPlayerControl : IInnerNetObject ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null); /// - /// Sets the current to be murdered by an impostor . + /// Murder player. + /// + /// 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 MurderPlayerAsync(IInnerPlayerControl target); + + /// + /// Exile the current player. This doesn't produce a body to be reported. /// Visible to all players. /// - /// /// The Impostor who kill. /// Task that must be awaited. - ValueTask SetMurderedByAsync(IClientPlayer impostor); + ValueTask ExileAsync(); } } 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.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs index d0a816dc0..8d8b4ac69 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerVoteBanSystem.cs @@ -2,6 +2,5 @@ { public interface IInnerVoteBanSystem : IInnerNetObject { - } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs b/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs index 2b6dd86f0..f4e54b151 100644 --- a/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs +++ b/src/Impostor.Api/Net/Inner/Objects/ITaskInfo.cs @@ -1,5 +1,4 @@ using Impostor.Api.Innersloth; -using Impostor.Api.Net.Messages; namespace Impostor.Api.Net.Inner.Objects { diff --git a/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerAirshipStatus.cs b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerAirshipStatus.cs new file mode 100644 index 000000000..2711b89be --- /dev/null +++ b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerAirshipStatus.cs @@ -0,0 +1,6 @@ +namespace Impostor.Api.Net.Inner.Objects.ShipStatus +{ + public interface IInnerAirshipStatus : IInnerShipStatus + { + } +} diff --git a/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerMiraShipStatus.cs b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerMiraShipStatus.cs new file mode 100644 index 000000000..870a9456e --- /dev/null +++ b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerMiraShipStatus.cs @@ -0,0 +1,6 @@ +namespace Impostor.Api.Net.Inner.Objects.ShipStatus +{ + public interface IInnerMiraShipStatus : IInnerShipStatus + { + } +} diff --git a/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerPolusShipStatus.cs b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerPolusShipStatus.cs new file mode 100644 index 000000000..5e336bd1a --- /dev/null +++ b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerPolusShipStatus.cs @@ -0,0 +1,6 @@ +namespace Impostor.Api.Net.Inner.Objects.ShipStatus +{ + public interface IInnerPolusShipStatus : IInnerShipStatus + { + } +} diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerShipStatus.cs similarity index 55% rename from src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs rename to src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerShipStatus.cs index c0a05aeae..c00769d98 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerShipStatus.cs +++ b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerShipStatus.cs @@ -1,7 +1,6 @@ -namespace Impostor.Api.Net.Inner.Objects +namespace Impostor.Api.Net.Inner.Objects.ShipStatus { public interface IInnerShipStatus : IInnerNetObject { - } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerSkeldShipStatus.cs b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerSkeldShipStatus.cs new file mode 100644 index 000000000..e0e9df17a --- /dev/null +++ b/src/Impostor.Api/Net/Inner/Objects/ShipStatus/IInnerSkeldShipStatus.cs @@ -0,0 +1,6 @@ +namespace Impostor.Api.Net.Inner.Objects.ShipStatus +{ + public interface IInnerSkeldShipStatus : IInnerShipStatus + { + } +} diff --git a/src/Impostor.Server/Net/Inner/RpcCalls.cs b/src/Impostor.Api/Net/Inner/RpcCalls.cs similarity index 89% rename from src/Impostor.Server/Net/Inner/RpcCalls.cs rename to src/Impostor.Api/Net/Inner/RpcCalls.cs index ce4896537..44f85e7aa 100644 --- a/src/Impostor.Server/Net/Inner/RpcCalls.cs +++ b/src/Impostor.Api/Net/Inner/RpcCalls.cs @@ -1,4 +1,4 @@ -namespace Impostor.Server.Net.Inner +namespace Impostor.Api.Net.Inner { public enum RpcCalls : byte { @@ -32,6 +32,7 @@ public enum RpcCalls : byte CloseDoorsOfType = 27, RepairSystem = 28, SetTasks = 29, - UpdateGameData = 30, + ClimbLadder = 31, + UsePlatform = 32, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/LimboStates.cs b/src/Impostor.Api/Net/LimboStates.cs index 44c493e81..5d1242d4b 100644 --- a/src/Impostor.Api/Net/LimboStates.cs +++ b/src/Impostor.Api/Net/LimboStates.cs @@ -10,4 +10,4 @@ public enum LimboStates WaitingForHost = 4, All = PreSpawn | NotLimbo | WaitingForHost, } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Manager/IClientManager.cs b/src/Impostor.Api/Net/Manager/IClientManager.cs index 92bf89f0d..737e42f37 100644 --- a/src/Impostor.Api/Net/Manager/IClientManager.cs +++ b/src/Impostor.Api/Net/Manager/IClientManager.cs @@ -6,4 +6,4 @@ public interface IClientManager { IEnumerable Clients { get; } } -} \ 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/Auth/Message01Complete.cs b/src/Impostor.Api/Net/Messages/Auth/Message01Complete.cs new file mode 100644 index 000000000..2572f8c4e --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Auth/Message01Complete.cs @@ -0,0 +1,15 @@ +namespace Impostor.Api.Net.Messages.Auth +{ + public static class Message01Complete + { + public static void Serialize(IMessageWriter writer, uint nonce) + { + writer.WritePacked(nonce); + } + + public static void Deserialize(IMessageReader reader, out uint nonce) + { + nonce = reader.ReadUInt32(); + } + } +} diff --git a/src/Impostor.Api/Net/Messages/Auth/MessageHandshake.cs b/src/Impostor.Api/Net/Messages/Auth/MessageHandshake.cs new file mode 100644 index 000000000..146d0046a --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Auth/MessageHandshake.cs @@ -0,0 +1,14 @@ +using Impostor.Api.Innersloth; + +namespace Impostor.Api.Net.Messages.Auth +{ + public static class MessageHandshake + { + public static void Deserialize(IMessageReader reader, out int clientVersion, out Platforms platform, out string clientId) + { + clientVersion = reader.ReadInt32(); + platform = (Platforms)reader.ReadByte(); + clientId = reader.ReadString(); + } + } +} diff --git a/src/Impostor.Api/Net/Messages/C2S/HandshakeC2S.cs b/src/Impostor.Api/Net/Messages/C2S/HandshakeC2S.cs new file mode 100644 index 000000000..649e5eec8 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/C2S/HandshakeC2S.cs @@ -0,0 +1,12 @@ +namespace Impostor.Api.Net.Messages.C2S +{ + public static class HandshakeC2S + { + public static void Deserialize(IMessageReader reader, out int clientVersion, out string name, out uint lastNonceReceived) + { + clientVersion = reader.ReadInt32(); + name = reader.ReadString(); + lastNonceReceived = reader.ReadUInt32(); + } + } +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs index 4f5b39ce7..2a61a1832 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,20 +7,22 @@ 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(); } - public static GameOptionsData Deserialize(IMessageReader reader) + /// + /// Deserialize a packet. + /// + /// with 0. + /// The chat type selected in the client of the player. + /// Deserialized . + public static GameOptionsData Deserialize(IMessageReader reader, out QuickChatModes chatMode) { - return GameOptionsData.DeserializeCreate(reader); + var gameOptionsData = GameOptionsData.DeserializeCreate(reader); + chatMode = (QuickChatModes)reader.ReadByte(); + + return gameOptionsData; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs index f121b97ea..e7771e7c9 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message01JoinGameC2S.cs @@ -1,4 +1,5 @@ using System; +using Impostor.Api.Games; namespace Impostor.Api.Net.Messages.C2S { @@ -6,15 +7,12 @@ public static class Message01JoinGameC2S { public static void Serialize(IMessageWriter writer) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public static void Deserialize(IMessageReader reader, out int gameCode, out byte unknown) + public static void Deserialize(IMessageReader reader, out GameCode gameCode) { - var slice = reader.ReadBytes(sizeof(Int32) + sizeof(byte)).Span; - - gameCode = slice.ReadInt32(); - unknown = slice.ReadByte(); + gameCode = reader.ReadInt32(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs index 99cdcfa7f..a3bcedfcb 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message04RemovePlayerC2S.cs @@ -1,10 +1,12 @@ -namespace Impostor.Api.Net.Messages.C2S +using System; + +namespace Impostor.Api.Net.Messages.C2S { public class Message04RemovePlayerC2S { public static void Serialize(IMessageWriter writer) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public static void Deserialize(IMessageReader reader, out int playerId, out byte reason) @@ -13,4 +15,4 @@ public static void Deserialize(IMessageReader reader, out int playerId, out byte reason = reader.ReadByte(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs index 7ca5e3a61..d264294cf 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message08EndGameC2S.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Innersloth; +using System; +using Impostor.Api.Innersloth; namespace Impostor.Api.Net.Messages.C2S { @@ -6,7 +7,7 @@ public class Message08EndGameC2S { public static void Serialize(IMessageWriter writer) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public static void Deserialize(IMessageReader reader, out GameOverReason gameOverReason) @@ -15,4 +16,4 @@ public static void Deserialize(IMessageReader reader, out GameOverReason gameOve reader.ReadBoolean(); // showAd } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs index 330f3b5ba..57db66902 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message10AlterGameC2S.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Innersloth; +using System; +using Impostor.Api.Innersloth; namespace Impostor.Api.Net.Messages.C2S { @@ -6,7 +7,7 @@ public class Message10AlterGameC2S { public static void Serialize(IMessageWriter writer) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public static void Deserialize(IMessageReader reader, out AlterGameTags gameTag, out bool isPublic) @@ -17,4 +18,4 @@ public static void Deserialize(IMessageReader reader, out AlterGameTags gameTag, isPublic = slice.ReadBoolean(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs index 7c5b8b980..acafcee06 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message11KickPlayerC2S.cs @@ -1,10 +1,12 @@ -namespace Impostor.Api.Net.Messages.C2S +using System; + +namespace Impostor.Api.Net.Messages.C2S { public class Message11KickPlayerC2S { public static void Serialize(IMessageWriter writer) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public static void Deserialize(IMessageReader reader, out int playerId, out bool isBan) @@ -13,4 +15,4 @@ public static void Deserialize(IMessageReader reader, out int playerId, out bool isBan = reader.ReadBoolean(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs index 2b7e12a76..01237678d 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Innersloth; +using System; +using Impostor.Api.Innersloth; namespace Impostor.Api.Net.Messages.C2S { @@ -6,13 +7,14 @@ public class Message16GetGameListC2S { public static void Serialize(IMessageWriter writer) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public static void Deserialize(IMessageReader reader, out GameOptionsData options) + public static void Deserialize(IMessageReader reader, out GameOptionsData options, out QuickChatModes chatMode) { reader.ReadPackedInt32(); // Hardcoded 0. options = GameOptionsData.DeserializeCreate(reader); + chatMode = (QuickChatModes)reader.ReadByte(); } } -} \ 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..397ad5adb 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 { @@ -15,7 +18,7 @@ public interface IMessageReader : IDisposable byte[] Buffer { get; } /// - /// Gets the offset of our current in the entire . + /// Gets the offset of our current in the entire . /// int Offset { get; } @@ -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..fd500adcd 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 { @@ -72,7 +74,7 @@ public interface IMessageWriter : IDisposable void Write(string value); /// - /// Writes a to the message. + /// Writes a to the message. /// /// Value to write. void Write(IPAddress value); @@ -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/IMessageWriterProvider.cs b/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs index f39893930..16a48ecf8 100644 --- a/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs +++ b/src/Impostor.Api/Net/Messages/IMessageWriterProvider.cs @@ -3,14 +3,14 @@ public interface IMessageWriterProvider { /// - /// Retrieves a from the internal pool. - /// Make sure to call when you are done! + /// Retrieves a from the internal pool. + /// Make sure to call when you are done!. /// /// - /// Whether to send the message as or . + /// Whether to send the message as or . /// Reliable packets will ensure delivery while unreliable packets may be lost. /// - /// A from the pool. + /// A from the pool. IMessageWriter Get(MessageType sendOption = MessageType.Unreliable); } } diff --git a/src/Impostor.Api/Net/Messages/MessageFlags.cs b/src/Impostor.Api/Net/Messages/MessageFlags.cs index aea0c60db..2ebf475dc 100644 --- a/src/Impostor.Api/Net/Messages/MessageFlags.cs +++ b/src/Impostor.Api/Net/Messages/MessageFlags.cs @@ -19,4 +19,4 @@ public static class MessageFlags public const byte GetGameList = 9; public const byte GetGameListV2 = 16; } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/MessageType.cs b/src/Impostor.Api/Net/Messages/MessageType.cs index 16043585d..50acb5246 100644 --- a/src/Impostor.Api/Net/Messages/MessageType.cs +++ b/src/Impostor.Api/Net/Messages/MessageType.cs @@ -29,4 +29,4 @@ public enum MessageType : byte /// Reliable, } -} \ 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/Net/Messages/Rpcs/Rpc31ClimbLadder.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc31ClimbLadder.cs new file mode 100644 index 000000000..a31522167 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc31ClimbLadder.cs @@ -0,0 +1,17 @@ +namespace Impostor.Api.Net.Messages.Rpcs +{ + public static class Rpc31ClimbLadder + { + public static void Serialize(IMessageWriter writer, byte ladderId, byte lastClimbLadderSid) + { + writer.Write(ladderId); + writer.Write(lastClimbLadderSid); + } + + public static void Deserialize(IMessageReader reader, out byte ladderId, out byte lastClimbLadderSid) + { + ladderId = reader.ReadByte(); + lastClimbLadderSid = reader.ReadByte(); + } + } +} diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc32UsePlatform.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc32UsePlatform.cs new file mode 100644 index 000000000..9344be201 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc32UsePlatform.cs @@ -0,0 +1,13 @@ +namespace Impostor.Api.Net.Messages.Rpcs +{ + public static class Rpc32UsePlatform + { + public static void Serialize(IMessageWriter writer) + { + } + + public static void Deserialize(IMessageReader reader) + { + } + } +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs index 8402d1007..8fef2251e 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs @@ -17,4 +17,4 @@ public static GameOptionsData Deserialize(IMessageReader reader) throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs index c45520105..74fdc4055 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs @@ -44,7 +44,7 @@ public static void SerializeError(IMessageWriter writer, bool clear, DisconnectR public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs index 77b447d05..bcdca0486 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message04RemovePlayerS2C.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Innersloth; +using System; +using Impostor.Api.Innersloth; namespace Impostor.Api.Net.Messages.S2C { @@ -23,7 +24,7 @@ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, in public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs index da6eb40ca..857fea1b8 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs @@ -1,4 +1,6 @@ -namespace Impostor.Api.Net.Messages.S2C +using System; + +namespace Impostor.Api.Net.Messages.S2C { public static class Message07JoinedGameS2C { @@ -25,7 +27,7 @@ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, in public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs index fa155dfab..40c949d3a 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message10AlterGameS2C.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Innersloth; +using System; +using Impostor.Api.Innersloth; namespace Impostor.Api.Net.Messages.S2C { @@ -20,7 +21,7 @@ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, bo public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs index 1e2b6efc1..7ffdb7b5e 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message11KickPlayerS2C.cs @@ -1,4 +1,6 @@ -namespace Impostor.Api.Net.Messages.S2C +using System; + +namespace Impostor.Api.Net.Messages.S2C { public class Message11KickPlayerS2C { @@ -18,7 +20,7 @@ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, in public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs index 5964b1c55..8cf9046f9 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message12WaitForHostS2C.cs @@ -1,4 +1,6 @@ -namespace Impostor.Api.Net.Messages.S2C +using System; + +namespace Impostor.Api.Net.Messages.S2C { public class Message12WaitForHostS2C { @@ -17,7 +19,7 @@ public static void Serialize(IMessageWriter writer, bool clear, int gameCode, in public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs index 4b93b0eef..0576c71d5 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message13RedirectS2C.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; namespace Impostor.Api.Net.Messages.S2C { @@ -19,7 +20,7 @@ public static void Serialize(IMessageWriter writer, bool clear, IPEndPoint ipEnd public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs index 93386d764..a09259ba6 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message16GetGameListS2C.cs @@ -1,21 +1,15 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Impostor.Api.Games; namespace Impostor.Api.Net.Messages.S2C { public class Message16GetGameListS2C { - public static void Serialize(IMessageWriter writer, int skeldGameCount, int miraHqGameCount, int polusGameCount, IEnumerable games) + public static void Serialize(IMessageWriter writer, IEnumerable games) { writer.StartMessage(MessageFlags.GetGameListV2); - // Count - writer.StartMessage(1); - writer.Write(skeldGameCount); // The Skeld - writer.Write(miraHqGameCount); // Mira HQ - writer.Write(polusGameCount); // Polus - writer.EndMessage(); - // Listing writer.StartMessage(0); @@ -25,10 +19,10 @@ public static void Serialize(IMessageWriter writer, int skeldGameCount, int mira writer.Write(game.PublicIp.Address); writer.Write((ushort)game.PublicIp.Port); writer.Write(game.Code); - writer.Write(game.Host.Client.Name); + writer.Write(game.DisplayName ?? game.Host?.Client.Name ?? string.Empty); writer.Write((byte)game.PlayerCount); writer.WritePacked(1); // TODO: What does Age do? - writer.Write((byte)game.Options.MapId); + writer.Write((byte)game.Options.Map); writer.Write((byte)game.Options.NumImpostors); writer.Write((byte)game.Options.MaxPlayers); writer.EndMessage(); @@ -40,7 +34,7 @@ public static void Serialize(IMessageWriter writer, int skeldGameCount, int mira public static void Deserialize(IMessageReader reader) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Net/Messages/S2C/MessageDisconnect.cs b/src/Impostor.Api/Net/Messages/S2C/MessageDisconnect.cs new file mode 100644 index 000000000..8dd5c7f94 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/S2C/MessageDisconnect.cs @@ -0,0 +1,53 @@ +using System; +using Impostor.Api.Innersloth; + +namespace Impostor.Api.Net.Messages.S2C +{ + public class MessageDisconnect + { + public static void Serialize(IMessageWriter writer, bool hasReason, DisconnectReason? reason, string? message) + { + writer.Write(hasReason); + + if (hasReason) + { + if (reason == null) + { + throw new ArgumentNullException(nameof(reason)); + } + + writer.StartMessage(0); + writer.Write((byte)reason); + + if (reason == DisconnectReason.Custom) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + writer.Write(message); + } + + writer.EndMessage(); + } + } + + public static void Deserialize(IMessageReader reader, out bool hasReason, out DisconnectReason? reason, out string? message) + { + hasReason = reader.ReadBoolean(); + + if (hasReason) + { + var inner = reader.ReadMessage(); + reason = (DisconnectReason)inner.ReadByte(); + message = reason == DisconnectReason.Custom ? inner.ReadString() : null; + } + else + { + reason = null; + message = null; + } + } + } +} diff --git a/src/Impostor.Api/Plugins/IPlugin.cs b/src/Impostor.Api/Plugins/IPlugin.cs index fd0f38f07..9f1efcf58 100644 --- a/src/Impostor.Api/Plugins/IPlugin.cs +++ b/src/Impostor.Api/Plugins/IPlugin.cs @@ -11,4 +11,4 @@ public interface IPlugin : IEventListener ValueTask ReloadAsync(); } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Plugins/IPluginStartup.cs b/src/Impostor.Api/Plugins/IPluginStartup.cs index aa6a35f82..e9d858573 100644 --- a/src/Impostor.Api/Plugins/IPluginStartup.cs +++ b/src/Impostor.Api/Plugins/IPluginStartup.cs @@ -9,4 +9,4 @@ public interface IPluginStartup void ConfigureServices(IServiceCollection services); } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs b/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs index b31bd4789..e2d6b0249 100644 --- a/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs +++ b/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs @@ -21,4 +21,4 @@ public ImpostorPluginAttribute(string package, string name, string author, strin public string Version { get; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Plugins/PluginBase.cs b/src/Impostor.Api/Plugins/PluginBase.cs index 03843638f..9d6f2b4f3 100644 --- a/src/Impostor.Api/Plugins/PluginBase.cs +++ b/src/Impostor.Api/Plugins/PluginBase.cs @@ -19,4 +19,4 @@ public virtual ValueTask ReloadAsync() return default; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/ProjectRules.ruleset b/src/Impostor.Api/ProjectRules.ruleset deleted file mode 100644 index 4ba23c2fc..000000000 --- a/src/Impostor.Api/ProjectRules.ruleset +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Impostor.Api/Properties/AssemblyInfo.cs b/src/Impostor.Api/Properties/AssemblyInfo.cs index c02e44aff..30143b8fa 100644 --- a/src/Impostor.Api/Properties/AssemblyInfo.cs +++ b/src/Impostor.Api/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Impostor.Server")] \ No newline at end of file +[assembly: InternalsVisibleTo("Impostor.Server")] diff --git a/src/Impostor.Api/Unity/Mathf.cs b/src/Impostor.Api/Unity/Mathf.cs index 4b034176d..5ab93c743 100644 --- a/src/Impostor.Api/Unity/Mathf.cs +++ b/src/Impostor.Api/Unity/Mathf.cs @@ -3,13 +3,13 @@ public static class Mathf { /// - /// Clamps the given value between the given minimum float and maximum float values. Returns the given value if it is within the min and max range. + /// Clamps the given value between the given minimum float and maximum float values. Returns the given value if it is within the min and max range. /// /// The floating point value to restrict inside the range defined by the min and max values. /// The minimum floating point value to compare against. /// The maximum floating point value to compare against. /// - /// The float result between the min and max values. + /// The float result between the min and max values. /// public static float Clamp(float value, float min, float max) { @@ -26,7 +26,7 @@ public static float Clamp(float value, float min, float max) } /// - /// Clamps value between 0 and 1 and returns value. + /// Clamps value between 0 and 1 and returns value. /// /// Value. /// Clamped value. @@ -41,14 +41,21 @@ public static float Clamp01(float value) } /// - /// Linearly interpolates between a and b by t. + /// Linearly interpolates between a and b by t. /// /// The start value. /// The end value. /// The interpolation value between the two floats. /// - /// The interpolated float result between the two float values. + /// 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) + { + const float range = 50f; + + return Clamp((t - -range) / (range - -range), 0f, 1f); + } } -} \ No newline at end of file +} diff --git a/src/Impostor.Api/Unity/RuntimePlatform.cs b/src/Impostor.Api/Unity/RuntimePlatform.cs new file mode 100644 index 000000000..6951ca2a5 --- /dev/null +++ b/src/Impostor.Api/Unity/RuntimePlatform.cs @@ -0,0 +1,42 @@ +namespace Impostor.Api.Unity +{ + public enum RuntimePlatform + { + OSXEditor = 0, + OSXPlayer = 1, + WindowsPlayer = 2, + OSXWebPlayer = 3, + OSXDashboardPlayer = 4, + WindowsWebPlayer = 5, + WindowsEditor = 7, + IPhonePlayer = 8, + PS3 = 9, + XBOX360 = 10, + Android = 11, + NaCl = 12, + LinuxPlayer = 13, + FlashPlayer = 15, + LinuxEditor = 16, + WebGLPlayer = 17, + MetroPlayerX86 = 18, + WSAPlayerX86 = 18, + MetroPlayerX64 = 19, + WSAPlayerX64 = 19, + MetroPlayerARM = 20, + WSAPlayerARM = 20, + WP8Player = 21, + BlackBerryPlayer = 22, + TizenPlayer = 23, + PSP2 = 24, + PS4 = 25, + PSM = 26, + XboxOne = 27, + SamsungTVPlayer = 28, + WiiU = 30, + TvOs = 31, + Switch = 32, + Lumin = 33, + Stadia = 34, + CloudRendering = 35, + } +} diff --git a/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs b/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs index 2aba724eb..7788f0e8a 100644 --- a/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs +++ b/src/Impostor.Benchmarks/Data/MessageReader_Bytes.cs @@ -41,7 +41,7 @@ public MessageReader_Bytes ReadMessage() public bool ReadBoolean() { - byte val = FastByte(); + var val = FastByte(); return val != 0; } @@ -88,7 +88,7 @@ public unsafe float ReadSingle() float output = 0; fixed (byte* bufPtr = &this.Buffer[Position]) { - byte* outPtr = (byte*)&output; + var outPtr = (byte*)&output; *outPtr = *bufPtr; *(outPtr + 1) = *(bufPtr + 1); @@ -134,13 +134,13 @@ public int ReadPackedInt32() public uint ReadPackedUInt32() { - bool readMore = true; - int shift = 0; + var readMore = true; + var shift = 0; uint output = 0; while (readMore) { - byte b = FastByte(); + var b = FastByte(); if (b >= 0x80) { readMore = true; diff --git a/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs b/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs index 110333626..3c88ffb7c 100644 --- a/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs +++ b/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled.cs @@ -65,7 +65,7 @@ public MessageReader_Bytes_Pooled ReadMessage() public bool ReadBoolean() { - byte val = FastByte(); + var val = FastByte(); return val != 0; } @@ -112,7 +112,7 @@ public unsafe float ReadSingle() float output = 0; fixed (byte* bufPtr = &this.Buffer[Position]) { - byte* outPtr = (byte*)&output; + var outPtr = (byte*)&output; *outPtr = *bufPtr; *(outPtr + 1) = *(bufPtr + 1); @@ -158,13 +158,13 @@ public int ReadPackedInt32() public uint ReadPackedUInt32() { - bool readMore = true; - int shift = 0; + var readMore = true; + var shift = 0; uint output = 0; while (readMore) { - byte b = FastByte(); + var b = FastByte(); if (b >= 0x80) { readMore = true; diff --git a/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs b/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs index 006684797..cb5d18dc6 100644 --- a/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs +++ b/src/Impostor.Benchmarks/Data/MessageReader_Bytes_Pooled_Improved.cs @@ -1,10 +1,6 @@ using System; using System.Buffers.Binary; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; using System.Runtime.CompilerServices; -using System.Text; using Microsoft.Extensions.ObjectPool; namespace Impostor.Benchmarks.Data diff --git a/src/Impostor.Benchmarks/Data/MessageWriter.cs b/src/Impostor.Benchmarks/Data/MessageWriter.cs index 94ff4418f..b55b7970e 100644 --- a/src/Impostor.Benchmarks/Data/MessageWriter.cs +++ b/src/Impostor.Benchmarks/Data/MessageWriter.cs @@ -9,8 +9,6 @@ namespace Impostor.Benchmarks.Data { public class MessageWriter { - private static int BufferSize = 64000; - public MessageType SendOption { get; private set; } private Stack messageStarts = new Stack(); @@ -34,7 +32,7 @@ public byte[] ToByteArray(bool includeHeader) { if (includeHeader) { - byte[] output = new byte[this.Length]; + var output = new byte[this.Length]; System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); return output; } @@ -43,17 +41,17 @@ public byte[] ToByteArray(bool includeHeader) switch (this.SendOption) { case MessageType.Reliable: - { - byte[] output = new byte[this.Length - 3]; - System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3); - return output; - } + { + var output = new byte[this.Length - 3]; + System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3); + return output; + } case MessageType.Unreliable: - { - byte[] output = new byte[this.Length - 1]; - System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1); - return output; - } + { + var output = new byte[this.Length - 1]; + System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1); + return output; + } default: throw new ArgumentOutOfRangeException(); } @@ -89,7 +87,7 @@ public void StartMessage(byte typeFlag) public void EndMessage() { var lastMessageStart = messageStarts.Pop(); - ushort length = (ushort)(this.Position - lastMessageStart - 3); // Minus length and type byte + var length = (ushort)(this.Position - lastMessageStart - 3); // Minus length and type byte this.Buffer[lastMessageStart] = (byte)length; this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); } @@ -175,7 +173,7 @@ public unsafe void Write(float value) { fixed (byte* ptr = &this.Buffer[this.Position]) { - byte* valuePtr = (byte*)&value; + var valuePtr = (byte*)&value; *ptr = *valuePtr; *(ptr + 1) = *(valuePtr + 1); @@ -262,7 +260,7 @@ public void WritePacked(uint value) { do { - byte b = (byte)(value & 0xFF); + var b = (byte)(value & 0xFF); if (value >= 0x80) { b |= 0x80; @@ -277,7 +275,7 @@ public void WritePacked(uint value) public void Write(MessageWriter msg, bool includeHeader) { - int offset = 0; + var offset = 0; if (!includeHeader) { switch (msg.SendOption) @@ -300,8 +298,8 @@ public unsafe static bool IsLittleEndian() byte b; unsafe { - int i = 1; - byte* bp = (byte*)&i; + var i = 1; + var bp = (byte*)&i; b = *bp; } diff --git a/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs b/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs index 5636dbed0..3a7edecb8 100644 --- a/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs +++ b/src/Impostor.Benchmarks/Data/Span/MessageReader_Span.cs @@ -1,6 +1,5 @@ using System; using System.Buffers.Binary; -using Impostor.Hazel; namespace Impostor.Benchmarks.Data.Span { diff --git a/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs b/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs index abf16428b..dbaf2279d 100644 --- a/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs +++ b/src/Impostor.Benchmarks/Tests/MessageReaderBenchmark.cs @@ -4,10 +4,8 @@ using Impostor.Benchmarks.Data; using Impostor.Benchmarks.Data.Pool; using Impostor.Benchmarks.Extensions; -using Impostor.Hazel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.ObjectPool; -using MessageWriter = Impostor.Benchmarks.Data.MessageWriter; namespace Impostor.Benchmarks.Tests { @@ -24,7 +22,7 @@ public void Setup() message.StartMessage(1); message.Write((ushort)3100); message.Write((byte)100); - message.Write((int) int.MaxValue); + message.Write((int)int.MaxValue); message.WritePacked(int.MaxValue); message.EndMessage(); diff --git a/src/Impostor.Client.App/Impostor.Client.App.csproj b/src/Impostor.Client.App/Impostor.Client.App.csproj index 886e26e80..3e5adc84f 100644 --- a/src/Impostor.Client.App/Impostor.Client.App.csproj +++ b/src/Impostor.Client.App/Impostor.Client.App.csproj @@ -1,16 +1,16 @@ - - Exe - net5.0 - + + Exe + net5.0 + - - - + + + - - - + + + diff --git a/src/Impostor.Client.App/Program.cs b/src/Impostor.Client.App/Program.cs index aa8866ade..ba039a572 100644 --- a/src/Impostor.Client.App/Program.cs +++ b/src/Impostor.Client.App/Program.cs @@ -1,5 +1,4 @@ -using System; -using System.Net; +using System.Net; using System.Threading; using System.Threading.Tasks; using Impostor.Api.Innersloth; @@ -31,7 +30,7 @@ private static async Task Main(string[] args) Message00HostGameC2S.Serialize(writeGameCreate, new GameOptionsData { MaxPlayers = 4, - NumImpostors = 2 + NumImpostors = 2, }); // TODO: ObjectPool for MessageReaders diff --git a/src/Impostor.Client/Impostor.Client.csproj b/src/Impostor.Client/Impostor.Client.csproj index 28b6ed3cd..75e19aa8a 100644 --- a/src/Impostor.Client/Impostor.Client.csproj +++ b/src/Impostor.Client/Impostor.Client.csproj @@ -1,12 +1,12 @@ - - net5.0 - + + net5.0 + - - - - + + + + diff --git a/src/Impostor.Hazel b/src/Impostor.Hazel new file mode 160000 index 000000000..8de027461 --- /dev/null +++ b/src/Impostor.Hazel @@ -0,0 +1 @@ +Subproject commit 8de027461e59af67fb9dc0d9b925a88ad4a01d26 diff --git a/src/Impostor.Hazel/Connection.cs b/src/Impostor.Hazel/Connection.cs deleted file mode 100644 index dec8cfecb..000000000 --- a/src/Impostor.Hazel/Connection.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; -using Serilog; - -namespace Impostor.Hazel -{ - /// - /// Base class for all connections. - /// - /// - /// - /// Connection is the base class for all connections that Hazel can make. It provides common functionality and a - /// standard interface to allow connections to be swapped easily. - /// - /// - /// Any class inheriting from Connection should provide the 3 standard guarantees that Hazel provides: - /// - /// - /// Thread Safe - /// - /// - /// Connection Orientated - /// - /// - /// Packet/Message Based - /// - /// - /// - /// - /// - public abstract class Connection : IDisposable - { - private static readonly ILogger Logger = Log.ForContext(); - - /// - /// Called when a message has been received. - /// - /// - /// - /// DataReceived is invoked everytime a message is received from the end point of this connection, the message - /// that was received can be found in the alongside other information from the - /// event. - /// - /// - /// - /// - /// - /// - public Func DataReceived; - - public int TestLagMs = -1; - public int TestDropRate = 0; - protected int testDropCount = 0; - - /// - /// Called when the end point disconnects or an error occurs. - /// - /// - /// - /// Disconnected is invoked when the connection is closed due to an exception occuring or because the remote - /// end point disconnected. If it was invoked due to an exception occuring then the exception is available - /// in the passed with the event. - /// - /// - /// - /// - /// - /// - public Func Disconnected; - - /// - /// The remote end point of this Connection. - /// - /// - /// This is the end point that this connection is connected to (i.e. the other device). This returns an abstract - /// which can then be cast to an appropriate end point depending on the - /// connection type. - /// - public IPEndPoint EndPoint { get; protected set; } - - public IPMode IPMode { get; protected set; } - - /// - /// The traffic statistics about this Connection. - /// - /// - /// Contains statistics about the number of messages and bytes sent and received by this connection. - /// - public ConnectionStatistics Statistics { get; protected set; } - - /// - /// The state of this connection. - /// - /// - /// All implementers should be aware that when this is set to ConnectionState.Connected it will - /// release all threads that are blocked on . - /// - public ConnectionState State - { - get - { - return this._state; - } - - protected set - { - this._state = value; - this.SetState(value); - } - } - - protected ConnectionState _state; - protected virtual void SetState(ConnectionState state) { } - - /// - /// Constructor that initializes the ConnecitonStatistics object. - /// - /// - /// This constructor initialises with empty statistics and sets to - /// . - /// - protected Connection() - { - this.Statistics = new ConnectionStatistics(); - this.State = ConnectionState.NotConnected; - } - - /// - /// Sends a number of bytes to the end point of the connection using the specified . - /// - /// The message to send. - /// - /// - /// - /// The messageType parameter is only a request to use those options and the actual method used to send the - /// data is up to the implementation. There are circumstances where this parameter may be ignored but in - /// general any implementer should aim to always follow the user's request. - /// - /// - public abstract ValueTask SendAsync(IMessageWriter msg); - - /// - /// Sends a number of bytes to the end point of the connection using the specified . - /// - /// The bytes of the message to send. - /// The option specifying how the message should be sent. - /// - /// - /// - /// The messageType parameter is only a request to use those options and the actual method used to send the - /// data is up to the implementation. There are circumstances where this parameter may be ignored but in - /// general any implementer should aim to always follow the user's request. - /// - /// - public abstract ValueTask SendBytes(byte[] bytes, MessageType messageType = MessageType.Unreliable); - - /// - /// Connects the connection to a server and begins listening. - /// This method does not block. - /// - /// The bytes of data to send in the handshake. - public abstract ValueTask ConnectAsync(byte[] bytes = null); - - /// - /// Invokes the DataReceived event. - /// - /// The bytes received. - /// The the message was received with. - /// - /// Invokes the event on this connection to alert subscribers a new message has been - /// received. The bytes and the send option that the message was sent with should be passed in to give to the - /// subscribers. - /// - protected async ValueTask InvokeDataReceived(IMessageReader msg, MessageType messageType) - { - // Make a copy to avoid race condition between null check and invocation - var handler = DataReceived; - if (handler != null) - { - try - { - await handler(new DataReceivedEventArgs(this, msg, messageType)); - } - catch (Exception e) - { - Logger.Error(e, "Invoking data received failed"); - await Disconnect("Invoking data received failed"); - } - } - } - - /// - /// Invokes the Disconnected event. - /// - /// The exception, if any, that occurred to cause this. - /// Extra disconnect data - /// - /// Invokes the event to alert subscribres this connection has been disconnected either - /// by the end point or because an error occurred. If an error occurred the error should be passed in in order to - /// pass to the subscribers, otherwise null can be passed in. - /// - protected async ValueTask InvokeDisconnected(string e, IMessageReader reader) - { - // Make a copy to avoid race condition between null check and invocation - var handler = Disconnected; - if (handler != null) - { - try - { - await handler(new DisconnectedEventArgs(e, reader)); - } - catch (Exception ex) - { - Logger.Error(ex, "Error in InvokeDisconnected"); - } - } - } - - /// - /// For times when you want to force the disconnect handler to fire as well as close it. - /// If you only want to close it, just use Dispose. - /// - public abstract ValueTask Disconnect(string reason, MessageWriter writer = null); - - /// - /// Disposes of this NetworkConnection. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of this NetworkConnection. - /// - /// Are we currently disposing? - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.DataReceived = null; - this.Disconnected = null; - } - } - } -} diff --git a/src/Impostor.Hazel/ConnectionListener.cs b/src/Impostor.Hazel/ConnectionListener.cs deleted file mode 100644 index 116f657e4..000000000 --- a/src/Impostor.Hazel/ConnectionListener.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; -using Serilog; - -namespace Impostor.Hazel -{ - /// - /// Base class for all connection listeners. - /// - /// - /// - /// ConnectionListeners are server side objects that listen for clients and create matching server side connections - /// for each client in a similar way to TCP does. These connections should be ready for communication immediately. - /// - /// - /// Each time a client connects the event will be invoked to alert all subscribers to - /// the new connection. A disconnected event is then present on the that is passed to the - /// subscribers. - /// - /// - /// - public abstract class ConnectionListener : IAsyncDisposable - { - private static readonly ILogger Logger = Log.ForContext(); - - /// - /// Invoked when a new client connects. - /// - /// - /// - /// NewConnection is invoked each time a client connects to the listener. The - /// contains the new for communication with this - /// client. - /// - /// - /// Hazel may or may not store connections so it is your responsibility to keep track and properly Dispose of - /// connections to your server. - /// - /// - /// - /// - /// - /// - public Func NewConnection; - - /// - /// Makes this connection listener begin listening for connections. - /// - /// - /// - /// This instructs the listener to begin listening for new clients connecting to the server. When a new client - /// connects the event will be invoked containing the connection to the new client. - /// - /// - /// To stop listening you should call . - /// - /// - /// - /// - /// - public abstract Task StartAsync(); - - /// - /// Invokes the NewConnection event with the supplied connection. - /// - /// The user sent bytes that were received as part of the handshake. - /// The connection to pass in the arguments. - /// - /// Implementers should call this to invoke the event before data is received so that - /// subscribers do not miss any data that may have been sent immediately after connecting. - /// - internal async Task InvokeNewConnection(IMessageReader msg, Connection connection) - { - // Make a copy to avoid race condition between null check and invocation - var handler = NewConnection; - if (handler != null) - { - try - { - await handler(new NewConnectionEventArgs(msg, connection)); - } - catch (Exception e) - { - Logger.Error(e, "Accepting connection failed"); - await connection.Disconnect("Accepting connection failed"); - } - } - } - - /// - /// Call to dispose of the connection listener. - /// - public virtual ValueTask DisposeAsync() - { - this.NewConnection = null; - return ValueTask.CompletedTask; - } - } -} diff --git a/src/Impostor.Hazel/ConnectionState.cs b/src/Impostor.Hazel/ConnectionState.cs deleted file mode 100644 index 5dd7c6a07..000000000 --- a/src/Impostor.Hazel/ConnectionState.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Impostor.Hazel -{ - /// - /// Represents the state a is currently in. - /// - public enum ConnectionState - { - /// - /// The Connection has either not been established yet or has been disconnected. - /// - NotConnected, - - /// - /// The Connection is currently connecting to an endpoint. - /// - Connecting, - - /// - /// The Connection is connected and data can be transfered. - /// - Connected, - } -} diff --git a/src/Impostor.Hazel/ConnectionStatistics.cs b/src/Impostor.Hazel/ConnectionStatistics.cs deleted file mode 100644 index 48026208e..000000000 --- a/src/Impostor.Hazel/ConnectionStatistics.cs +++ /dev/null @@ -1,566 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Threading; - -[assembly: InternalsVisibleTo("Hazel.Tests")] -namespace Impostor.Hazel -{ - /// - /// Holds statistics about the traffic through a . - /// - /// - public class ConnectionStatistics - { - private const int ExpectedMTU = 1200; - - /// - /// The total number of messages sent. - /// - public int MessagesSent - { - get - { - return UnreliableMessagesSent + ReliableMessagesSent + FragmentedMessagesSent + AcknowledgementMessagesSent + HelloMessagesSent; - } - } - - /// - /// The number of messages sent larger than 576 bytes. This is smaller than most default MTUs. - /// - /// - /// This is the number of unreliable messages that were sent from the , incremented - /// each time that LogUnreliableSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - public int FragmentableMessagesSent - { - get - { - return fragmentableMessagesSent; - } - } - - /// - /// The number of messages sent larger than 576 bytes. - /// - int fragmentableMessagesSent; - - /// - /// The number of unreliable messages sent. - /// - /// - /// This is the number of unreliable messages that were sent from the , incremented - /// each time that LogUnreliableSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - public int UnreliableMessagesSent - { - get - { - return unreliableMessagesSent; - } - } - - /// - /// The number of unreliable messages sent. - /// - int unreliableMessagesSent; - - /// - /// The number of reliable messages sent. - /// - /// - /// This is the number of reliable messages that were sent from the , incremented - /// each time that LogReliableSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - public int ReliableMessagesSent - { - get - { - return reliableMessagesSent; - } - } - - /// - /// The number of unreliable messages sent. - /// - int reliableMessagesSent; - - /// - /// The number of fragmented messages sent. - /// - /// - /// This is the number of fragmented messages that were sent from the , incremented - /// each time that LogFragmentedSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - public int FragmentedMessagesSent - { - get - { - return fragmentedMessagesSent; - } - } - - /// - /// The number of fragmented messages sent. - /// - int fragmentedMessagesSent; - - /// - /// The number of acknowledgement messages sent. - /// - /// - /// This is the number of acknowledgements that were sent from the , incremented - /// each time that LogAcknowledgementSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - public int AcknowledgementMessagesSent - { - get - { - return acknowledgementMessagesSent; - } - } - - /// - /// The number of acknowledgement messages sent. - /// - int acknowledgementMessagesSent; - - /// - /// The number of hello messages sent. - /// - /// - /// This is the number of hello messages that were sent from the , incremented - /// each time that LogHelloSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - public int HelloMessagesSent - { - get - { - return helloMessagesSent; - } - } - - /// - /// The number of hello messages sent. - /// - int helloMessagesSent; - - /// - /// The number of bytes of data sent. - /// - /// - /// - /// This is the number of bytes of data (i.e. user bytes) that were sent from the , - /// accumulated each time that LogSend is called by the Connection. Messages that caused an error are not - /// counted and messages are only counted once all other operations in the send are complete. - /// - /// - /// For the number of bytes including protocol bytes see . - /// - /// - public long DataBytesSent - { - get - { - return Interlocked.Read(ref dataBytesSent); - } - } - - /// - /// The number of bytes of data sent. - /// - long dataBytesSent; - - /// - /// The number of bytes sent in total. - /// - /// - /// - /// This is the total number of bytes (the data bytes plus protocol bytes) that were sent from the - /// , accumulated each time that LogSend is called by the Connection. Messages that - /// caused an error are not counted and messages are only counted once all other operations in the send are - /// complete. - /// - /// - /// For the number of data bytes excluding protocol bytes see . - /// - /// - public long TotalBytesSent - { - get - { - return Interlocked.Read(ref totalBytesSent); - } - } - - /// - /// The number of bytes sent in total. - /// - long totalBytesSent; - - /// - /// The total number of messages received. - /// - public int MessagesReceived - { - get - { - return UnreliableMessagesReceived + ReliableMessagesReceived + FragmentedMessagesReceived + AcknowledgementMessagesReceived + helloMessagesReceived; - } - } - - /// - /// The number of unreliable messages received. - /// - /// - /// This is the number of unreliable messages that were received by the , incremented - /// each time that LogUnreliableReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int UnreliableMessagesReceived - { - get - { - return unreliableMessagesReceived; - } - } - - /// - /// The number of unreliable messages received. - /// - int unreliableMessagesReceived; - - /// - /// The number of reliable messages received. - /// - /// - /// This is the number of reliable messages that were received by the , incremented - /// each time that LogReliableReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int ReliableMessagesReceived - { - get - { - return reliableMessagesReceived; - } - } - - /// - /// The number of reliable messages received. - /// - int reliableMessagesReceived; - - /// - /// The number of fragmented messages received. - /// - /// - /// This is the number of fragmented messages that were received by the , incremented - /// each time that LogFragmentedReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int FragmentedMessagesReceived - { - get - { - return fragmentedMessagesReceived; - } - } - - /// - /// The number of fragmented messages received. - /// - int fragmentedMessagesReceived; - - /// - /// The number of acknowledgement messages received. - /// - /// - /// This is the number of acknowledgement messages that were received by the , incremented - /// each time that LogAcknowledgemntReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int AcknowledgementMessagesReceived - { - get - { - return acknowledgementMessagesReceived; - } - } - - /// - /// The number of acknowledgement messages received. - /// - int acknowledgementMessagesReceived; - - /// - /// The number of ping messages received. - /// - /// - /// This is the number of hello messages that were received by the , incremented - /// each time that LogHelloReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int PingMessagesReceived - { - get - { - return pingMessagesReceived; - } - } - - /// - /// The number of hello messages received. - /// - int pingMessagesReceived; - - /// - /// The number of hello messages received. - /// - /// - /// This is the number of hello messages that were received by the , incremented - /// each time that LogHelloReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int HelloMessagesReceived - { - get - { - return helloMessagesReceived; - } - } - - /// - /// The number of hello messages received. - /// - int helloMessagesReceived; - - /// - /// The number of bytes of data received. - /// - /// - /// - /// This is the number of bytes of data (i.e. user bytes) that were received by the , - /// accumulated each time that LogReceive is called by the Connection. Messages are counted before the receive - /// event is invoked. - /// - /// - /// For the number of bytes including protocol bytes see . - /// - /// - public long DataBytesReceived - { - get - { - return Interlocked.Read(ref dataBytesReceived); - } - } - - /// - /// The number of bytes of data received. - /// - long dataBytesReceived; - - /// - /// The number of bytes received in total. - /// - /// - /// - /// This is the total number of bytes (the data bytes plus protocol bytes) that were received by the - /// , accumulated each time that LogReceive is called by the Connection. Messages are - /// counted before the receive event is invoked. - /// - /// - /// For the number of data bytes excluding protocol bytes see . - /// - /// - public long TotalBytesReceived - { - get - { - return Interlocked.Read(ref totalBytesReceived); - } - } - - /// - /// The number of bytes received in total. - /// - long totalBytesReceived; - - public int MessagesResent { get { return messagesResent; } } - int messagesResent; - - /// - /// Logs the sending of an unreliable data packet in the statistics. - /// - /// The number of bytes of data sent. - /// The total number of bytes sent. - /// - /// This should be called after the data has been sent and should only be called for data that is sent sucessfully. - /// - internal void LogUnreliableSend(int dataLength, int totalLength) - { - Interlocked.Increment(ref unreliableMessagesSent); - Interlocked.Add(ref dataBytesSent, dataLength); - Interlocked.Add(ref totalBytesSent, totalLength); - - if (totalLength > ExpectedMTU) - { - Interlocked.Increment(ref fragmentableMessagesSent); - } - } - - /// - /// Logs the sending of a reliable data packet in the statistics. - /// - /// The number of bytes of data sent. - /// The total number of bytes sent. - /// - /// This should be called after the data has been sent and should only be called for data that is sent sucessfully. - /// - internal void LogReliableSend(int dataLength, int totalLength) - { - Interlocked.Increment(ref reliableMessagesSent); - Interlocked.Add(ref dataBytesSent, dataLength); - Interlocked.Add(ref totalBytesSent, totalLength); - - if (totalLength > ExpectedMTU) - { - Interlocked.Increment(ref fragmentableMessagesSent); - } - } - - /// - /// Logs the sending of a fragmented data packet in the statistics. - /// - /// The number of bytes of data sent. - /// The total number of bytes sent. - /// - /// This should be called after the data has been sent and should only be called for data that is sent sucessfully. - /// - internal void LogFragmentedSend(int dataLength, int totalLength) - { - Interlocked.Increment(ref fragmentedMessagesSent); - Interlocked.Add(ref dataBytesSent, dataLength); - Interlocked.Add(ref totalBytesSent, totalLength); - - if (totalLength > ExpectedMTU) - { - Interlocked.Increment(ref fragmentableMessagesSent); - } - } - - /// - /// Logs the sending of a acknowledgement data packet in the statistics. - /// - /// The total number of bytes sent. - /// - /// This should be called after the data has been sent and should only be called for data that is sent sucessfully. - /// - internal void LogAcknowledgementSend(int totalLength) - { - Interlocked.Increment(ref acknowledgementMessagesSent); - Interlocked.Add(ref totalBytesSent, totalLength); - } - - /// - /// Logs the sending of a hellp data packet in the statistics. - /// - /// The total number of bytes sent. - /// - /// This should be called after the data has been sent and should only be called for data that is sent sucessfully. - /// - internal void LogHelloSend(int totalLength) - { - Interlocked.Increment(ref helloMessagesSent); - Interlocked.Add(ref totalBytesSent, totalLength); - } - - /// - /// Logs the receiving of an unreliable data packet in the statistics. - /// - /// The number of bytes of data received. - /// The total number of bytes received. - /// - /// This should be called before the received event is invoked so it is up to date for subscribers to that event. - /// - internal void LogUnreliableReceive(int dataLength, int totalLength) - { - Interlocked.Increment(ref unreliableMessagesReceived); - Interlocked.Add(ref dataBytesReceived, dataLength); - Interlocked.Add(ref totalBytesReceived, totalLength); - } - - /// - /// Logs the receiving of a reliable data packet in the statistics. - /// - /// The number of bytes of data received. - /// The total number of bytes received. - /// - /// This should be called before the received event is invoked so it is up to date for subscribers to that event. - /// - internal void LogReliableReceive(int dataLength, int totalLength) - { - Interlocked.Increment(ref reliableMessagesReceived); - Interlocked.Add(ref dataBytesReceived, dataLength); - Interlocked.Add(ref totalBytesReceived, totalLength); - } - - /// - /// Logs the receiving of a fragmented data packet in the statistics. - /// - /// The number of bytes of data received. - /// The total number of bytes received. - /// - /// This should be called before the received event is invoked so it is up to date for subscribers to that event. - /// - internal void LogFragmentedReceive(int dataLength, int totalLength) - { - Interlocked.Increment(ref fragmentedMessagesReceived); - Interlocked.Add(ref dataBytesReceived, dataLength); - Interlocked.Add(ref totalBytesReceived, totalLength); - } - - /// - /// Logs the receiving of an acknowledgement data packet in the statistics. - /// - /// The total number of bytes received. - /// - /// This should be called before the received event is invoked so it is up to date for subscribers to that event. - /// - internal void LogAcknowledgementReceive(int totalLength) - { - Interlocked.Increment(ref acknowledgementMessagesReceived); - Interlocked.Add(ref totalBytesReceived, totalLength); - } - - /// - /// Logs the receiving of a hello data packet in the statistics. - /// - /// The total number of bytes received. - /// - /// This should be called before the received event is invoked so it is up to date for subscribers to that event. - /// - internal void LogPingReceive(int totalLength) - { - Interlocked.Increment(ref pingMessagesReceived); - Interlocked.Add(ref totalBytesReceived, totalLength); - } - - /// - /// Logs the receiving of a hello data packet in the statistics. - /// - /// The total number of bytes received. - /// - /// This should be called before the received event is invoked so it is up to date for subscribers to that event. - /// - internal void LogHelloReceive(int totalLength) - { - Interlocked.Increment(ref helloMessagesReceived); - Interlocked.Add(ref totalBytesReceived, totalLength); - } - - internal void LogMessageResent() - { - Interlocked.Increment(ref messagesResent); - } - } -} diff --git a/src/Impostor.Hazel/DataReceivedEventArgs.cs b/src/Impostor.Hazel/DataReceivedEventArgs.cs deleted file mode 100644 index 9176d8d93..000000000 --- a/src/Impostor.Hazel/DataReceivedEventArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Impostor.Api.Net.Messages; - -namespace Impostor.Hazel -{ - public struct DataReceivedEventArgs - { - public readonly Connection Sender; - - /// - /// The bytes received from the client. - /// - public readonly IMessageReader Message; - - /// - /// The the data was sent with. - /// - public readonly MessageType Type; - - public DataReceivedEventArgs(Connection sender, IMessageReader msg, MessageType type) - { - this.Sender = sender; - this.Message = msg; - this.Type = type; - } - } -} diff --git a/src/Impostor.Hazel/DisconnectedEventArgs.cs b/src/Impostor.Hazel/DisconnectedEventArgs.cs deleted file mode 100644 index d46df4b9c..000000000 --- a/src/Impostor.Hazel/DisconnectedEventArgs.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Impostor.Api.Net.Messages; - -namespace Impostor.Hazel -{ - public class DisconnectedEventArgs : EventArgs - { - /// - /// Optional disconnect reason. May be null. - /// - public readonly string Reason; - - /// - /// Optional data sent with a disconnect message. May be null. - /// You must not recycle this. If you need the message outside of a callback, you should copy it. - /// - public readonly IMessageReader Message; - - public DisconnectedEventArgs(string reason, IMessageReader message) - { - this.Reason = reason; - this.Message = message; - } - } -} diff --git a/src/Impostor.Hazel/HazelException.cs b/src/Impostor.Hazel/HazelException.cs deleted file mode 100644 index 8c6fc3c12..000000000 --- a/src/Impostor.Hazel/HazelException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Impostor.Hazel -{ - /// - /// Wrapper for exceptions thrown from Hazel. - /// - [Serializable] - public class HazelException : Exception - { - internal HazelException(string msg) : base (msg) - { - - } - - internal HazelException(string msg, Exception e) : base (msg, e) - { - - } - } -} diff --git a/src/Impostor.Hazel/IPMode.cs b/src/Impostor.Hazel/IPMode.cs deleted file mode 100644 index 5eb6679e5..000000000 --- a/src/Impostor.Hazel/IPMode.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Impostor.Hazel -{ - /// - /// Represents the IP version that a connection or listener will use. - /// - /// - /// If you wand a client to connect or be able to connect using IPv6 then you should use , - /// this sets the underlying sockets to use IPv6 but still allow IPv4 sockets to connect for backwards compatability - /// and hence it is the default IPMode in most cases. - /// - public enum IPMode - { - /// - /// Instruction to use IPv4 only, IPv6 connections will not be able to connect. - /// - IPv4, - - /// - /// Instruction to use IPv6 only, IPv4 connections will not be able to connect. IPv4 addresses can be connected - /// by converting to IPv6 addresses. - /// - IPv6 - } -} diff --git a/src/Impostor.Hazel/IRecyclable.cs b/src/Impostor.Hazel/IRecyclable.cs deleted file mode 100644 index 69be1225b..000000000 --- a/src/Impostor.Hazel/IRecyclable.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Impostor.Hazel -{ - /// - /// Interface for all items that can be returned to an object pool. - /// - /// - public interface IRecyclable - { - /// - /// Returns this object back to the object pool. - /// - /// - /// - /// Calling this when you are done with the object returns the object back to a pool in order to be reused. - /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this. - /// - /// - /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are - /// completely finished with the object as the contents can be overwritten at any point after. - /// - /// - void Recycle(); - } -} diff --git a/src/Impostor.Hazel/Impostor.Hazel.csproj b/src/Impostor.Hazel/Impostor.Hazel.csproj deleted file mode 100644 index 3e035fbe9..000000000 --- a/src/Impostor.Hazel/Impostor.Hazel.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - true - net5.0 - HAZEL_BAG - 1.0.0 - - - - - - - - - - - - diff --git a/src/Impostor.Hazel/MessageReader.cs b/src/Impostor.Hazel/MessageReader.cs deleted file mode 100644 index 986d0b024..000000000 --- a/src/Impostor.Hazel/MessageReader.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System; -using System.Buffers; -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Text; -using Impostor.Api; -using Impostor.Api.Net.Messages; -using Microsoft.Extensions.ObjectPool; - -namespace Impostor.Hazel -{ - public class MessageReader : IMessageReader - { - private static readonly ArrayPool ArrayPool = ArrayPool.Shared; - - private readonly ObjectPool _pool; - private bool _inUse; - - internal MessageReader(ObjectPool pool) - { - _pool = pool; - } - - public byte[] Buffer { get; private set; } - - public int Offset { get; internal set; } - - public int Position { get; internal set; } - - public int Length { get; internal set; } - - public byte Tag { get; private set; } - - public MessageReader Parent { get; private set; } - - private int ReadPosition => Offset + Position; - - public void Update(byte[] buffer, int offset = 0, int position = 0, int? length = null, byte tag = byte.MaxValue, MessageReader parent = null) - { - _inUse = true; - - Buffer = buffer; - Offset = offset; - Position = position; - Length = length ?? buffer.Length; - Tag = tag; - Parent = parent; - } - - internal void Reset() - { - _inUse = false; - - Tag = byte.MaxValue; - Buffer = null; - Offset = 0; - Position = 0; - Length = 0; - Parent = null; - } - - public IMessageReader ReadMessage() - { - var length = ReadUInt16(); - var tag = FastByte(); - var pos = ReadPosition; - - Position += length; - - var reader = _pool.Get(); - reader.Update(Buffer, pos, 0, length, tag, this); - return reader; - } - - public bool ReadBoolean() - { - byte val = FastByte(); - return val != 0; - } - - public sbyte ReadSByte() - { - return (sbyte)FastByte(); - } - - public byte ReadByte() - { - return FastByte(); - } - - public ushort ReadUInt16() - { - var output = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.AsSpan(ReadPosition)); - Position += sizeof(ushort); - return output; - } - - public short ReadInt16() - { - var output = BinaryPrimitives.ReadInt16LittleEndian(Buffer.AsSpan(ReadPosition)); - Position += sizeof(short); - return output; - } - - public uint ReadUInt32() - { - var output = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.AsSpan(ReadPosition)); - Position += sizeof(uint); - return output; - } - - public int ReadInt32() - { - var output = BinaryPrimitives.ReadInt32LittleEndian(Buffer.AsSpan(ReadPosition)); - Position += sizeof(int); - return output; - } - - public unsafe float ReadSingle() - { - var output = BinaryPrimitives.ReadSingleLittleEndian(Buffer.AsSpan(ReadPosition)); - Position += sizeof(float); - return output; - } - - public string ReadString() - { - var len = ReadPackedInt32(); - var output = Encoding.UTF8.GetString(Buffer.AsSpan(ReadPosition, len)); - Position += len; - return output; - } - - public ReadOnlyMemory ReadBytesAndSize() - { - var len = ReadPackedInt32(); - return ReadBytes(len); - } - - public ReadOnlyMemory ReadBytes(int length) - { - var output = Buffer.AsMemory(ReadPosition, length); - Position += length; - return output; - } - - public int ReadPackedInt32() - { - return (int)ReadPackedUInt32(); - } - - public uint ReadPackedUInt32() - { - bool readMore = true; - int shift = 0; - uint output = 0; - - while (readMore) - { - byte b = FastByte(); - if (b >= 0x80) - { - readMore = true; - b ^= 0x80; - } - else - { - readMore = false; - } - - output |= (uint)(b << shift); - shift += 7; - } - - return output; - } - - public void CopyTo(IMessageWriter writer) - { - writer.Write((ushort) Length); - writer.Write((byte) Tag); - writer.Write(Buffer.AsMemory(Offset, Length)); - } - - public void Seek(int position) - { - Position = position; - } - - public void RemoveMessage(IMessageReader message) - { - if (message.Buffer != Buffer) - { - throw new ImpostorProtocolException("Tried to remove message from a message that does not have the same buffer."); - } - - // Offset of where to start removing. - var offsetStart = message.Offset - 3; - - // Offset of where to end removing. - var offsetEnd = message.Offset + message.Length; - - // The amount of bytes to copy over ourselves. - var lengthToCopy = message.Buffer.Length - offsetEnd; - - System.Buffer.BlockCopy(Buffer, offsetEnd, Buffer, offsetStart, lengthToCopy); - - ((MessageReader) message).Parent.AdjustLength(message.Offset, message.Length + 3); - } - - private void AdjustLength(int offset, int amount) - { - this.Length -= amount; - - if (this.ReadPosition > offset) - { - this.Position -= amount; - } - - if (Parent != null) - { - var lengthOffset = this.Offset - 3; - var curLen = this.Buffer[lengthOffset] | - (this.Buffer[lengthOffset + 1] << 8); - - curLen -= amount; - - this.Buffer[lengthOffset] = (byte)curLen; - this.Buffer[lengthOffset + 1] = (byte)(this.Buffer[lengthOffset + 1] >> 8); - - Parent.AdjustLength(offset, amount); - } - } - - public IMessageReader Copy(int offset = 0) - { - var reader = _pool.Get(); - reader.Update(Buffer, Offset + offset, Position, Length - offset, Tag, Parent); - return reader; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte FastByte() - { - return Buffer[Offset + Position++]; - } - - public void Dispose() - { - if (_inUse) - { - _pool.Return(this); - } - } - } -} diff --git a/src/Impostor.Hazel/MessageReaderPolicy.cs b/src/Impostor.Hazel/MessageReaderPolicy.cs deleted file mode 100644 index ef3939a48..000000000 --- a/src/Impostor.Hazel/MessageReaderPolicy.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.ObjectPool; - -namespace Impostor.Hazel -{ - public class MessageReaderPolicy : IPooledObjectPolicy - { - private readonly IServiceProvider _serviceProvider; - - public MessageReaderPolicy(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public MessageReader Create() - { - return new MessageReader(_serviceProvider.GetRequiredService>()); - } - - public bool Return(MessageReader obj) - { - obj.Reset(); - return true; - } - } -} diff --git a/src/Impostor.Hazel/MessageWriter.cs b/src/Impostor.Hazel/MessageWriter.cs deleted file mode 100644 index 5b7342a31..000000000 --- a/src/Impostor.Hazel/MessageWriter.cs +++ /dev/null @@ -1,335 +0,0 @@ -using Impostor.Api.Games; -using Impostor.Api.Net.Messages; - -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; - -namespace Impostor.Hazel -{ - public class MessageWriter : IMessageWriter, IRecyclable, IDisposable - { - private static int BufferSize = 64000; - private static readonly ObjectPoolCustom WriterPool = new ObjectPoolCustom(() => new MessageWriter(BufferSize)); - - public MessageType SendOption { get; private set; } - - private Stack messageStarts = new Stack(); - - public MessageWriter(byte[] buffer) - { - this.Buffer = buffer; - this.Length = this.Buffer.Length; - } - - public MessageWriter(int bufferSize) - { - this.Buffer = new byte[bufferSize]; - } - - public byte[] Buffer { get; } - public int Length { get; set; } - public int Position { get; set; } - - public byte[] ToByteArray(bool includeHeader) - { - if (includeHeader) - { - byte[] output = new byte[this.Length]; - System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); - return output; - } - else - { - switch (this.SendOption) - { - case MessageType.Reliable: - { - byte[] output = new byte[this.Length - 3]; - System.Buffer.BlockCopy(this.Buffer, 3, output, 0, this.Length - 3); - return output; - } - case MessageType.Unreliable: - { - byte[] output = new byte[this.Length - 1]; - System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1); - return output; - } - default: - throw new ArgumentOutOfRangeException(); - } - } - - throw new NotImplementedException(); - } - - /// - /// The option specifying how the message should be sent. - public static MessageWriter Get(MessageType sendOption = MessageType.Unreliable) - { - var output = WriterPool.GetObject(); - output.Clear(sendOption); - - return output; - } - - public bool HasBytes(int expected) - { - if (this.SendOption == MessageType.Unreliable) - { - return this.Length > 1 + expected; - } - - return this.Length > 3 + expected; - } - - public void Write(GameCode value) - { - this.Write(value.Value); - } - - /// - public void StartMessage(byte typeFlag) - { - messageStarts.Push(this.Position); - this.Position += 2; // Skip for size - this.Write(typeFlag); - } - - /// - public void EndMessage() - { - var lastMessageStart = messageStarts.Pop(); - ushort length = (ushort)(this.Position - lastMessageStart - 3); // Minus length and type byte - this.Buffer[lastMessageStart] = (byte)length; - this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); - } - - /// - public void CancelMessage() - { - this.Position = this.messageStarts.Pop(); - this.Length = this.Position; - } - - public void Clear(MessageType sendOption) - { - this.messageStarts.Clear(); - this.SendOption = sendOption; - this.Buffer[0] = (byte)sendOption; - switch (sendOption) - { - default: - case MessageType.Unreliable: - this.Length = this.Position = 1; - break; - - case MessageType.Reliable: - this.Length = this.Position = 3; - break; - } - } - - /// - public void Recycle() - { - this.Position = this.Length = 0; - WriterPool.PutObject(this); - } - - #region WriteMethods - - public void Write(bool value) - { - this.Buffer[this.Position++] = (byte)(value ? 1 : 0); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(sbyte value) - { - this.Buffer[this.Position++] = (byte)value; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte value) - { - this.Buffer[this.Position++] = value; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(short value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(ushort value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(uint value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - this.Buffer[this.Position++] = (byte)(value >> 16); - this.Buffer[this.Position++] = (byte)(value >> 24); - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(int value) - { - this.Buffer[this.Position++] = (byte)value; - this.Buffer[this.Position++] = (byte)(value >> 8); - this.Buffer[this.Position++] = (byte)(value >> 16); - this.Buffer[this.Position++] = (byte)(value >> 24); - if (this.Position > this.Length) this.Length = this.Position; - } - - public unsafe void Write(float value) - { - fixed (byte* ptr = &this.Buffer[this.Position]) - { - byte* valuePtr = (byte*)&value; - - *ptr = *valuePtr; - *(ptr + 1) = *(valuePtr + 1); - *(ptr + 2) = *(valuePtr + 2); - *(ptr + 3) = *(valuePtr + 3); - } - - this.Position += 4; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(string value) - { - var bytes = UTF8Encoding.UTF8.GetBytes(value); - this.WritePacked(bytes.Length); - this.Write(bytes); - } - - public void Write(IPAddress value) - { - this.Write(value.GetAddressBytes()); - } - - public void WriteBytesAndSize(byte[] bytes) - { - this.WritePacked((uint)bytes.Length); - this.Write(bytes); - } - - public void WriteBytesAndSize(byte[] bytes, int length) - { - this.WritePacked((uint)length); - this.Write(bytes, length); - } - - public void WriteBytesAndSize(byte[] bytes, int offset, int length) - { - this.WritePacked((uint)length); - this.Write(bytes, offset, length); - } - - public void Write(ReadOnlyMemory data) - { - Write(data.Span); - } - - public void Write(ReadOnlySpan bytes) - { - bytes.CopyTo(this.Buffer.AsSpan(this.Position, bytes.Length)); - - this.Position += bytes.Length; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte[] bytes) - { - Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length); - this.Position += bytes.Length; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte[] bytes, int offset, int length) - { - Array.Copy(bytes, offset, this.Buffer, this.Position, length); - this.Position += length; - if (this.Position > this.Length) this.Length = this.Position; - } - - public void Write(byte[] bytes, int length) - { - Array.Copy(bytes, 0, this.Buffer, this.Position, length); - this.Position += length; - if (this.Position > this.Length) this.Length = this.Position; - } - - /// - public void WritePacked(int value) - { - this.WritePacked((uint)value); - } - - /// - public void WritePacked(uint value) - { - do - { - byte b = (byte)(value & 0xFF); - if (value >= 0x80) - { - b |= 0x80; - } - - this.Write(b); - value >>= 7; - } while (value > 0); - } - - #endregion WriteMethods - - public void Write(MessageWriter msg, bool includeHeader) - { - int offset = 0; - if (!includeHeader) - { - switch (msg.SendOption) - { - case MessageType.Unreliable: - offset = 1; - break; - - case MessageType.Reliable: - offset = 3; - break; - } - } - - this.Write(msg.Buffer, offset, msg.Length - offset); - } - - public unsafe static bool IsLittleEndian() - { - byte b; - unsafe - { - int i = 1; - byte* bp = (byte*)&i; - b = *bp; - } - - return b == 1; - } - - public void Dispose() - { - Recycle(); - } - } -} diff --git a/src/Impostor.Hazel/NetworkConnection.cs b/src/Impostor.Hazel/NetworkConnection.cs deleted file mode 100644 index 282fe1078..000000000 --- a/src/Impostor.Hazel/NetworkConnection.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; - -namespace Impostor.Hazel -{ - public enum HazelInternalErrors - { - SocketExceptionSend, - SocketExceptionReceive, - ReceivedZeroBytes, - PingsWithoutResponse, - ReliablePacketWithoutResponse, - ConnectionDisconnected - } - - /// - /// Abstract base class for a to a remote end point via a network protocol like TCP or UDP. - /// - /// - public abstract class NetworkConnection : Connection - { - /// - /// An event that gives us a chance to send well-formed disconnect messages to clients when an internal disconnect happens. - /// - public Func OnInternalDisconnect; - - /// - /// The remote end point of this connection. - /// - /// - /// This is the end point of the other device given as an rather than a generic - /// as the base does. - /// - public IPEndPoint RemoteEndPoint { get; protected set; } - - public long GetIP4Address() - { - if (IPMode == IPMode.IPv4) - { - return ((IPEndPoint)this.RemoteEndPoint).Address.Address; - } - else - { - var bytes = ((IPEndPoint)this.RemoteEndPoint).Address.GetAddressBytes(); - return BitConverter.ToInt64(bytes, bytes.Length - 8); - } - } - - /// - /// Sends a disconnect message to the end point. - /// - protected abstract ValueTask SendDisconnect(MessageWriter writer); - - /// - /// Called when the socket has been disconnected at the remote host. - /// - protected async ValueTask DisconnectRemote(string reason, IMessageReader reader) - { - if (await SendDisconnect(null)) - { - try - { - await InvokeDisconnected(reason, reader); - } - catch { } - } - - this.Dispose(); - } - - /// - /// Called when socket is disconnected internally - /// - internal async ValueTask DisconnectInternal(HazelInternalErrors error, string reason) - { - var handler = this.OnInternalDisconnect; - if (handler != null) - { - MessageWriter messageToRemote = handler(error); - if (messageToRemote != null) - { - try - { - await Disconnect(reason, messageToRemote); - } - finally - { - messageToRemote.Recycle(); - } - } - else - { - await Disconnect(reason); - } - } - else - { - await Disconnect(reason); - } - } - - /// - /// Called when the socket has been disconnected locally. - /// - public override async ValueTask Disconnect(string reason, MessageWriter writer = null) - { - if (await SendDisconnect(writer)) - { - try - { - await InvokeDisconnected(reason, null); - } - catch { } - } - - this.Dispose(); - } - } -} diff --git a/src/Impostor.Hazel/NetworkConnectionListener.cs b/src/Impostor.Hazel/NetworkConnectionListener.cs deleted file mode 100644 index e1d7ffaf8..000000000 --- a/src/Impostor.Hazel/NetworkConnectionListener.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Net; - -namespace Impostor.Hazel -{ - /// - /// Abstract base class for a for network based connections. - /// - /// - public abstract class NetworkConnectionListener : ConnectionListener - { - /// - /// The local end point the listener is listening for new clients on. - /// - public IPEndPoint EndPoint { get; protected set; } - - /// - /// The IPMode the listener is listening for new clients on. - /// - public IPMode IPMode { get; protected set; } - } -} diff --git a/src/Impostor.Hazel/NewConnectionEventArgs.cs b/src/Impostor.Hazel/NewConnectionEventArgs.cs deleted file mode 100644 index be9e7a214..000000000 --- a/src/Impostor.Hazel/NewConnectionEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Impostor.Api.Net.Messages; - -namespace Impostor.Hazel -{ - public struct NewConnectionEventArgs - { - /// - /// The data received from the client in the handshake. - /// This data is yours. Remember to recycle it. - /// - public readonly IMessageReader HandshakeData; - - /// - /// The to the new client. - /// - public readonly Connection Connection; - - public NewConnectionEventArgs(IMessageReader handshakeData, Connection connection) - { - this.HandshakeData = handshakeData; - this.Connection = connection; - } - } -} diff --git a/src/Impostor.Hazel/ObjectPoolCustom.cs b/src/Impostor.Hazel/ObjectPoolCustom.cs deleted file mode 100644 index 5c9ef9b40..000000000 --- a/src/Impostor.Hazel/ObjectPoolCustom.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Impostor.Hazel -{ - /// - /// A fairly simple object pool for items that will be created a lot. - /// - /// The type that is pooled. - /// - public sealed class ObjectPoolCustom where T : IRecyclable - { - private int numberCreated; - public int NumberCreated { get { return numberCreated; } } - - public int NumberInUse { get { return this.inuse.Count; } } - public int NumberNotInUse { get { return this.pool.Count; } } - public int Size { get { return this.NumberInUse + this.NumberNotInUse; } } - -#if HAZEL_BAG - private readonly ConcurrentBag pool = new ConcurrentBag(); -#else - private readonly List pool = new List(); -#endif - - // Unavailable objects - private readonly ConcurrentDictionary inuse = new ConcurrentDictionary(); - - /// - /// The generator for creating new objects. - /// - /// - private readonly Func objectFactory; - - /// - /// Internal constructor for our ObjectPool. - /// - internal ObjectPoolCustom(Func objectFactory) - { - this.objectFactory = objectFactory; - } - - /// - /// Returns a pooled object of type T, if none are available another is created. - /// - /// An instance of T. - internal T GetObject() - { -#if HAZEL_BAG - if (!pool.TryTake(out T item)) - { - Interlocked.Increment(ref numberCreated); - item = objectFactory.Invoke(); - } -#else - T item; - lock (this.pool) - { - if (this.pool.Count > 0) - { - var idx = this.pool.Count - 1; - item = this.pool[idx]; - this.pool.RemoveAt(idx); - } - else - { - Interlocked.Increment(ref numberCreated); - item = objectFactory.Invoke(); - } - } -#endif - - if (!inuse.TryAdd(item, true)) - { - throw new Exception("Duplicate pull " + typeof(T).Name); - } - - return item; - } - - /// - /// Returns an object to the pool. - /// - /// The item to return. - internal void PutObject(T item) - { - if (inuse.TryRemove(item, out bool b)) - { -#if HAZEL_BAG - pool.Add(item); -#else - lock (this.pool) - { - pool.Add(item); - } -#endif - } - else - { -#if DEBUG - throw new Exception("Duplicate add " + typeof(T).Name); -#endif - } - } - } -} diff --git a/src/Impostor.Hazel/Udp/SendOptionInternal.cs b/src/Impostor.Hazel/Udp/SendOptionInternal.cs deleted file mode 100644 index c0c4e2127..000000000 --- a/src/Impostor.Hazel/Udp/SendOptionInternal.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Impostor.Hazel.Udp -{ - /// - /// Extra internal states for SendOption enumeration when using UDP. - /// - public enum UdpSendOption : byte - { - /// - /// Hello message for initiating communication. - /// - Hello = 8, - - /// - /// A single byte of continued existence - /// - Ping = 12, - - /// - /// Message for discontinuing communication. - /// - Disconnect = 9, - - /// - /// Message acknowledging the receipt of a message. - /// - Acknowledgement = 10, - - /// - /// Message that is part of a larger, fragmented message. - /// - Fragment = 11, - } -} diff --git a/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs b/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs deleted file mode 100644 index ed7b68dbd..000000000 --- a/src/Impostor.Hazel/Udp/UdpBroadcastListener.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Text; - -namespace Impostor.Hazel.Udp -{ - public class BroadcastPacket - { - public string Data; - public DateTime ReceiveTime; - public IPEndPoint Sender; - - public BroadcastPacket(string data, IPEndPoint sender) - { - this.Data = data; - this.Sender = sender; - this.ReceiveTime = DateTime.Now; - } - - public string GetAddress() - { - return this.Sender.Address.ToString(); - } - } - - public class UdpBroadcastListener : IDisposable - { - private Socket socket; - private EndPoint endpoint; - private Action logger; - - private byte[] buffer = new byte[1024]; - - private List packets = new List(); - - public bool Running { get; private set; } - - /// - public UdpBroadcastListener(int port, Action logger = null) - { - this.logger = logger; - this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - this.socket.EnableBroadcast = true; - this.socket.MulticastLoopback = false; - this.endpoint = new IPEndPoint(IPAddress.Any, port); - this.socket.Bind(this.endpoint); - } - - /// - public void StartListen() - { - if (this.Running) return; - this.Running = true; - - try - { - EndPoint endpt = new IPEndPoint(IPAddress.Any, 0); - this.socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpt, this.HandleData, null); - } - catch (NullReferenceException) { } - catch (Exception e) - { - this.logger?.Invoke("BroadcastListener: " + e); - this.Dispose(); - } - } - - private void HandleData(IAsyncResult result) - { - this.Running = false; - - int numBytes; - EndPoint endpt = new IPEndPoint(IPAddress.Any, 0); - try - { - numBytes = this.socket.EndReceiveFrom(result, ref endpt); - } - catch (NullReferenceException) - { - // Already disposed - return; - } - catch (Exception e) - { - this.logger?.Invoke("BroadcastListener: " + e); - this.Dispose(); - return; - } - - if (numBytes < 3 - || buffer[0] != 4 || buffer[1] != 2) - { - this.StartListen(); - return; - } - - IPEndPoint ipEnd = (IPEndPoint)endpt; - string data = UTF8Encoding.UTF8.GetString(buffer, 2, numBytes - 2); - int dataHash = data.GetHashCode(); - - lock (packets) - { - bool found = false; - for (int i = 0; i < this.packets.Count; ++i) - { - var pkt = this.packets[i]; - if (pkt == null || pkt.Data == null) - { - this.packets.RemoveAt(i); - i--; - continue; - } - - if (pkt.Data.GetHashCode() == dataHash - && pkt.Sender.Equals(ipEnd)) - { - this.packets[i].ReceiveTime = DateTime.Now; - break; - } - } - - if (!found) - { - this.packets.Add(new BroadcastPacket(data, ipEnd)); - } - } - - this.StartListen(); - } - - /// - public BroadcastPacket[] GetPackets() - { - lock (this.packets) - { - var output = this.packets.ToArray(); - this.packets.Clear(); - return output; - } - } - - /// - public void Dispose() - { - if (this.socket != null) - { - try { this.socket.Shutdown(SocketShutdown.Both); } catch { } - try { this.socket.Close(); } catch { } - try { this.socket.Dispose(); } catch { } - this.socket = null; - } - } - } -} \ No newline at end of file diff --git a/src/Impostor.Hazel/Udp/UdpBroadcaster.cs b/src/Impostor.Hazel/Udp/UdpBroadcaster.cs deleted file mode 100644 index 5fa1ccac2..000000000 --- a/src/Impostor.Hazel/Udp/UdpBroadcaster.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Text; - -namespace Impostor.Hazel.Udp -{ - /// - public class UdpBroadcaster : IDisposable - { - private Socket socket; - private byte[] data; - private EndPoint endpoint; - private Action logger; - - /// - public UdpBroadcaster(int port, Action logger = null) - { - this.logger = logger; - this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - this.socket.EnableBroadcast = true; - this.socket.MulticastLoopback = false; - this.endpoint = new IPEndPoint(IPAddress.Broadcast, port); - } - - /// - public void SetData(string data) - { - int len = UTF8Encoding.UTF8.GetByteCount(data); - this.data = new byte[len + 2]; - this.data[0] = 4; - this.data[1] = 2; - - UTF8Encoding.UTF8.GetBytes(data, 0, data.Length, this.data, 2); - } - - /// - public void Broadcast() - { - if (this.data == null) - { - return; - } - - try - { - this.socket.BeginSendTo(data, 0, data.Length, SocketFlags.None, this.endpoint, this.FinishSendTo, null); - } - catch (Exception e) - { - this.logger?.Invoke("BroadcastListener: " + e); - } - } - - private void FinishSendTo(IAsyncResult evt) - { - try - { - this.socket.EndSendTo(evt); - } - catch (Exception e) - { - this.logger?.Invoke("BroadcastListener: " + e); - } - } - - /// - public void Dispose() - { - if (this.socket != null) - { - try { this.socket.Shutdown(SocketShutdown.Both); } catch { } - try { this.socket.Close(); } catch { } - try { this.socket.Dispose(); } catch { } - this.socket = null; - } - } - } -} \ No newline at end of file diff --git a/src/Impostor.Hazel/Udp/UdpClientConnection.cs b/src/Impostor.Hazel/Udp/UdpClientConnection.cs deleted file mode 100644 index 5125ebe80..000000000 --- a/src/Impostor.Hazel/Udp/UdpClientConnection.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Buffers; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; -using Microsoft.Extensions.ObjectPool; -using Serilog; - -namespace Impostor.Hazel.Udp -{ - /// - /// Represents a client's connection to a server that uses the UDP protocol. - /// - /// - public sealed class UdpClientConnection : UdpConnection - { - private static readonly ILogger Logger = Log.ForContext(); - - /// - /// The socket we're connected via. - /// - private readonly UdpClient _socket; - - private readonly Timer _reliablePacketTimer; - private readonly SemaphoreSlim _connectWaitLock; - private Task _listenTask; - - /// - /// Creates a new UdpClientConnection. - /// - /// A to connect to. - public UdpClientConnection(IPEndPoint remoteEndPoint, ObjectPool readerPool, IPMode ipMode = IPMode.IPv4) : base(null, readerPool) - { - EndPoint = remoteEndPoint; - RemoteEndPoint = remoteEndPoint; - IPMode = ipMode; - - _socket = new UdpClient - { - DontFragment = false - }; - - _reliablePacketTimer = new Timer(ManageReliablePacketsInternal, null, 100, Timeout.Infinite); - _connectWaitLock = new SemaphoreSlim(1, 1); - } - - ~UdpClientConnection() - { - Dispose(false); - } - - private async void ManageReliablePacketsInternal(object state) - { - await ManageReliablePackets(); - - try - { - _reliablePacketTimer.Change(100, Timeout.Infinite); - } - catch - { - // ignored - } - } - - /// - protected override ValueTask WriteBytesToConnection(byte[] bytes, int length) - { - return WriteBytesToConnectionReal(bytes, length); - } - - private async ValueTask WriteBytesToConnectionReal(byte[] bytes, int length) - { - try - { - await _socket.SendAsync(bytes, length); - } - catch (NullReferenceException) { } - catch (ObjectDisposedException) - { - // Already disposed and disconnected... - } - catch (SocketException ex) - { - await DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message); - } - } - - /// - public override async ValueTask ConnectAsync(byte[] bytes = null) - { - State = ConnectionState.Connecting; - - try - { - _socket.Connect(RemoteEndPoint); - } - catch (SocketException e) - { - State = ConnectionState.NotConnected; - throw new HazelException("A SocketException occurred while binding to the port.", e); - } - - try - { - _listenTask = Task.Factory.StartNew(ListenAsync, TaskCreationOptions.LongRunning); - } - catch (ObjectDisposedException) - { - // If the socket's been disposed then we can just end there but make sure we're in NotConnected state. - // If we end up here I'm really lost... - State = ConnectionState.NotConnected; - return; - } - catch (SocketException e) - { - Dispose(); - throw new HazelException("A SocketException occurred while initiating a receive operation.", e); - } - - // Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present) - // When acknowledged set the state to connected - await SendHello(bytes, () => - { - State = ConnectionState.Connected; - InitializeKeepAliveTimer(); - }); - - await _connectWaitLock.WaitAsync(TimeSpan.FromSeconds(10)); - } - - private async Task ListenAsync() - { - // Start packet handler. - await StartAsync(); - - // Listen. - while (State != ConnectionState.NotConnected) - { - UdpReceiveResult data; - - try - { - data = await _socket.ReceiveAsync(); - } - catch (SocketException e) - { - await DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception while reading data: " + e.Message); - return; - } - catch (Exception) - { - return; - } - - if (data.Buffer.Length == 0) - { - await DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes"); - return; - } - - // Write to client. - await Pipeline.Writer.WriteAsync(data.Buffer); - } - } - - protected override void SetState(ConnectionState state) - { - if (state == ConnectionState.Connected) - { - _connectWaitLock.Release(); - } - } - - /// - /// Sends a disconnect message to the end point. - /// You may include optional disconnect data. The SendOption must be unreliable. - /// - protected override async ValueTask SendDisconnect(MessageWriter data = null) - { - lock (this) - { - if (_state == ConnectionState.NotConnected) return false; - _state = ConnectionState.NotConnected; - } - - var bytes = EmptyDisconnectBytes; - if (data != null && data.Length > 0) - { - if (data.SendOption != MessageType.Unreliable) - { - throw new ArgumentException("Disconnect messages can only be unreliable."); - } - - bytes = data.ToByteArray(true); - bytes[0] = (byte)UdpSendOption.Disconnect; - } - - try - { - await _socket.SendAsync(bytes, bytes.Length, RemoteEndPoint); - } - catch { } - - return true; - } - - /// - protected override void Dispose(bool disposing) - { - State = ConnectionState.NotConnected; - - try { _socket.Close(); } catch { } - try { _socket.Dispose(); } catch { } - - _reliablePacketTimer.Dispose(); - _connectWaitLock.Dispose(); - - base.Dispose(disposing); - } - } -} diff --git a/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs b/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs deleted file mode 100644 index a73291bc4..000000000 --- a/src/Impostor.Hazel/Udp/UdpConnection.KeepAlive.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace Impostor.Hazel.Udp -{ - partial class UdpConnection - { - - /// - /// Class to hold packet data - /// - public class PingPacket : IRecyclable - { - private static readonly ObjectPoolCustom PacketPool = new ObjectPoolCustom(() => new PingPacket()); - - public readonly Stopwatch Stopwatch = new Stopwatch(); - - internal static PingPacket GetObject() - { - return PacketPool.GetObject(); - } - - public void Recycle() - { - Stopwatch.Stop(); - PacketPool.PutObject(this); - } - } - - internal ConcurrentDictionary activePingPackets = new ConcurrentDictionary(); - - /// - /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds. - /// - /// - /// - /// Keepalive packets serve to close connections when an endpoint abruptly disconnects and to ensure than any - /// NAT devices do not close their translation for our argument. By ensuring there is regular contact the - /// connection can detect and prevent these issues. - /// - /// - /// The default value is 10 seconds, set to System.Threading.Timeout.Infinite to disable keepalive packets. - /// - /// - public int KeepAliveInterval - { - get - { - return keepAliveInterval; - } - - set - { - keepAliveInterval = value; - ResetKeepAliveTimer(); - } - } - private int keepAliveInterval = 1500; - - public int MissingPingsUntilDisconnect { get; set; } = 6; - private volatile int pingsSinceAck = 0; - - /// - /// The timer creating keepalive pulses. - /// - private Timer keepAliveTimer; - - /// - /// Starts the keepalive timer. - /// - protected void InitializeKeepAliveTimer() - { - keepAliveTimer = new Timer( - HandleKeepAlive, - null, - keepAliveInterval, - keepAliveInterval - ); - } - - private async void HandleKeepAlive(object state) - { - if (this.State != ConnectionState.Connected) return; - - if (this.pingsSinceAck >= this.MissingPingsUntilDisconnect) - { - this.DisposeKeepAliveTimer(); - await this.DisconnectInternal(HazelInternalErrors.PingsWithoutResponse, $"Sent {this.pingsSinceAck} pings that remote has not responded to."); - return; - } - - try - { - Interlocked.Increment(ref pingsSinceAck); - await SendPing(); - } - catch - { - } - } - - // Pings are special, quasi-reliable packets. - // We send them to trigger responses that validate our connection is alive - // An unacked ping should never be the sole cause of a disconnect. - // Rather, the responses will reset our pingsSinceAck, enough unacked - // pings should cause a disconnect. - private async ValueTask SendPing() - { - ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated); - - byte[] bytes = new byte[3]; - bytes[0] = (byte)UdpSendOption.Ping; - bytes[1] = (byte)(id >> 8); - bytes[2] = (byte)id; - - PingPacket pkt; - if (!this.activePingPackets.TryGetValue(id, out pkt)) - { - pkt = PingPacket.GetObject(); - if (!this.activePingPackets.TryAdd(id, pkt)) - { - throw new Exception("This shouldn't be possible"); - } - } - - pkt.Stopwatch.Restart(); - - await WriteBytesToConnection(bytes, bytes.Length); - - Statistics.LogReliableSend(0, bytes.Length); - } - - /// - /// Resets the keepalive timer to zero. - /// - private void ResetKeepAliveTimer() - { - try - { - keepAliveTimer.Change(keepAliveInterval, keepAliveInterval); - } - catch { } - } - - /// - /// Disposes of the keep alive timer. - /// - private void DisposeKeepAliveTimer() - { - if (this.keepAliveTimer != null) - { - this.keepAliveTimer.Dispose(); - } - - foreach (var kvp in activePingPackets) - { - if (this.activePingPackets.TryRemove(kvp.Key, out var pkt)) - { - pkt.Recycle(); - } - } - } - } -} \ No newline at end of file diff --git a/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs b/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs deleted file mode 100644 index a7a4309e9..000000000 --- a/src/Impostor.Hazel/Udp/UdpConnection.Reliable.cs +++ /dev/null @@ -1,491 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; - -namespace Impostor.Hazel.Udp -{ - partial class UdpConnection - { - /// - /// The starting timeout, in miliseconds, at which data will be resent. - /// - /// - /// - /// For reliable delivery data is resent at specified intervals unless an acknowledgement is received from the - /// receiving device. The ResendTimeout specifies the interval between the packets being resent, each time a packet - /// is resent the interval is increased for that packet until the duration exceeds the value. - /// - /// - /// Setting this to its default of 0 will mean the timeout is 2 times the value of the average ping, usually - /// resulting in a more dynamic resend that responds to endpoints on slower or faster connections. - /// - /// - public volatile int ResendTimeout = 0; - - /// - /// Max number of times to resend. 0 == no limit - /// - public volatile int ResendLimit = 0; - - /// - /// A compounding multiplier to back off resend timeout. - /// Applied to ping before first timeout when ResendTimeout == 0. - /// - public volatile float ResendPingMultiplier = 2; - - /// - /// Holds the last ID allocated. - /// - private int lastIDAllocated = 0; - - /// - /// The packets of data that have been transmitted reliably and not acknowledged. - /// - internal ConcurrentDictionary reliableDataPacketsSent = new ConcurrentDictionary(); - - /// - /// Packet ids that have not been received, but are expected. - /// - private HashSet reliableDataPacketsMissing = new HashSet(); - - /// - /// The packet id that was received last. - /// - private volatile ushort reliableReceiveLast = ushort.MaxValue; - - private object PingLock = new object(); - - /// - /// Returns the average ping to this endpoint. - /// - /// - /// This returns the average ping for a one-way trip as calculated from the reliable packets that have been sent - /// and acknowledged by the endpoint. - /// - public float AveragePingMs = 500; - - /// - /// The maximum times a message should be resent before marking the endpoint as disconnected. - /// - /// - /// Reliable packets will be resent at an interval defined in for the number of times - /// specified here. Once a packet has been retransmitted this number of times and has not been acknowledged the - /// connection will be marked as disconnected and the Disconnected event - /// will be invoked. - /// - public volatile int DisconnectTimeout = 5000; - - /// - /// Class to hold packet data - /// - public class Packet : IRecyclable - { - /// - /// Object pool for this event. - /// - public static readonly ObjectPoolCustom PacketPool = new ObjectPoolCustom(() => new Packet()); - - /// - /// Returns an instance of this object from the pool. - /// - /// - internal static Packet GetObject() - { - return PacketPool.GetObject(); - } - - public ushort Id; - private byte[] Data; - private UdpConnection Connection; - private int Length; - - public int NextTimeout; - public volatile bool Acknowledged; - - public Action AckCallback; - - public int Retransmissions; - public Stopwatch Stopwatch = new Stopwatch(); - - Packet() - { - } - - internal void Set(ushort id, UdpConnection connection, byte[] data, int length, int timeout, Action ackCallback) - { - this.Id = id; - this.Data = data; - this.Connection = connection; - this.Length = length; - - this.Acknowledged = false; - this.NextTimeout = timeout; - this.AckCallback = ackCallback; - this.Retransmissions = 0; - - this.Stopwatch.Restart(); - } - - // Packets resent - public async ValueTask Resend() - { - var connection = this.Connection; - if (!this.Acknowledged && connection != null) - { - long lifetime = this.Stopwatch.ElapsedMilliseconds; - if (lifetime >= connection.DisconnectTimeout) - { - if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) - { - await connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {lifetime}ms ({self.Retransmissions} resends)"); - - self.Recycle(); - } - - return 0; - } - - if (lifetime >= this.NextTimeout) - { - ++this.Retransmissions; - if (connection.ResendLimit != 0 - && this.Retransmissions > connection.ResendLimit) - { - if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) - { - await connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {self.Retransmissions} resends ({lifetime}ms)"); - - self.Recycle(); - } - - return 0; - } - - this.NextTimeout += (int)Math.Min(this.NextTimeout * connection.ResendPingMultiplier, 1000); - try - { - await connection.WriteBytesToConnection(this.Data, this.Length); - connection.Statistics.LogMessageResent(); - return 1; - } - catch (InvalidOperationException) - { - await connection.DisconnectInternal(HazelInternalErrors.ConnectionDisconnected, "Could not resend data as connection is no longer connected"); - } - } - } - - return 0; - } - - /// - /// Returns this object back to the object pool from whence it came. - /// - public void Recycle() - { - this.Acknowledged = true; - this.Connection = null; - - PacketPool.PutObject(this); - } - } - - internal async ValueTask ManageReliablePackets() - { - int output = 0; - if (this.reliableDataPacketsSent.Count > 0) - { - foreach (var kvp in this.reliableDataPacketsSent) - { - Packet pkt = kvp.Value; - - try - { - output += await pkt.Resend(); - } - catch { } - } - } - - return output; - } - - /// - /// Adds a 2 byte ID to the packet at offset and stores the packet reference for retransmission. - /// - /// The buffer to attach to. - /// The offset to attach at. - /// The callback to make once the packet has been acknowledged. - protected void AttachReliableID(byte[] buffer, int offset, int sendLength, Action ackCallback = null) - { - ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated); - - buffer[offset] = (byte)(id >> 8); - buffer[offset + 1] = (byte)id; - - Packet packet = Packet.GetObject(); - packet.Set( - id, - this, - buffer, - sendLength, - ResendTimeout > 0 ? ResendTimeout : (int)Math.Min(AveragePingMs * this.ResendPingMultiplier, 300), - ackCallback); - - if (!reliableDataPacketsSent.TryAdd(id, packet)) - { - throw new Exception("That shouldn't be possible"); - } - } - - public static int ClampToInt(float value, int min, int max) - { - if (value < min) return min; - if (value > max) return max; - return (int)value; - } - - /// - /// Sends the bytes reliably and stores the send. - /// - /// - /// The byte array to write to. - /// The callback to make once the packet has been acknowledged. - private async ValueTask ReliableSend(byte sendOption, byte[] data, Action ackCallback = null) - { - //Inform keepalive not to send for a while - ResetKeepAliveTimer(); - - byte[] bytes = new byte[data.Length + 3]; - - //Add message type - bytes[0] = sendOption; - - //Add reliable ID - AttachReliableID(bytes, 1, bytes.Length, ackCallback); - - //Copy data into new array - Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length); - - //Write to connection - await WriteBytesToConnection(bytes, bytes.Length); - - Statistics.LogReliableSend(data.Length, bytes.Length); - } - - /// - /// Handles a reliable message being received and invokes the data event. - /// - /// The buffer received. - private async ValueTask ReliableMessageReceive(MessageReader message) - { - if (await ProcessReliableReceive(message.Buffer, 1)) - { - message.Offset += 3; - message.Length -= 3; - message.Position = 0; - - await InvokeDataReceived(message, MessageType.Reliable); - } - - Statistics.LogReliableReceive(message.Length - 3, message.Length); - } - - /// - /// Handles receives from reliable packets. - /// - /// The buffer containing the data. - /// The offset of the reliable header. - /// Whether the packet was a new packet or not. - private async ValueTask ProcessReliableReceive(ReadOnlyMemory bytes, int offset) - { - var b1 = bytes.Span[offset]; - var b2 = bytes.Span[offset + 1]; - - //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...) - * - * In a simple world if our data is greater than the last reliable packet received (reliableReceiveLast) - * then it is guaranteed to be a new packet, if it's not we can see if we are missing that packet (lookup - * in reliableDataPacketsMissing). - * - * --------rrl############# (1) - * - * (where --- are packets received already and #### are packets that will be counted as new) - * - * Unfortunately if id becomes greater than 65535 it will loop back to zero so we will add a pointer that - * specifies any packets with an id behind it are also new (overwritePointer). - * - * ####op----------rrl##### (2) - * - * ------rll#########op---- (3) - * - * Anything behind than the reliableReceiveLast pointer (but greater than the overwritePointer is either a - * missing packet or something we've already received so when we change the pointers we need to make sure - * we keep note of what hasn't been received yet (reliableDataPacketsMissing). - * - * So... - */ - - lock (reliableDataPacketsMissing) - { - //Calculate overwritePointer - ushort overwritePointer = (ushort)(reliableReceiveLast - 32768); - - //Calculate if it is a new packet by examining if it is within the range - bool isNew; - if (overwritePointer < reliableReceiveLast) - isNew = id > reliableReceiveLast || id <= overwritePointer; //Figure (2) - else - isNew = id > reliableReceiveLast && id <= overwritePointer; //Figure (3) - - //If it's new or we've not received anything yet - if (isNew) - { - // Mark items between the most recent receive and the id received as missing - if (id > reliableReceiveLast) - { - for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++) - { - reliableDataPacketsMissing.Add(i); - } - } - else - { - int cnt = (ushort.MaxValue - reliableReceiveLast) + id; - for (ushort i = 1; i < cnt; ++i) - { - reliableDataPacketsMissing.Add((ushort)(i + reliableReceiveLast)); - } - } - - //Update the most recently received - reliableReceiveLast = id; - } - - //Else it could be a missing packet - else - { - //See if we're missing it, else this packet is a duplicate as so we return false - if (!reliableDataPacketsMissing.Remove(id)) - { - return false; - } - } - } - - return true; - } - - /// - /// Handles acknowledgement packets to us. - /// - /// The buffer containing the data. - private void AcknowledgementMessageReceive(ReadOnlySpan bytes) - { - this.pingsSinceAck = 0; - - ushort id = (ushort)((bytes[1] << 8) + bytes[2]); - AcknowledgeMessageId(id); - - if (bytes.Length == 4) - { - byte recentPackets = bytes[3]; - for (int i = 1; i <= 8; ++i) - { - if ((recentPackets & 1) != 0) - { - AcknowledgeMessageId((ushort)(id - i)); - } - - recentPackets >>= 1; - } - } - - Statistics.LogReliableReceive(0, bytes.Length); - } - - private void AcknowledgeMessageId(ushort id) - { - // Dispose of timer and remove from dictionary - if (reliableDataPacketsSent.TryRemove(id, out Packet packet)) - { - float rt = packet.Stopwatch.ElapsedMilliseconds; - - packet.AckCallback?.Invoke(); - packet.Recycle(); - - lock (PingLock) - { - this.AveragePingMs = Math.Max(50, this.AveragePingMs * .7f + rt * .3f); - } - } - else if (this.activePingPackets.TryRemove(id, out PingPacket pingPkt)) - { - float rt = pingPkt.Stopwatch.ElapsedMilliseconds; - - pingPkt.Recycle(); - - lock (PingLock) - { - this.AveragePingMs = Math.Max(50, this.AveragePingMs * .7f + rt * .3f); - } - } - } - - /// - /// Sends an acknowledgement for a packet given its identification bytes. - /// - /// The first identification byte. - /// The second identification byte. - private async ValueTask SendAck(ushort id) - { - byte recentPackets = 0; - lock (this.reliableDataPacketsMissing) - { - for (int i = 1; i <= 8; ++i) - { - if (!this.reliableDataPacketsMissing.Contains((ushort)(id - i))) - { - recentPackets |= (byte)(1 << (i - 1)); - } - } - } - - byte[] bytes = new byte[] - { - (byte)UdpSendOption.Acknowledgement, - (byte)(id >> 8), - (byte)(id >> 0), - recentPackets - }; - - try - { - await WriteBytesToConnection(bytes, bytes.Length); - } - catch (InvalidOperationException) { } - } - - private void DisposeReliablePackets() - { - foreach (var kvp in reliableDataPacketsSent) - { - if (this.reliableDataPacketsSent.TryRemove(kvp.Key, out var pkt)) - { - pkt.Recycle(); - } - } - } - } -} diff --git a/src/Impostor.Hazel/Udp/UdpConnection.cs b/src/Impostor.Hazel/Udp/UdpConnection.cs deleted file mode 100644 index 5288d3ce2..000000000 --- a/src/Impostor.Hazel/Udp/UdpConnection.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; -using Microsoft.Extensions.ObjectPool; -using Serilog; - -namespace Impostor.Hazel.Udp -{ - /// - /// Represents a connection that uses the UDP protocol. - /// - /// - public abstract partial class UdpConnection : NetworkConnection - { - protected static readonly byte[] EmptyDisconnectBytes = { (byte)UdpSendOption.Disconnect }; - - private static readonly ILogger Logger = Log.ForContext(); - private readonly ConnectionListener _listener; - private readonly ObjectPool _readerPool; - private readonly CancellationTokenSource _stoppingCts; - - private bool _isDisposing; - private bool _isFirst = true; - private Task _executingTask; - - protected UdpConnection(ConnectionListener listener, ObjectPool readerPool) - { - _listener = listener; - _readerPool = readerPool; - _stoppingCts = new CancellationTokenSource(); - - Pipeline = Channel.CreateUnbounded(new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = true - }); - } - - internal Channel Pipeline { get; } - - public Task StartAsync() - { - // Store the task we're executing - _executingTask = Task.Factory.StartNew(ReadAsync, TaskCreationOptions.LongRunning); - - // If the task is completed then return it, this will bubble cancellation and failure to the caller - if (_executingTask.IsCompleted) - { - return _executingTask; - } - - // Otherwise it's running - return Task.CompletedTask; - } - - public void Stop() - { - // Stop called without start - if (_executingTask == null) - { - return; - } - - // Signal cancellation to methods. - _stoppingCts.Cancel(); - - try - { - // Cancel reader. - Pipeline.Writer.Complete(); - } - catch (ChannelClosedException) - { - // Already done. - } - - // Remove references. - if (!_isDisposing) - { - Dispose(true); - } - } - - private async Task ReadAsync() - { - var reader = new MessageReader(_readerPool); - - while (!_stoppingCts.IsCancellationRequested) - { - var result = await Pipeline.Reader.ReadAsync(_stoppingCts.Token); - - try - { - reader.Update(result); - - await HandleReceive(reader); - } - catch (Exception e) - { - Logger.Error(e, "Exception during ReadAsync"); - Dispose(true); - break; - } - } - } - - /// - /// Writes the given bytes to the connection. - /// - /// The bytes to write. - /// - protected abstract ValueTask WriteBytesToConnection(byte[] bytes, int length); - - /// - public override async ValueTask SendAsync(IMessageWriter msg) - { - if (this._state != ConnectionState.Connected) - throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); - - byte[] buffer = new byte[msg.Length]; - Buffer.BlockCopy(msg.Buffer, 0, buffer, 0, msg.Length); - - switch (msg.SendOption) - { - case MessageType.Reliable: - ResetKeepAliveTimer(); - - AttachReliableID(buffer, 1, buffer.Length); - await WriteBytesToConnection(buffer, buffer.Length); - Statistics.LogReliableSend(buffer.Length - 3, buffer.Length); - break; - - default: - await WriteBytesToConnection(buffer, buffer.Length); - Statistics.LogUnreliableSend(buffer.Length - 1, buffer.Length); - break; - } - } - - /// - /// - /// - /// - /// Udp connections can currently send messages using and - /// . Fragmented messages are not currently supported and will default to - /// until implemented. - /// - /// - public override async ValueTask SendBytes(byte[] bytes, MessageType sendOption = MessageType.Unreliable) - { - //Add header information and send - await HandleSend(bytes, (byte)sendOption); - } - - /// - /// Handles the reliable/fragmented sending from this connection. - /// - /// The data being sent. - /// The specified as its byte value. - /// The callback to invoke when this packet is acknowledged. - /// The bytes that should actually be sent. - protected async ValueTask HandleSend(byte[] data, byte sendOption, Action ackCallback = null) - { - switch (sendOption) - { - case (byte)UdpSendOption.Ping: - case (byte)MessageType.Reliable: - case (byte)UdpSendOption.Hello: - await ReliableSend(sendOption, data, ackCallback); - break; - - //Treat all else as unreliable - default: - await UnreliableSend(sendOption, data); - break; - } - } - - /// - /// Handles the receiving of data. - /// - /// The buffer containing the bytes received. - protected async ValueTask HandleReceive(MessageReader message) - { - // Check if the first message received is the hello packet. - if (_isFirst) - { - _isFirst = false; - - // Slice 4 bytes to get handshake data. - if (_listener != null) - { - using (var handshake = message.Copy(4)) - { - await _listener.InvokeNewConnection(handshake, this); - } - } - } - - switch (message.Buffer[0]) - { - //Handle reliable receives - case (byte)MessageType.Reliable: - await ReliableMessageReceive(message); - break; - - //Handle acknowledgments - case (byte)UdpSendOption.Acknowledgement: - AcknowledgementMessageReceive(message.Buffer); - break; - - //We need to acknowledge hello and ping messages but dont want to invoke any events! - case (byte)UdpSendOption.Ping: - await ProcessReliableReceive(message.Buffer, 1); - Statistics.LogHelloReceive(message.Length); - break; - case (byte)UdpSendOption.Hello: - await ProcessReliableReceive(message.Buffer, 1); - Statistics.LogHelloReceive(message.Length); - break; - - case (byte)UdpSendOption.Disconnect: - using (var reader = message.Copy(1)) - { - await DisconnectRemote("The remote sent a disconnect request", reader); - } - break; - - //Treat everything else as unreliable - default: - using (var reader = message.Copy(1)) - { - await InvokeDataReceived(reader, MessageType.Unreliable); - } - Statistics.LogUnreliableReceive(message.Length - 1, message.Length); - break; - } - } - - /// - /// Sends bytes using the unreliable UDP protocol. - /// - /// The SendOption to attach. - /// The data. - ValueTask UnreliableSend(byte sendOption, byte[] data) - { - return UnreliableSend(sendOption, data, 0, data.Length); - } - - /// - /// Sends bytes using the unreliable UDP protocol. - /// - /// The data. - /// The SendOption to attach. - /// - /// - async ValueTask UnreliableSend(byte sendOption, byte[] data, int offset, int length) - { - byte[] bytes = new byte[length + 1]; - - //Add message type - bytes[0] = sendOption; - - //Copy data into new array - Buffer.BlockCopy(data, offset, bytes, bytes.Length - length, length); - - //Write to connection - await WriteBytesToConnection(bytes, bytes.Length); - - Statistics.LogUnreliableSend(length, bytes.Length); - } - - /// - /// Sends a hello packet to the remote endpoint. - /// - /// - /// The callback to invoke when the hello packet is acknowledged. - protected ValueTask SendHello(byte[] bytes, Action acknowledgeCallback) - { - //First byte of handshake is version indicator so add data after - byte[] actualBytes; - if (bytes == null) - { - actualBytes = new byte[1]; - } - else - { - actualBytes = new byte[bytes.Length + 1]; - Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length); - } - - return HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback); - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _isDisposing = true; - - Stop(); - DisposeKeepAliveTimer(); - DisposeReliablePackets(); - } - - base.Dispose(disposing); - } - } -} diff --git a/src/Impostor.Hazel/Udp/UdpConnectionListener.cs b/src/Impostor.Hazel/Udp/UdpConnectionListener.cs deleted file mode 100644 index 573a00cbf..000000000 --- a/src/Impostor.Hazel/Udp/UdpConnectionListener.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Concurrent; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Microsoft.Extensions.ObjectPool; -using Serilog; - -namespace Impostor.Hazel.Udp -{ - /// - /// Listens for new UDP connections and creates UdpConnections for them. - /// - /// - public class UdpConnectionListener : NetworkConnectionListener - { - private static readonly ILogger Logger = Log.ForContext(); - - /// - /// A callback for early connection rejection. - /// * Return false to reject connection. - /// * A null response is ok, we just won't send anything. - /// - public AcceptConnectionCheck AcceptConnection; - public delegate bool AcceptConnectionCheck(IPEndPoint endPoint, byte[] input, out byte[] response); - - private readonly UdpClient _socket; - private readonly ObjectPool _readerPool; - private readonly MemoryPool _pool; - private readonly Timer _reliablePacketTimer; - private readonly ConcurrentDictionary _allConnections; - private readonly CancellationTokenSource _stoppingCts; - private readonly UdpConnectionRateLimit _connectionRateLimit; - private Task _executingTask; - - /// - /// Creates a new UdpConnectionListener for the given , port and . - /// - /// The endpoint to listen on. - /// - public UdpConnectionListener(IPEndPoint endPoint, ObjectPool readerPool, IPMode ipMode = IPMode.IPv4) - { - EndPoint = endPoint; - IPMode = ipMode; - - _readerPool = readerPool; - _pool = MemoryPool.Shared; - _socket = new UdpClient(endPoint); - - try - { - _socket.DontFragment = false; - } - catch (SocketException) - { - } - - _reliablePacketTimer = new Timer(ManageReliablePackets, null, 100, Timeout.Infinite); - - _allConnections = new ConcurrentDictionary(); - - _stoppingCts = new CancellationTokenSource(); - _stoppingCts.Token.Register(() => - { - _socket.Dispose(); - }); - - _connectionRateLimit = new UdpConnectionRateLimit(); - } - - public int ConnectionCount => this._allConnections.Count; - - private async void ManageReliablePackets(object state) - { - foreach (var kvp in _allConnections) - { - var sock = kvp.Value; - await sock.ManageReliablePackets(); - } - - try - { - this._reliablePacketTimer.Change(100, Timeout.Infinite); - } - catch { } - } - - /// - public override Task StartAsync() - { - // Store the task we're executing - _executingTask = Task.Factory.StartNew(ListenAsync, TaskCreationOptions.LongRunning); - - // If the task is completed then return it, this will bubble cancellation and failure to the caller - if (_executingTask.IsCompleted) - { - return _executingTask; - } - - // Otherwise it's running - return Task.CompletedTask; - } - - private async Task StopAsync() - { - // Stop called without start - if (_executingTask == null) - { - return; - } - - try - { - // Signal cancellation to the executing method - _stoppingCts.Cancel(); - } - finally - { - // Wait until the task completes or the timeout triggers - await Task.WhenAny(_executingTask, Task.Delay(TimeSpan.FromSeconds(5))); - } - } - - /// - /// Instructs the listener to begin listening. - /// - private async Task ListenAsync() - { - try - { - while (!_stoppingCts.IsCancellationRequested) - { - UdpReceiveResult data; - - try - { - data = await _socket.ReceiveAsync(); - - if (data.Buffer.Length == 0) - { - Logger.Fatal("Hazel read 0 bytes from UDP server socket."); - continue; - } - } - catch (SocketException) - { - // Client no longer reachable, pretend it didn't happen - continue; - } - catch (ObjectDisposedException) - { - // Socket was disposed, don't care. - return; - } - - // Get client from active clients - if (!_allConnections.TryGetValue(data.RemoteEndPoint, out var client)) - { - // Check for malformed connection attempts - if (data.Buffer[0] != (byte)UdpSendOption.Hello) - { - continue; - } - - // Check rateLimit. - if (!_connectionRateLimit.IsAllowed(data.RemoteEndPoint.Address)) - { - Logger.Warning("Ratelimited connection attempt from {0}.", data.RemoteEndPoint); - continue; - } - - // Create new client - client = new UdpServerConnection(this, data.RemoteEndPoint, IPMode, _readerPool); - - // Store the client - if (!_allConnections.TryAdd(data.RemoteEndPoint, client)) - { - throw new HazelException("Failed to add a connection. This should never happen."); - } - - // Activate the reader loop of the client - await client.StartAsync(); - } - - // Write to client. - await client.Pipeline.Writer.WriteAsync(data.Buffer); - } - } - catch (Exception e) - { - Logger.Error(e, "Listen loop error"); - } - } - -#if DEBUG - public int TestDropRate = -1; - private int dropCounter = 0; -#endif - - /// - /// Sends data from the listener socket. - /// - /// The bytes to send. - /// The endpoint to send to. - internal async ValueTask SendData(byte[] bytes, int length, IPEndPoint endPoint) - { - if (length > bytes.Length) return; - -#if DEBUG - if (TestDropRate > 0) - { - if (Interlocked.Increment(ref dropCounter) % TestDropRate == 0) - { - return; - } - } -#endif - - try - { - await _socket.SendAsync(bytes, length, endPoint); - } - catch (SocketException e) - { - Logger.Error(e, "Could not send data as a SocketException occurred"); - } - catch (ObjectDisposedException) - { - //Keep alive timer probably ran, ignore - return; - } - } - - /// - /// Sends data from the listener socket. - /// - /// The bytes to send. - /// - /// The endpoint to send to. - internal void SendDataSync(byte[] bytes, int length, IPEndPoint endPoint) - { - try - { - _socket.Send(bytes, length, endPoint); - } - catch (SocketException e) - { - Logger.Error(e, "Could not send data sync as a SocketException occurred"); - } - } - - /// - /// Removes a virtual connection from the list. - /// - /// The endpoint of the virtual connection. - internal void RemoveConnectionTo(EndPoint endPoint) - { - this._allConnections.TryRemove(endPoint, out var conn); - } - - /// - public override async ValueTask DisposeAsync() - { - foreach (var kvp in _allConnections) - { - kvp.Value.Dispose(); - } - - await StopAsync(); - - await _reliablePacketTimer.DisposeAsync(); - - _connectionRateLimit.Dispose(); - - await base.DisposeAsync(); - } - } -} diff --git a/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs b/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs deleted file mode 100644 index 64881d3da..000000000 --- a/src/Impostor.Hazel/Udp/UdpConnectionRateLimit.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Net; -using System.Threading; -using Serilog; - -namespace Impostor.Hazel.Udp -{ - public class UdpConnectionRateLimit : IDisposable - { - private static readonly ILogger Logger = Log.ForContext(); - - // Allow burst to 5 connections. - // Decrease by 1 every second. - private const int MaxConnections = 5; - private const int FalloffMs = 1000; - - private readonly ConcurrentDictionary _connectionCount; - private readonly Timer _timer; - private bool _isDisposed; - - public UdpConnectionRateLimit() - { - _connectionCount = new ConcurrentDictionary(); - _timer = new Timer(UpdateRateLimit, null, FalloffMs, Timeout.Infinite); - } - - private void UpdateRateLimit(object state) - { - try - { - foreach (var pair in _connectionCount) - { - var count = pair.Value - 1; - if (count > 0) - { - _connectionCount.TryUpdate(pair.Key, count, pair.Value); - } - else - { - _connectionCount.TryRemove(pair); - } - } - } - catch (Exception e) - { - Logger.Error(e, "Exception caught in UpdateRateLimit."); - } - finally - { - if (!_isDisposed) - { - _timer.Change(FalloffMs, Timeout.Infinite); - } - } - } - - public bool IsAllowed(IPAddress key) - { - if (_connectionCount.TryGetValue(key, out var value) && value >= MaxConnections) - { - return false; - } - - _connectionCount.AddOrUpdate(key, _ => 1, (_, i) => i + 1); - return true; - } - - public void Dispose() - { - _isDisposed = true; - _timer.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Impostor.Hazel/Udp/UdpServerConnection.cs b/src/Impostor.Hazel/Udp/UdpServerConnection.cs deleted file mode 100644 index 22eed988f..000000000 --- a/src/Impostor.Hazel/Udp/UdpServerConnection.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using Impostor.Api.Net.Messages; -using Microsoft.Extensions.ObjectPool; - -namespace Impostor.Hazel.Udp -{ - /// - /// Represents a servers's connection to a client that uses the UDP protocol. - /// - /// - internal sealed class UdpServerConnection : UdpConnection - { - /// - /// The connection listener that we use the socket of. - /// - /// - /// Udp server connections utilize the same socket in the listener for sends/receives, this is the listener that - /// created this connection and is hence the listener this conenction sends and receives via. - /// - public UdpConnectionListener Listener { get; private set; } - - /// - /// Creates a UdpConnection for the virtual connection to the endpoint. - /// - /// The listener that created this connection. - /// The endpoint that we are connected to. - /// The IPMode we are connected using. - internal UdpServerConnection(UdpConnectionListener listener, IPEndPoint endPoint, IPMode IPMode, ObjectPool readerPool) : base(listener, readerPool) - { - this.Listener = listener; - this.RemoteEndPoint = endPoint; - this.EndPoint = endPoint; - this.IPMode = IPMode; - - State = ConnectionState.Connected; - this.InitializeKeepAliveTimer(); - } - - /// - protected override async ValueTask WriteBytesToConnection(byte[] bytes, int length) - { - await Listener.SendData(bytes, length, RemoteEndPoint); - } - - /// - /// - /// This will always throw a HazelException. - /// - public override ValueTask ConnectAsync(byte[] bytes = null) - { - throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?"); - } - - /// - /// Sends a disconnect message to the end point. - /// - protected override async ValueTask SendDisconnect(MessageWriter data = null) - { - lock (this) - { - if (this._state != ConnectionState.Connected) return false; - this._state = ConnectionState.NotConnected; - } - - var bytes = EmptyDisconnectBytes; - if (data != null && data.Length > 0) - { - if (data.SendOption != MessageType.Unreliable) throw new ArgumentException("Disconnect messages can only be unreliable."); - - bytes = data.ToByteArray(true); - bytes[0] = (byte)UdpSendOption.Disconnect; - } - - try - { - await Listener.SendData(bytes, bytes.Length, RemoteEndPoint); - } - catch { } - - return true; - } - - protected override void Dispose(bool disposing) - { - Listener.RemoveConnectionTo(RemoteEndPoint); - - if (disposing) - { - SendDisconnect(); - } - - base.Dispose(disposing); - } - } -} diff --git a/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj b/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj index c59fa8784..7835c4ffb 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj +++ b/src/Impostor.Patcher/Impostor.Patcher.Cli/Impostor.Patcher.Cli.csproj @@ -1,19 +1,19 @@ - - Impostor.Cli - net5.0 - win-x64;linux-x64;linux-arm;linux-arm64;osx-x64 - Exe - true - + + Impostor.Cli + net5.0 + win-x64;linux-x64;linux-arm;linux-arm64;osx-x64 + Exe + true + - - - + + + - - - + + + diff --git a/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs b/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs index 76653a1b5..26c00d9cc 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Cli/Program.cs @@ -23,7 +23,7 @@ internal static Task Main(string[] args) "--name", () => AmongUsModifier.DefaultRegionName, "Name for server region" - ) + ), }; rootCommand.Handler = CommandHandler.Create((address, name) => diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs index 95f552480..d3b6de7e5 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/AmongUsModifier.cs @@ -63,7 +63,7 @@ private string FindProtonAppData() var libraries = new List { - steamApps + steamApps, }; var vdf = Path.Combine(steamApps, "libraryfolders.vdf"); @@ -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; } /// @@ -171,12 +174,11 @@ private bool WriteIp(IPAddress ipAddress, ushort port) var ip = ipAddress.ToString(); var region = new RegionInfo(RegionName, ip, new[] { - new ServerInfo($"{RegionName}-Master-1", ip, port) + new ServerInfo($"{RegionName}-Master-1", ip, port), }); region.Serialize(writer); - OnSaved(ip, port); return true; } } @@ -244,4 +246,4 @@ private void OnSaved(string ipAddress, ushort port) public event EventHandler Error; public event EventHandler Saved; } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs index b256156cd..56bf29743 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Configuration.cs @@ -62,4 +62,4 @@ public void AddIp(string ip) } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs index 7211d5d35..b7925d3d1 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/ErrorEventArgs.cs @@ -11,4 +11,4 @@ public ErrorEventArgs(string message) public string Message { get; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs index c91d07197..a0f08ae47 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Events/SavedEventArgs.cs @@ -13,4 +13,4 @@ public SavedEventArgs(string ipAddress, ushort port) public string IpAddress { get; } public ushort Port { get; } } -} \ No newline at end of file +} 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..3039810f9 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Impostor.Patcher.Shared.csproj @@ -1,12 +1,11 @@ - - netstandard2.0;netstandard2.1 - 1.0.0 - + + netstandard2.0;netstandard2.1 + + + + + - - - - diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs index 01b74d1d8..77fbb6be0 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/RegionInfo.cs @@ -45,4 +45,4 @@ public static RegionInfo Deserialize(BinaryReader reader) return new RegionInfo(name, ping, servers); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs b/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs index 7203c8489..4768377a3 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.Shared/Innersloth/ServerInfo.cs @@ -15,7 +15,7 @@ public ServerInfo(string name, string ip, ushort port) Ip = ip; Port = port; } - + public void Serialize(BinaryWriter writer) { writer.Write(Name); @@ -30,8 +30,8 @@ public static ServerInfo Deserialize(BinaryReader reader) var ip = new IPAddress(reader.ReadBytes(4)).ToString(); var port = reader.ReadUInt16(); var unknown = reader.ReadInt32(); - + return new ServerInfo(name, ip, port); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs index 0f6320b4f..4667c454f 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.Designer.cs @@ -81,7 +81,7 @@ private void InitializeComponent() this.lblUrl.Name = "lblUrl"; this.lblUrl.Size = new System.Drawing.Size(212, 13); this.lblUrl.TabIndex = 5; - this.lblUrl.Text = "https://github.com/AeonLucid/Impostor"; + this.lblUrl.Text = "https://github.com/Impostor/Impostor"; this.lblUrl.Click += new System.EventHandler(this.lblUrl_Click); // // label3 @@ -138,4 +138,4 @@ private void InitializeComponent() private System.Windows.Forms.Label label3; private System.Windows.Forms.ComboBox comboIp; } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs index 5c06669e0..dd3696f5a 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Forms/FrmMain.cs @@ -95,7 +95,7 @@ private async void buttonLaunch_Click(object sender, EventArgs e) private void lblUrl_Click(object sender, EventArgs e) { - Process.Start("https://github.com/AeonLucid/Impostor"); + Process.Start("https://github.com/Impostor/Impostor"); } private void RefreshComboIps() @@ -111,4 +111,4 @@ private void RefreshComboIps() } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj index 71494365f..83f6e269e 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj +++ b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Impostor.Patcher.WinForms.csproj @@ -1,22 +1,22 @@  - - - Impostor - {804CF172-0C87-4423-9688-BD97D549891E} - WinExe - net472 - true - Copyright © AeonLucid 2020 - icon.ico - true - true - - - - - - - + + Impostor + {804CF172-0C87-4423-9688-BD97D549891E} + WinExe + net472 + true + Copyright © AeonLucid 2020 + icon.ico + true + true + - \ No newline at end of file + + + + + + + + diff --git a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs index 7ca903533..f48149064 100644 --- a/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs +++ b/src/Impostor.Patcher/Impostor.Patcher.WinForms/Program.cs @@ -14,4 +14,4 @@ private static void Main() Application.Run(new FrmMain()); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Plugins.Debugger/DebugPlugin.cs b/src/Impostor.Plugins.Debugger/DebugPlugin.cs index 8f57e89e0..a978619bb 100644 --- a/src/Impostor.Plugins.Debugger/DebugPlugin.cs +++ b/src/Impostor.Plugins.Debugger/DebugPlugin.cs @@ -10,4 +10,4 @@ namespace Impostor.Plugins.Debugger public class DebugPlugin : PluginBase { } -} \ No newline at end of file +} diff --git a/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs b/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs index cc36ce661..c39ce314a 100644 --- a/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs +++ b/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs @@ -32,4 +32,4 @@ public void ConfigureHost(IHostBuilder host) }); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj b/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj index 9518e48e9..8fce4f886 100644 --- a/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj +++ b/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj @@ -1,16 +1,16 @@  - - net5.0 - Library - + + net5.0 + Library + - - - + + + - - - + + + - \ No newline at end of file + diff --git a/src/Impostor.Plugins.Example/ExamplePlugin.cs b/src/Impostor.Plugins.Example/ExamplePlugin.cs index dafba7c47..73fea6ad6 100644 --- a/src/Impostor.Plugins.Example/ExamplePlugin.cs +++ b/src/Impostor.Plugins.Example/ExamplePlugin.cs @@ -1,4 +1,6 @@ using System.Threading.Tasks; +using Impostor.Api.Games.Managers; +using Impostor.Api.Innersloth; using Impostor.Api.Plugins; using Microsoft.Extensions.Logging; @@ -12,16 +14,23 @@ namespace Impostor.Plugins.Example public class ExamplePlugin : PluginBase { private readonly ILogger _logger; + private readonly IGameManager _gameManager; - public ExamplePlugin(ILogger logger) + public ExamplePlugin(ILogger logger, IGameManager gameManager) { _logger = logger; + _gameManager = gameManager; } - public override ValueTask EnableAsync() + public override async ValueTask EnableAsync() { _logger.LogInformation("Example is being enabled."); - return default; + + var game = await _gameManager.CreateAsync(new GameOptionsData()); + game.DisplayName = "Example game"; + await game.SetPrivacyAsync(true); + + _logger.LogInformation("Created game {0}.", game.Code.Code); } public override ValueTask DisableAsync() 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.Plugins.Example/Handlers/GameEventListener.cs b/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs index be2d0f3ff..0125a19a1 100644 --- a/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs +++ b/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs @@ -1,64 +1,75 @@ -using System; -using Impostor.Api.Events; +using Impostor.Api.Events; +using Microsoft.Extensions.Logging; namespace Impostor.Plugins.Example.Handlers { public class GameEventListener : IEventListener { - [EventListener(EventPriority.Monitor)] - public void OnGame(IGameEvent e) + private readonly ILogger _logger; + + public GameEventListener(ILogger logger) { - Console.WriteLine(e.GetType().Name + " triggered"); + _logger = logger; } [EventListener] public void OnGameCreated(IGameCreatedEvent e) { - Console.WriteLine("Game > created"); + _logger.LogInformation("Game {code} > created", e.Game.Code); } [EventListener] public void OnGameStarting(IGameStartingEvent e) { - Console.WriteLine("Game > starting"); + _logger.LogInformation("Game {code} > starting", e.Game.Code); } [EventListener] public void OnGameStarted(IGameStartedEvent e) { - Console.WriteLine("Game > started"); + _logger.LogInformation("Game {code} > started", e.Game.Code); foreach (var player in e.Game.Players) { - var info = player.Character.PlayerInfo; + var info = player.Character!.PlayerInfo; - Console.WriteLine($"- {info.PlayerName} {info.IsImpostor}"); + _logger.LogInformation("- {player} is {role}", info.PlayerName, info.IsImpostor ? "an impostor" : "a crewmate"); } } [EventListener] public void OnGameEnded(IGameEndedEvent e) { - Console.WriteLine("Game > ended"); - Console.WriteLine("- Reason: " + e.GameOverReason); + _logger.LogInformation("Game {code} > ended because {reason}", e.Game.Code, e.GameOverReason); } [EventListener] public void OnGameDestroyed(IGameDestroyedEvent e) { - Console.WriteLine("Game > destroyed"); + _logger.LogInformation("Game {code} > destroyed", e.Game.Code); + } + + [EventListener] + public void OnGameHostChanged(IGameHostChangedEvent e) + { + _logger.LogInformation( + "Game {code} > changed host from {previous} to {new}", + e.Game.Code, + e.PreviousHost.Character?.PlayerInfo.PlayerName, + e.NewHost != null ? e.NewHost.Character?.PlayerInfo.PlayerName : "none" + ); } [EventListener] public void OnPlayerJoined(IGamePlayerJoinedEvent e) { - Console.WriteLine("Player joined a game."); + _logger.LogInformation("Game {code} > {player} joined", e.Game.Code, e.Player.Client.Name); } [EventListener] public void OnPlayerLeftGame(IGamePlayerLeftEvent e) { - Console.WriteLine("Player left a game."); + _logger.LogInformation("Game {code} > {player} left", e.Game.Code, e.Player.Client.Name); } } } diff --git a/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs b/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs index 847532c50..84948a71c 100644 --- a/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs +++ b/src/Impostor.Plugins.Example/Handlers/MeetingEventListener.cs @@ -1,21 +1,28 @@ -using System; -using Impostor.Api.Events; +using Impostor.Api.Events; using Impostor.Api.Events.Meeting; +using Microsoft.Extensions.Logging; namespace Impostor.Plugins.Example.Handlers { public class MeetingEventListener : IEventListener { + private readonly ILogger _logger; + + public MeetingEventListener(ILogger logger) + { + _logger = logger; + } + [EventListener] public void OnMeetingStarted(IMeetingStartedEvent e) { - Console.WriteLine("Meeting > started"); + _logger.LogInformation("Meeting > started"); } [EventListener] public void OnMeetingEnded(IMeetingEndedEvent e) { - Console.WriteLine("Meeting > ended"); + _logger.LogInformation("Meeting > ended"); } } } diff --git a/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs b/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs index 0190d3b14..999cfc174 100644 --- a/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs +++ b/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs @@ -10,7 +10,7 @@ namespace Impostor.Plugins.Example.Handlers { public class PlayerEventListener : IEventListener { - private static readonly Random Random = new Random(); + private readonly Random _random = new Random(); private readonly ILogger _logger; @@ -22,48 +22,45 @@ public PlayerEventListener(ILogger logger) [EventListener] public void OnPlayerSpawned(IPlayerSpawnedEvent e) { - _logger.LogDebug(e.PlayerControl.PlayerInfo.PlayerName + " spawned"); + _logger.LogInformation("Player {player} > spawned", e.PlayerControl.PlayerInfo.PlayerName); // Need to make a local copy because it might be possible that // the event gets changed after being handled. var clientPlayer = e.ClientPlayer; var playerControl = e.PlayerControl; - /* Task.Run(async () => { - Console.WriteLine("Starting player task."); + _logger.LogDebug("Starting player task"); // Give the player time to load. await Task.Delay(TimeSpan.FromSeconds(3)); - while (clientPlayer.Client.Connection != null && - clientPlayer.Client.Connection.IsConnected) + while (clientPlayer.Client.Connection != null && clientPlayer.Client.Connection.IsConnected) { // Modify player properties. - await playerControl.SetColorAsync((byte) Random.Next(1, 9)); - await playerControl.SetHatAsync((uint) Random.Next(1, 9)); - await playerControl.SetSkinAsync((uint) Random.Next(1, 9)); - await playerControl.SetPetAsync((uint) Random.Next(1, 9)); + await playerControl.SetColorAsync((ColorType)_random.Next(1, 9)); + await playerControl.SetHatAsync((HatType)_random.Next(1, 9)); + await playerControl.SetSkinAsync((SkinType)_random.Next(1, 9)); + await playerControl.SetPetAsync((PetType)_random.Next(1, 9)); await Task.Delay(TimeSpan.FromMilliseconds(5000)); } - _logger.LogDebug("Stopping player task."); + _logger.LogDebug("Stopping player task"); }); - */ } [EventListener] public void OnPlayerDestroyed(IPlayerDestroyedEvent e) { - _logger.LogDebug(e.PlayerControl.PlayerInfo.PlayerName + " destroyed"); + _logger.LogInformation("Player {player} > destroyed", e.PlayerControl.PlayerInfo.PlayerName); } [EventListener] public async ValueTask OnPlayerChat(IPlayerChatEvent e) { - _logger.LogDebug(e.PlayerControl.PlayerInfo.PlayerName + " said " + e.Message); + _logger.LogInformation("Player {player} > said {message}", e.PlayerControl.PlayerInfo.PlayerName, e.Message); if (e.Message == "test") { @@ -94,7 +91,25 @@ public async ValueTask OnPlayerChat(IPlayerChatEvent e) [EventListener] public void OnPlayerStartMeetingEvent(IPlayerStartMeetingEvent e) { - _logger.LogDebug($"Player {e.PlayerControl.PlayerInfo.PlayerName} start meeting, reason: " + (e.Body==null ? "Emergency call button" : "Found the body of the player "+e.Body.PlayerInfo.PlayerName)); + _logger.LogInformation("Player {player} > started meeting, reason: {reason}", e.PlayerControl.PlayerInfo.PlayerName, e.Body == null ? "Emergency call button" : "Found the body of the player " + e.Body.PlayerInfo.PlayerName); + } + + [EventListener] + public void OnPlayerEnterVentEvent(IPlayerEnterVentEvent e) + { + _logger.LogInformation("Player {player} entered the vent in {vent}", e.PlayerControl.PlayerInfo.PlayerName, e.Vent.Name); + } + + [EventListener] + public void OnPlayerExitVentEvent(IPlayerExitVentEvent e) + { + _logger.LogInformation("Player {player} exited the vent in {vent}", e.PlayerControl.PlayerInfo.PlayerName, e.Vent.Name); + } + + [EventListener] + public void OnPlayerVentEvent(IPlayerVentEvent e) + { + _logger.LogInformation("Player {player} vented to {vent}", e.PlayerControl.PlayerInfo.PlayerName, e.NewVent.Name); } } } diff --git a/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj b/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj index dd4724dc1..f0ebef3dc 100644 --- a/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj +++ b/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj @@ -3,7 +3,7 @@ netstandard2.1 - + diff --git a/src/Impostor.Server/Config/AnnouncementsServerConfig.cs b/src/Impostor.Server/Config/AnnouncementsServerConfig.cs new file mode 100644 index 000000000..43f8f8ff8 --- /dev/null +++ b/src/Impostor.Server/Config/AnnouncementsServerConfig.cs @@ -0,0 +1,22 @@ +using Impostor.Server.Utils; + +namespace Impostor.Server.Config +{ + internal class AnnouncementsServerConfig + { + public const string Section = "AnnouncementsServer"; + + private string? _resolvedListenIp; + + 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/Config/AuthServerConfig.cs b/src/Impostor.Server/Config/AuthServerConfig.cs new file mode 100644 index 000000000..72c8efe4a --- /dev/null +++ b/src/Impostor.Server/Config/AuthServerConfig.cs @@ -0,0 +1,27 @@ +using System.IO; +using Impostor.Server.Utils; + +namespace Impostor.Server.Config +{ + internal class AuthServerConfig + { + public const string Section = "AuthServer"; + + private string? _resolvedListenIp; + + public bool Enabled { get; set; } = false; + + public string ListenIp { get; set; } = "0.0.0.0"; + + public ushort ListenPort { get; set; } = 22025; + + public string Certificate { get; set; } = Path.Combine("dtls", "certificate.pem"); + + public string PrivateKey { get; set; } = Path.Combine("dtls", "key.pem"); + + public string ResolveListenIp() + { + return _resolvedListenIp ??= IpUtils.ResolveIp(ListenIp); + } + } +} diff --git a/src/Impostor.Server/Config/DebugConfig.cs b/src/Impostor.Server/Config/DebugConfig.cs index 630d1b488..72529de35 100644 --- a/src/Impostor.Server/Config/DebugConfig.cs +++ b/src/Impostor.Server/Config/DebugConfig.cs @@ -6,6 +6,6 @@ public class DebugConfig public bool GameRecorderEnabled { get; set; } - public string GameRecorderPath { get; set; } + public string GameRecorderPath { get; set; } = string.Empty; } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Config/DisconnectMessages.cs b/src/Impostor.Server/Config/DisconnectMessages.cs index a86735f8f..6bf8e7286 100644 --- a/src/Impostor.Server/Config/DisconnectMessages.cs +++ b/src/Impostor.Server/Config/DisconnectMessages.cs @@ -16,4 +16,4 @@ public static class DisconnectMessages public const string UsernameIllegalCharacters = "Your username contains illegal characters, please remove them."; } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Config/ServerConfig.cs b/src/Impostor.Server/Config/ServerConfig.cs index 1c584333f..7ed3ae64c 100644 --- a/src/Impostor.Server/Config/ServerConfig.cs +++ b/src/Impostor.Server/Config/ServerConfig.cs @@ -4,11 +4,11 @@ namespace Impostor.Server.Config { internal class ServerConfig { + public const string Section = "Server"; + private string? _resolvedPublicIp; private string? _resolvedListenIp; - public const string Section = "Server"; - public string PublicIp { get; set; } = "127.0.0.1"; public ushort PublicPort { get; set; } = 22023; diff --git a/src/Impostor.Server/Config/ServerRedirectorConfig.cs b/src/Impostor.Server/Config/ServerRedirectorConfig.cs index 0ccfa0d4c..686ad7fdb 100644 --- a/src/Impostor.Server/Config/ServerRedirectorConfig.cs +++ b/src/Impostor.Server/Config/ServerRedirectorConfig.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Impostor.Server.Config { @@ -10,15 +11,17 @@ public class ServerRedirectorConfig public bool Master { get; set; } - public NodeLocator Locator { get; set; } + public NodeLocator? Locator { get; set; } - public List Nodes { get; set; } + public List? Nodes { get; set; } public class NodeLocator { + [AllowNull] public string Redis { get; set; } + [AllowNull] public string UdpMasterEndpoint { get; set; } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Config/ServerRedirectorNode.cs b/src/Impostor.Server/Config/ServerRedirectorNode.cs index d11b60ff3..c47951234 100644 --- a/src/Impostor.Server/Config/ServerRedirectorNode.cs +++ b/src/Impostor.Server/Config/ServerRedirectorNode.cs @@ -1,9 +1,13 @@ -namespace Impostor.Server.Config +using System.Diagnostics.CodeAnalysis; + +namespace Impostor.Server.Config { public class ServerRedirectorNode { + [AllowNull] public string Ip { get; set; } + [AllowNull] public ushort Port { get; set; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Constants.cs b/src/Impostor.Server/Constants.cs index 62d90b290..ce0114aac 100644 --- a/src/Impostor.Server/Constants.cs +++ b/src/Impostor.Server/Constants.cs @@ -5,4 +5,4 @@ internal static class Constants public const int SpawnTimeout = 2500; public const int ConnectionTimeout = 2500; } -} \ 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/EventHandler.cs b/src/Impostor.Server/Events/EventHandler.cs index 190f7f398..e185c9a1f 100644 --- a/src/Impostor.Server/Events/EventHandler.cs +++ b/src/Impostor.Server/Events/EventHandler.cs @@ -5,20 +5,20 @@ namespace Impostor.Server.Events { internal readonly struct EventHandler { - public EventHandler(IEventListener o, IRegisteredEventListener listener) + public EventHandler(IEventListener? o, IRegisteredEventListener listener) { Object = o; Listener = listener; } - public IEventListener Object { get; } + public IEventListener? Object { get; } public IRegisteredEventListener Listener { get; } - public void Deconstruct(out IEventListener o, out IRegisteredEventListener listener) + public void Deconstruct(out IEventListener? o, out IRegisteredEventListener listener) { o = Object; listener = Listener; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Events/EventManager.cs b/src/Impostor.Server/Events/EventManager.cs index 5625c4dd9..4ab2d1536 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; @@ -28,7 +27,7 @@ public EventManager(ILogger logger, IServiceProvider serviceProvid } /// - public IDisposable RegisterListener(TListener listener, Func, Task> invoker = null) + public IDisposable RegisterListener(TListener listener, Func, Task>? invoker = null) where TListener : IEventListener { if (listener == null) @@ -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/GameEndedEvent.cs b/src/Impostor.Server/Events/Game/GameEndedEvent.cs index 4ec4dcf92..b0704cfc3 100644 --- a/src/Impostor.Server/Events/Game/GameEndedEvent.cs +++ b/src/Impostor.Server/Events/Game/GameEndedEvent.cs @@ -13,7 +13,7 @@ public GameEndedEvent(IGame game, GameOverReason gameOverReason) } public IGame Game { get; } - + public GameOverReason GameOverReason { get; } } } diff --git a/src/Impostor.Server/Events/Game/GameHostChangedEvent.cs b/src/Impostor.Server/Events/Game/GameHostChangedEvent.cs new file mode 100644 index 000000000..3aa00f225 --- /dev/null +++ b/src/Impostor.Server/Events/Game/GameHostChangedEvent.cs @@ -0,0 +1,22 @@ +using Impostor.Api.Events; +using Impostor.Api.Games; +using Impostor.Api.Net; + +namespace Impostor.Server.Events +{ + public class GameHostChangedEvent : IGameHostChangedEvent + { + public GameHostChangedEvent(IGame game, IClientPlayer previousHost, IClientPlayer? newHost) + { + Game = game; + PreviousHost = previousHost; + NewHost = newHost; + } + + public IGame Game { get; } + + public IClientPlayer PreviousHost { get; } + + public IClientPlayer? NewHost { get; } + } +} diff --git a/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs index 7b7eb22fe..34ec95ed2 100644 --- a/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs +++ b/src/Impostor.Server/Events/Game/Player/PlayerChatEvent.cs @@ -22,5 +22,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/PlayerEnterVentEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerEnterVentEvent.cs new file mode 100644 index 000000000..b072dc799 --- /dev/null +++ b/src/Impostor.Server/Events/Game/Player/PlayerEnterVentEvent.cs @@ -0,0 +1,27 @@ +using Impostor.Api.Events.Player; +using Impostor.Api.Games; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; + +namespace Impostor.Server.Events.Player +{ + public class PlayerEnterVentEvent : IPlayerEnterVentEvent + { + public PlayerEnterVentEvent(IGame game, IClientPlayer sender, IInnerPlayerControl innerPlayerPhysics, IVent vent) + { + Game = game; + ClientPlayer = sender; + PlayerControl = innerPlayerPhysics; + Vent = vent; + } + + public IGame Game { get; } + + public IClientPlayer ClientPlayer { get; } + + public IInnerPlayerControl PlayerControl { get; } + + public IVent Vent { get; } + } +} diff --git a/src/Impostor.Server/Events/Game/Player/PlayerExitVentEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerExitVentEvent.cs new file mode 100644 index 000000000..2343c708d --- /dev/null +++ b/src/Impostor.Server/Events/Game/Player/PlayerExitVentEvent.cs @@ -0,0 +1,27 @@ +using Impostor.Api.Events.Player; +using Impostor.Api.Games; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; + +namespace Impostor.Server.Events.Player +{ + public class PlayerExitVentEvent : IPlayerExitVentEvent + { + public PlayerExitVentEvent(IGame game, IClientPlayer sender, IInnerPlayerControl innerPlayerPhysics, IVent vent) + { + Game = game; + ClientPlayer = sender; + PlayerControl = innerPlayerPhysics; + Vent = vent; + } + + public IGame Game { get; } + + public IClientPlayer ClientPlayer { get; } + + public IInnerPlayerControl PlayerControl { get; } + + public IVent Vent { get; } + } +} 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/Events/Game/Player/PlayerVentEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs index 0798d40b9..7cfdcd027 100644 --- a/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs +++ b/src/Impostor.Server/Events/Game/Player/PlayerVentEvent.cs @@ -8,13 +8,12 @@ namespace Impostor.Server.Events.Player { public class PlayerVentEvent : IPlayerVentEvent { - public PlayerVentEvent(IGame game, IClientPlayer sender, IInnerPlayerControl innerPlayerPhysics, VentLocation ventId, bool ventEnter) + public PlayerVentEvent(IGame game, IClientPlayer sender, IInnerPlayerControl innerPlayerPhysics, IVent vent) { Game = game; ClientPlayer = sender; PlayerControl = innerPlayerPhysics; - VentId = ventId; - VentEnter = ventEnter; + NewVent = vent; } public IGame Game { get; } @@ -23,8 +22,6 @@ public PlayerVentEvent(IGame game, IClientPlayer sender, IInnerPlayerControl inn public IInnerPlayerControl PlayerControl { get; } - public VentLocation VentId { get; } - - public bool VentEnter { get; } + public IVent NewVent { get; } } } diff --git a/src/Impostor.Server/Events/MultiDisposable.cs b/src/Impostor.Server/Events/MultiDisposable.cs index b68f06401..b1e2348b0 100644 --- a/src/Impostor.Server/Events/MultiDisposable.cs +++ b/src/Impostor.Server/Events/MultiDisposable.cs @@ -4,7 +4,7 @@ namespace Impostor.Server.Events { /// - /// Disposes multiple . + /// Disposes multiple . /// internal class MultiDisposable : IDisposable { diff --git a/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs index 479a3f641..3b39bc9b8 100644 --- a/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs +++ b/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs @@ -10,6 +10,6 @@ internal interface IRegisteredEventListener EventPriority Priority { get; } - ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider); + ValueTask InvokeAsync(object? eventHandler, object @event, IServiceProvider provider); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs index a21c3b1f6..adec183b4 100644 --- a/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs +++ b/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs @@ -19,9 +19,9 @@ public InvokedRegisteredEventListener(IRegisteredEventListener innerObject, Func public EventPriority Priority => _innerObject.Priority; - public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider) + public ValueTask InvokeAsync(object? eventHandler, object @event, IServiceProvider provider) { return new ValueTask(_invoker(() => _innerObject.InvokeAsync(eventHandler, @event, provider).AsTask())); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs index e81e8f844..394f47d37 100644 --- a/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs +++ b/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs @@ -6,8 +6,6 @@ namespace Impostor.Server.Events.Register { internal class ManualRegisteredEventListener : IRegisteredEventListener { - public Type EventType { get; } = typeof(object); - private readonly IManualEventListener _manualEventListener; public ManualRegisteredEventListener(IManualEventListener manualEventListener) @@ -15,9 +13,11 @@ public ManualRegisteredEventListener(IManualEventListener manualEventListener) _manualEventListener = manualEventListener; } + public Type EventType { get; } = typeof(object); + public EventPriority Priority => _manualEventListener.Priority; - public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider) + public ValueTask InvokeAsync(object? eventHandler, object @event, IServiceProvider provider) { if (@event is IEvent typedEvent) { diff --git a/src/Impostor.Server/Events/Register/RegisteredEventListener.cs b/src/Impostor.Server/Events/Register/RegisteredEventListener.cs index 120a45ed3..76d2a2af0 100644 --- a/src/Impostor.Server/Events/Register/RegisteredEventListener.cs +++ b/src/Impostor.Server/Events/Register/RegisteredEventListener.cs @@ -15,7 +15,7 @@ internal class RegisteredEventListener : IRegisteredEventListener private static readonly PropertyInfo IsCancelledProperty = typeof(IEventCancelable).GetProperty(nameof(IEventCancelable.IsCancelled))!; private static readonly ConcurrentDictionary Instances = new ConcurrentDictionary(); - private readonly Func _invoker; + private readonly Func _invoker; private readonly Type _eventListenerType; public RegisteredEventListener(Type eventType, MethodInfo method, EventListenerAttribute attribute, Type eventListenerType) @@ -38,12 +38,52 @@ public RegisteredEventListener(Type eventType, MethodInfo method, EventListenerA public string Method { get; } - public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider) + public static IReadOnlyList FromType(Type type) + { + return Instances.GetOrAdd(type, t => + { + return t.GetMethods() + .Where(m => !m.IsStatic && m.GetCustomAttributes(typeof(EventListenerAttribute), false).Any()) + .SelectMany(m => FromMethod(t, m)) + .ToArray(); + }); + } + + public static IEnumerable FromMethod(Type listenerType, MethodInfo methodType) + { + // Get the return type. + var returnType = methodType.ReturnType; + + if (returnType != typeof(void) && returnType != typeof(ValueTask)) + { + throw new InvalidOperationException($"The method {methodType.GetFriendlyName()} does not return void or ValueTask."); + } + + // Register the event. + foreach (var attribute in methodType.GetCustomAttributes(false)) + { + var eventType = attribute.Event; + + if (eventType == null) + { + if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType)) + { + throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}."); + } + + eventType = methodType.GetParameters()[0].ParameterType; + } + + yield return new RegisteredEventListener(eventType, methodType, attribute, listenerType); + } + } + + public ValueTask InvokeAsync(object? eventHandler, object @event, IServiceProvider provider) { return _invoker(eventHandler, @event, provider); } - private Func CreateInvoker(MethodInfo method, bool ignoreCancelled) + private Func CreateInvoker(MethodInfo method, bool ignoreCancelled) { var instance = Expression.Parameter(typeof(object), "instance"); var eventParameter = Expression.Parameter(typeof(object), "event"); @@ -119,48 +159,8 @@ private Func CreateInvoker(MethodIn throw new InvalidOperationException($"The method {method.GetFriendlyName()} must return void or ValueTask."); } - return Expression.Lambda>(invoke, instance, eventParameter, provider) + return Expression.Lambda>(invoke, instance, eventParameter, provider) .Compile(); } - - public static IReadOnlyList FromType(Type type) - { - return Instances.GetOrAdd(type, t => - { - return t.GetMethods() - .Where(m => !m.IsStatic && m.GetCustomAttributes(typeof(EventListenerAttribute), false).Any()) - .SelectMany(m => FromMethod(t, m)) - .ToArray(); - }); - } - - public static IEnumerable FromMethod(Type listenerType, MethodInfo methodType) - { - // Get the return type. - var returnType = methodType.ReturnType; - - if (returnType != typeof(void) && returnType != typeof(ValueTask)) - { - throw new InvalidOperationException($"The method {methodType.GetFriendlyName()} does not return void or ValueTask."); - } - - // Register the event. - foreach (var attribute in methodType.GetCustomAttributes(false)) - { - var eventType = attribute.Event; - - if (eventType == null) - { - if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType)) - { - throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}."); - } - - eventType = methodType.GetParameters()[0].ParameterType; - } - - yield return new RegisteredEventListener(eventType, methodType, attribute, listenerType); - } - } } } diff --git a/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs b/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs index 1446ad1eb..f2bfee254 100644 --- a/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs +++ b/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs @@ -56,4 +56,4 @@ public void Dispose() } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs index dd668c5dd..d1a66ee84 100644 --- a/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs +++ b/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs @@ -19,9 +19,9 @@ public WrappedRegisteredEventListener(IRegisteredEventListener innerObject, obje public EventPriority Priority => _innerObject.Priority; - public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider) + public ValueTask InvokeAsync(object? eventHandler, object @event, IServiceProvider provider) { return _innerObject.InvokeAsync(_object, @event, provider); } } -} \ No newline at end of file +} 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/NodeLocatorExtensions.cs b/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs index 370bca620..96ceb4d4e 100644 --- a/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs +++ b/src/Impostor.Server/Extensions/NodeLocatorExtensions.cs @@ -10,4 +10,4 @@ public static async ValueTask ExistsAsync(this INodeLocator nodeLocator, s return await nodeLocator.FindAsync(gameCode) != null; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs b/src/Impostor.Server/Extensions/ServiceProviderExtensions.cs similarity index 60% rename from src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs rename to src/Impostor.Server/Extensions/ServiceProviderExtensions.cs index 56c738071..eee14806d 100644 --- a/src/Impostor.Hazel/Extensions/ServiceProviderExtensions.cs +++ b/src/Impostor.Server/Extensions/ServiceProviderExtensions.cs @@ -1,19 +1,20 @@ -using Microsoft.Extensions.DependencyInjection; +using Impostor.Server.Events.Player; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.ObjectPool; -namespace Impostor.Hazel.Extensions +namespace Impostor.Server { public static class ServiceProviderExtensions { - public static void AddHazel(this IServiceCollection services) + public static void AddEventPools(this IServiceCollection services) { services.TryAddSingleton(new DefaultObjectPoolProvider()); services.AddSingleton(serviceProvider => { var provider = serviceProvider.GetRequiredService(); - var policy = ActivatorUtilities.CreateInstance(serviceProvider); + var policy = ActivatorUtilities.CreateInstance(serviceProvider); return provider.Create(policy); }); } diff --git a/src/Impostor.Server/Extensions/TypeExtensions.cs b/src/Impostor.Server/Extensions/TypeExtensions.cs index 55d42fbda..a49cb5e2b 100644 --- a/src/Impostor.Server/Extensions/TypeExtensions.cs +++ b/src/Impostor.Server/Extensions/TypeExtensions.cs @@ -45,7 +45,7 @@ public static string GetFriendlyName(this Type type) /// /// The method. /// True if the parameters should be included in the name. - /// Friendly name of the method + /// Friendly name of the method. public static string GetFriendlyName(this MethodBase method, bool showParameters = true) { var str = method.Name; @@ -64,4 +64,4 @@ public static string GetFriendlyName(this MethodBase method, bool showParameters return str; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Impostor.Server.csproj b/src/Impostor.Server/Impostor.Server.csproj index ad5a6db05..49bb222cb 100644 --- a/src/Impostor.Server/Impostor.Server.csproj +++ b/src/Impostor.Server/Impostor.Server.csproj @@ -6,9 +6,10 @@ win-x64;linux-x64;linux-arm;linux-arm64;osx-x64 true icon.ico - ProjectRules.ruleset + ../ProjectRules.ruleset enable false + true @@ -16,25 +17,26 @@ Impostor.Server Impostor.Server Copyright © AeonLucid 2020 - 1.0.0 - + - + - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -55,4 +57,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..6ca0bb9a1 --- /dev/null +++ b/src/Impostor.Server/Net/AnnouncementsService.cs @@ -0,0 +1,116 @@ +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.Logging; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; + +namespace Impostor.Server.Net +{ + internal class AnnouncementsService : IHostedService + { + private readonly ILogger _logger; + private readonly AnnouncementsServerConfig _config; + private readonly ObjectPool _readerPool; + private readonly IEventManager _eventManager; + private UdpConnectionListener? _connection; + + public AnnouncementsService(ILogger logger, IOptions config, ObjectPool readerPool, IEventManager eventManager) + { + _logger = logger; + _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(); + + _logger.LogInformation("Announcements server is listening on {Address}:{Port}", endpoint.Address, endpoint.Port); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogWarning("Announcements server is shutting down!"); + + if (_connection != null) + { + await _connection.DisposeAsync(); + } + } + + private async ValueTask OnNewConnection(NewConnectionEventArgs e) + { + MessageHello.Deserialize(e.HandshakeData, out var announcementVersion, out var id, out var language); + + _logger.LogDebug("Client requested announcement (version: {Version}, id: {Id}, language: {Language})", announcementVersion, id, 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); + + _logger.LogDebug("Sent UseCache response"); + } + + 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); + + _logger.LogDebug("Sent ({Id}) {Message}", announcement.Id, announcement.Message); + } + + if (response.FreeWeekendState != FreeWeekendState.NotFree) + { + using var writer = MessageWriter.Get(MessageType.Reliable); + Message02SetFreeWeekend.Serialize(writer, response.FreeWeekendState); + await e.Connection.SendAsync(writer); + + _logger.LogDebug("Sent {FreeWeekendState} weekend state", response.FreeWeekendState); + } + + await e.Connection.Disconnect(null); + } + } +} diff --git a/src/Impostor.Server/Net/AuthService.cs b/src/Impostor.Server/Net/AuthService.cs new file mode 100644 index 000000000..c8a95b200 --- /dev/null +++ b/src/Impostor.Server/Net/AuthService.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Impostor.Api.Events.Managers; +using Impostor.Api.Net.Messages; +using Impostor.Api.Net.Messages.Auth; +using Impostor.Hazel; +using Impostor.Hazel.Dtls; +using Impostor.Server.Config; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; + +namespace Impostor.Server.Net +{ + internal class AuthService : IHostedService + { + private readonly ILogger _logger; + private readonly AuthServerConfig _config; + private readonly ObjectPool _readerPool; + private readonly IEventManager _eventManager; + private DtlsConnectionListener? _connection; + + public AuthService(ILogger logger, IOptions config, ObjectPool readerPool, IEventManager eventManager) + { + _logger = logger; + _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(), + }; + + var rsa = RSA.Create(); + rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(string.Join(string.Empty, (await File.ReadAllLinesAsync(_config.PrivateKey, cancellationToken)).Where(x => !x.StartsWith("-----")))), out _); + var cert = new X509Certificate2(_config.Certificate).CopyWithPrivateKey(rsa); + + _connection = new DtlsConnectionListener(endpoint, _readerPool, mode); + _connection.SetCertificate(cert); + _connection.NewConnection = ConnectionOnNewConnection; + + await _connection.StartAsync(); + + _logger.LogInformation("Auth server is listening on {Address}:{Port}", endpoint.Address, endpoint.Port); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogWarning("Auth server is shutting down!"); + + if (_connection != null) + { + await _connection.DisposeAsync(); + } + } + + private ValueTask ConnectionOnNewConnection(NewConnectionEventArgs e) + { + MessageHandshake.Deserialize(e.HandshakeData, out var clientVersion, out var platform, out var clientId); + + _logger.LogTrace("New authentication request: {clientVersion}, {platform}, {clientId}", clientVersion, platform, clientId); + + using var writer = MessageWriter.Get(MessageType.Reliable); + + writer.StartMessage(1); + Message01Complete.Serialize(writer, (uint)RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue)); + writer.EndMessage(); + + return e.Connection.SendAsync(writer); + } + } +} diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index 87a1bb40e..6c5547ec0 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -22,8 +22,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, int gameVersion, IHazelConnection connection) + : base(name, gameVersion, connection) { _logger = logger; _antiCheatConfig = antiCheatOptions.Value; @@ -31,6 +31,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; @@ -42,7 +61,7 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag case MessageFlags.HostGame: { // Read game settings. - var gameInfo = Message00HostGameC2S.Deserialize(reader); + var gameInfo = Message00HostGameC2S.Deserialize(reader, out _); // Create game. var game = await _gameManager.CreateAsync(gameInfo); @@ -59,10 +78,7 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag case MessageFlags.JoinGame: { - Message01JoinGameC2S.Deserialize( - reader, - out var gameCode, - out _); + Message01JoinGameC2S.Deserialize(reader, out var gameCode); var game = _gameManager.Find(gameCode); if (game == null) @@ -113,7 +129,7 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag return; } - await Player.Game.HandleStartGame(reader); + await Player!.Game.HandleStartGame(reader); break; } @@ -133,7 +149,7 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag out var playerId, out var reason); - await Player.Game.HandleRemovePlayer(playerId, (DisconnectReason)reason); + await Player!.Game.HandleRemovePlayer(playerId, (DisconnectReason)reason); break; } @@ -150,38 +166,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,8 +199,8 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag Message08EndGameC2S.Deserialize( reader, out var gameOverReason); - - await Player.Game.HandleEndGame(reader, gameOverReason); + + await Player!.Game.HandleEndGame(reader, gameOverReason); break; } @@ -218,7 +221,7 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag return; } - await Player.Game.HandleAlterGame(reader, Player, value); + await Player!.Game.HandleAlterGame(reader, Player, value); break; } @@ -234,13 +237,13 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag out var playerId, out var isBan); - await Player.Game.HandleKickPlayer(playerId, isBan); + await Player!.Game.HandleKickPlayer(playerId, isBan); break; } case MessageFlags.GetGameListV2: { - Message16GetGameListC2S.Deserialize(reader, out var options); + Message16GetGameListC2S.Deserialize(reader, out var options, out _); await OnRequestGameListAsync(options); break; } @@ -324,13 +327,9 @@ private ValueTask OnRequestGameListAsync(GameOptionsData options) { using var message = MessageWriter.Get(MessageType.Reliable); - var games = _gameManager.FindListings((MapFlags)options.MapId, options.NumImpostors, options.Keywords); - - var skeldGameCount = _gameManager.GetGameCount(MapFlags.Skeld); - var miraHqGameCount = _gameManager.GetGameCount(MapFlags.MiraHQ); - var polusGameCount = _gameManager.GetGameCount(MapFlags.Polus); + var games = _gameManager.FindListings((MapFlags)options.Map, options.NumImpostors, options.Keywords); - Message16GetGameListS2C.Serialize(message, skeldGameCount, miraHqGameCount, polusGameCount, games); + Message16GetGameListS2C.Serialize(message, games); return Connection.SendAsync(message); } diff --git a/src/Impostor.Server/Net/ClientBase.cs b/src/Impostor.Server/Net/ClientBase.cs index 527919256..b9488c246 100644 --- a/src/Impostor.Server/Net/ClientBase.cs +++ b/src/Impostor.Server/Net/ClientBase.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; +using Impostor.Api; using Impostor.Api.Innersloth; using Impostor.Api.Net; using Impostor.Api.Net.Messages; @@ -13,9 +14,10 @@ namespace Impostor.Server.Net { internal abstract class ClientBase : IClient { - protected ClientBase(string name, IHazelConnection connection) + protected ClientBase(string name, int gameVersion, IHazelConnection connection) { Name = name; + GameVersion = gameVersion; Connection = connection; Items = new ConcurrentDictionary(); } @@ -24,6 +26,8 @@ protected ClientBase(string name, IHazelConnection connection) public string Name { get; } + public int GameVersion { get; } + public IHazelConnection Connection { get; } public IDictionary Items { get; } @@ -32,11 +36,15 @@ 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); - public async ValueTask DisconnectAsync(DisconnectReason reason, string? message = null) { if (!Connection.IsConnected) @@ -44,15 +52,10 @@ public async ValueTask DisconnectAsync(DisconnectReason reason, string? message return; } - using var packet = MessageWriter.Get(MessageType.Reliable); - Message01JoinGameS2C.SerializeError(packet, false, reason, message); - - await Connection.SendAsync(packet); - - // Need this to show the correct message, otherwise it shows a generic disconnect message. - await Task.Delay(TimeSpan.FromMilliseconds(250)); + using var writer = MessageWriter.Get(); + MessageDisconnect.Serialize(writer, true, reason, message); - await Connection.DisconnectAsync(message ?? reason.ToString()); + await Connection.DisconnectAsync(message ?? reason.ToString(), writer); } } } diff --git a/src/Impostor.Server/Net/Factories/ClientFactory.cs b/src/Impostor.Server/Net/Factories/ClientFactory.cs index ad1fdc778..e7d3e668f 100644 --- a/src/Impostor.Server/Net/Factories/ClientFactory.cs +++ b/src/Impostor.Server/Net/Factories/ClientFactory.cs @@ -16,9 +16,9 @@ public ClientFactory(IServiceProvider serviceProvider) public ClientBase Create(IHazelConnection connection, string name, int clientVersion) { - var client = ActivatorUtilities.CreateInstance(_serviceProvider, name, connection); + var client = ActivatorUtilities.CreateInstance(_serviceProvider, name, clientVersion, connection); connection.Client = client; return client; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Factories/IClientFactory.cs b/src/Impostor.Server/Net/Factories/IClientFactory.cs index 6859ae3c9..11a1ddc95 100644 --- a/src/Impostor.Server/Net/Factories/IClientFactory.cs +++ b/src/Impostor.Server/Net/Factories/IClientFactory.cs @@ -6,4 +6,4 @@ internal interface IClientFactory { ClientBase Create(IHazelConnection connection, string name, int clientVersion); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/GameCodeFactory.cs b/src/Impostor.Server/Net/GameCodeFactory.cs index 2a0553fc8..f8b0de478 100644 --- a/src/Impostor.Server/Net/GameCodeFactory.cs +++ b/src/Impostor.Server/Net/GameCodeFactory.cs @@ -9,4 +9,4 @@ public GameCode Create() return GameCode.Create(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Hazel/HazelConnection.cs b/src/Impostor.Server/Net/Hazel/HazelConnection.cs index 45d2be827..c59a91c2d 100644 --- a/src/Impostor.Server/Net/Hazel/HazelConnection.cs +++ b/src/Impostor.Server/Net/Hazel/HazelConnection.cs @@ -25,16 +25,16 @@ public HazelConnection(Connection innerConnection, ILogger logg public bool IsConnected => InnerConnection.State == ConnectionState.Connected; - public IClient Client { get; set; } + public IClient? Client { get; set; } public ValueTask SendAsync(IMessageWriter writer) { return InnerConnection.SendAsync(writer); } - public ValueTask DisconnectAsync(string reason) + public ValueTask DisconnectAsync(string? reason, IMessageWriter? writer = null) { - return InnerConnection.Disconnect(reason); + return InnerConnection.Disconnect(reason, writer as MessageWriter); } public void DisposeInnerConnection() diff --git a/src/Impostor.Server/Net/Inner/GameDataTag.cs b/src/Impostor.Server/Net/Inner/GameDataTag.cs index fa9ddb8cb..300ce81be 100644 --- a/src/Impostor.Server/Net/Inner/GameDataTag.cs +++ b/src/Impostor.Server/Net/Inner/GameDataTag.cs @@ -9,5 +9,7 @@ public static class GameDataTag public const byte SceneChangeFlag = 6; public const byte ReadyFlag = 7; public const byte ChangeSettingsFlag = 8; + public const byte ConsoleDeclareClientPlatformFlag = 205; + public const byte PS4RoomRequest = 206; } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/GameObject.cs b/src/Impostor.Server/Net/Inner/GameObject.cs index 9b53d708f..7fd4f0125 100644 --- a/src/Impostor.Server/Net/Inner/GameObject.cs +++ b/src/Impostor.Server/Net/Inner/GameObject.cs @@ -26,4 +26,4 @@ public List GetComponentsInChildren() return result; } } -} \ 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..4a1510414 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 Impostor.Api.Games; using Impostor.Api.Net; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; @@ -6,26 +7,40 @@ namespace Impostor.Server.Net.Inner { - internal abstract class InnerNetObject : GameObject, IInnerNetObject + internal abstract partial class InnerNetObject : GameObject, IInnerNetObject { private const int HostInheritId = -2; + protected InnerNetObject(Game game) + { + Game = game; + } + public uint NetId { get; internal set; } public int OwnerId { get; internal set; } - public SpawnFlags SpawnFlags { get; internal set; } - - public abstract ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader); + public Game Game { get; } - public abstract bool Serialize(IMessageWriter writer, bool initialState); + IGame IInnerNetObject.Game => Game; - public abstract void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState); + public SpawnFlags SpawnFlags { get; internal set; } 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); + + public virtual ValueTask OnSpawnAsync() + { + return default; + } } -} \ 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..b73b835f8 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs @@ -1,5 +1,6 @@ using System.Numerics; using System.Threading.Tasks; +using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects.Components; namespace Impostor.Server.Net.Inner.Objects.Components @@ -11,14 +12,14 @@ 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)) + using (var writer = Game.StartRpc(NetId, RpcCalls.SnapTo)) { - WriteVector2(writer, position); + writer.Write(position); writer.Write(_lastSequenceId); - await _game.FinishRpcAsync(writer); + 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..0b5290548 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs @@ -1,125 +1,77 @@ -using System.Numerics; +using System.Linq; +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.Inner; using Impostor.Api.Net.Messages; +using Impostor.Api.Net.Messages.Rpcs; +using Impostor.Server.Events.Player; +using Impostor.Server.Net.Inner.Objects.ShipStatus; 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 static readonly Vector2 ColliderOffset = new Vector2(0f, -0.4f); 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; + private bool _spawnSnapAllowed; - public InnerCustomNetworkTransform(ILogger logger, InnerPlayerControl playerControl, Game game) + public InnerCustomNetworkTransform(Game game, ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager, ObjectPool pool) : base(game) { _logger = logger; _playerControl = playerControl; - _game = game; + _eventManager = eventManager; + _pool = pool; } - private static bool SidGreaterThan(ushort newSid, ushort prevSid) - { - var num = (ushort)(prevSid + (uint) short.MaxValue); - - 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; + public Vector2 Position { get; private set; } - return new Vector2(XRange.Lerp(v1), YRange.Lerp(v2)); - } + public Vector2 Velocity { get; private set; } - 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)) - { - throw new ImpostorCheatException($"Client attempted to send unowned {nameof(InnerCustomNetworkTransform)} data"); - } - - if (target != null) + if (!await ValidateOwnership(CheatContext.Deserialize, sender) || !await ValidateBroadcast(CheatContext.Deserialize, sender, target)) { - throw new ImpostorCheatException($"Client attempted to send {nameof(InnerCustomNetworkTransform)} data to a specific player, must be broadcast"); + return; } if (!SidGreaterThan(sequenceId, _lastSequenceId)) @@ -128,21 +80,104 @@ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IM } _lastSequenceId = sequenceId; - _targetSyncPosition = ReadVector2(reader); - _targetSyncVelocity = ReadVector2(reader); + await SetPositionAsync(sender, reader.ReadVector2(), reader.ReadVector2()); + } + } + + public override async ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + if (call == RpcCalls.SnapTo) + { + if (!await ValidateOwnership(call, sender)) + { + return false; + } + + Rpc21SnapTo.Deserialize(reader, out var position, out var minSid); + + if (Game.GameNet.ShipStatus is InnerAirshipStatus airshipStatus) + { + // As part of airship spawning, clients are sending snap to -25 40 for no reason(?), cancelling it works just fine + if (Approximately(position, airshipStatus.PreSpawnLocation)) + { + return false; + } + + if (_spawnSnapAllowed && airshipStatus.SpawnLocations.Any(location => Approximately(position, location))) + { + _spawnSnapAllowed = false; + return true; + } + } + + if (!await ValidateImpostor(call, sender, _playerControl.PlayerInfo)) + { + return false; + } + + var vents = Game.GameNet.ShipStatus!.Data.Vents.Values; + + var vent = vents.SingleOrDefault(x => Approximately(x.Position, position + ColliderOffset)); + + if (vent == null) + { + if (await sender.Client.ReportCheatAsync(call, "Failed vent position check")) + { + return false; + } + } + else + { + await _eventManager.CallAsync(new PlayerVentEvent(Game, sender, _playerControl, vent)); + } + + 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); + } + + internal void OnPlayerSpawn() + { + _spawnSnapAllowed = true; + } + + private static bool SidGreaterThan(ushort newSid, ushort prevSid) + { + var num = (ushort)(prevSid + (uint)short.MaxValue); + + return (int)prevSid < (int)num + ? newSid > prevSid && newSid <= num + : newSid > prevSid || newSid <= num; + } + + private static bool Approximately(Vector2 a, Vector2 b, float tolerance = 0.1f) + { + var abs = Vector2.Abs(a - b); + return abs.X <= tolerance && abs.Y <= tolerance; } - private void SnapTo(Vector2 position, ushort minSid) + 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..edd7b1de8 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs @@ -1,10 +1,12 @@ using System; using System.Threading.Tasks; -using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Maps; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; 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; @@ -16,55 +18,77 @@ internal partial class InnerPlayerPhysics : InnerNetObject private readonly ILogger _logger; private readonly InnerPlayerControl _playerControl; private readonly IEventManager _eventManager; - private readonly Game _game; - public InnerPlayerPhysics(ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager, Game game) + public InnerPlayerPhysics(Game game, ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager) : base(game) { _logger = logger; _playerControl = playerControl; _eventManager = eventManager; - _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)) { - throw new ImpostorCheatException($"Client sent {call} to a specific player instead of broadcast"); + return false; } - if (!sender.Character.PlayerInfo.IsImpostor) + switch (call) { - throw new ImpostorCheatException($"Client sent {call} as crewmate"); - } + case RpcCalls.EnterVent: + case RpcCalls.ExitVent: + if (!await ValidateImpostor(call, sender, _playerControl.PlayerInfo)) + { + return false; + } - var ventId = reader.ReadPackedUInt32(); - var ventEnter = call == RpcCalls.EnterVent; + int ventId; - await _eventManager.CallAsync(new PlayerVentEvent(_game, sender, _playerControl, (VentLocation)ventId, ventEnter)); + switch (call) + { + case RpcCalls.EnterVent: + Rpc19EnterVent.Deserialize(reader, out ventId); + break; + case RpcCalls.ExitVent: + Rpc20ExitVent.Deserialize(reader, out ventId); + break; + default: + throw new ArgumentOutOfRangeException(nameof(call), call, null); + } - return; - } + var vent = Game.GameNet.ShipStatus!.Data.Vents[ventId]; - public override bool Serialize(IMessageWriter writer, bool initialState) - { - throw new NotImplementedException(); - } + switch (call) + { + case RpcCalls.EnterVent: + await _eventManager.CallAsync(new PlayerEnterVentEvent(Game, sender, _playerControl, vent)); + break; + case RpcCalls.ExitVent: + await _eventManager.CallAsync(new PlayerExitVentEvent(Game, sender, _playerControl, vent)); + break; + } - public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) - { - throw new NotImplementedException(); + break; + + case RpcCalls.ClimbLadder: + Rpc31ClimbLadder.Deserialize(reader, out var ladderId, out var lastClimbLadderSid); + break; + + default: + return await UnregisteredCall(call, sender); + } + + 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..ac6304140 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs @@ -3,8 +3,10 @@ using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; 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; @@ -15,48 +17,22 @@ internal class InnerVoteBanSystem : InnerNetObject, IInnerVoteBanSystem private readonly ILogger _logger; private readonly Dictionary _votes; - public InnerVoteBanSystem(ILogger logger) + public InnerVoteBanSystem(Game game, ILogger logger) : base(game) { _logger = 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 +60,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..0a29d6378 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Threading.Tasks; using Impostor.Api; -using Impostor.Api.Innersloth; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; using Impostor.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; @@ -17,17 +18,15 @@ namespace Impostor.Server.Net.Inner.Objects internal partial class InnerGameData : InnerNetObject, IInnerGameData { private readonly ILogger _logger; - private readonly Game _game; private readonly ConcurrentDictionary _allPlayers; - public InnerGameData(ILogger logger, Game game, IServiceProvider serviceProvider) + public InnerGameData(Game game, ILogger logger, IServiceProvider serviceProvider) : base(game) { _logger = logger; - _game = game; _allPlayers = new ConcurrentDictionary(); Components.Add(this); - Components.Add(ActivatorUtilities.CreateInstance(serviceProvider)); + Components.Add(ActivatorUtilities.CreateInstance(serviceProvider, game)); } public int PlayerCount => _allPlayers.Count; @@ -44,85 +43,16 @@ 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) - { - switch (call) - { - case RpcCalls.SetTasks: - { - 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"); - } - - var playerId = reader.ReadByte(); - var taskTypeIds = reader.ReadBytesAndSize(); - - 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(); - var player = GetPlayerById(message.Tag); - if (player != null) - { - player.Deserialize(message); - } - else - { - var playerInfo = new InnerPlayerInfo(message.Tag); - - playerInfo.Deserialize(reader); - - if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo)) - { - throw new ImpostorException("Failed to add player to InnerGameData."); - } - } - } - - break; - } - - default: - { - _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerGameData), 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)) { - throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerGameData)} as non-host"); + return; } if (initialState) @@ -144,19 +74,62 @@ public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IM } else { - throw new NotImplementedException("This shouldn't happen, according to Among Us disassembly."); + while (reader.Position < reader.Length) + { + var inner = reader.ReadMessage(); + var playerInfo = this.GetPlayerById(inner.Tag); + if (playerInfo != null) + { + playerInfo.Deserialize(inner); + } + else + { + playerInfo = new InnerPlayerInfo(inner.Tag); + playerInfo.Deserialize(inner); + + if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo)) + { + throw new ImpostorException("Failed to add player to InnerGameData."); + } + } + } + } + } + + 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; + } + + default: + return await UnregisteredCall(call, sender); } + + return true; } - internal void AddPlayer(InnerPlayerControl control) + internal InnerPlayerInfo? AddPlayer(InnerPlayerControl control) { var playerId = control.PlayerId; var playerInfo = new InnerPlayerInfo(control.PlayerId); if (_allPlayers.TryAdd(playerId, playerInfo)) { - control.PlayerInfo = playerInfo; + return playerInfo; } + + return null; } private void SetTasks(byte playerId, ReadOnlyMemory taskTypeIds) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs b/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs index 63448edd2..c897a6860 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks; -using Impostor.Api.Games; +using System; +using System.Threading.Tasks; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; using Impostor.Api.Net.Messages; using Impostor.Server.Net.State; @@ -9,28 +10,24 @@ namespace Impostor.Server.Net.Inner.Objects { internal class InnerLobbyBehaviour : InnerNetObject, IInnerLobbyBehaviour { - private readonly IGame _game; - - public InnerLobbyBehaviour(IGame game) + public InnerLobbyBehaviour(Game game) : base(game) { - _game = 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(); + throw new 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(); + throw new 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(); + throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs index 5d120c5cf..0219fbab2 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs @@ -4,6 +4,5 @@ namespace Impostor.Server.Net.Inner.Objects { internal partial class InnerMeetingHud : IInnerMeetingHud { - } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs index fbf25102b..48f551b9a 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs @@ -29,12 +29,6 @@ public PlayerVoteArea(InnerMeetingHud parent, byte targetPlayerId) public sbyte VotedFor { get; private set; } - internal void SetDead(bool didReport, bool isDead) - { - DidReport = didReport; - IsDead = isDead; - } - public void Deserialize(IMessageReader reader) { var num = reader.ReadByte(); @@ -44,6 +38,12 @@ public void Deserialize(IMessageReader reader) DidVote = (num & VotedBit) > 0; DidReport = (num & ReportedBit) > 0; } + + internal void SetDead(bool didReport, bool isDead) + { + DidReport = didReport; + IsDead = isDead; + } } } -} \ 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..f774cd0c0 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs @@ -1,11 +1,14 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Innersloth; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; 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; @@ -17,16 +20,14 @@ internal partial class InnerMeetingHud : InnerNetObject { private readonly ILogger _logger; private readonly IEventManager _eventManager; - private readonly Game _game; - private readonly GameNet _gameNet; + + [AllowNull] private PlayerVoteArea[] _playerStates; - public InnerMeetingHud(ILogger logger, IEventManager eventManager, Game game) + public InnerMeetingHud(Game game, ILogger logger, IEventManager eventManager) : base(game) { _logger = logger; _eventManager = eventManager; - _game = game; - _gameNet = game.GameNet; _playerStates = null; Components.Add(this); @@ -34,143 +35,150 @@ public InnerMeetingHud(ILogger logger, IEventManager eventManag public byte ReporterId { get; private set; } - private void PopulateButtons(byte reporter) + public override ValueTask SerializeAsync(IMessageWriter writer, bool initialState) { - _playerStates = _gameNet.GameData.Players - .Select(x => - { - var area = new PlayerVoteArea(this, x.Key); - area.SetDead(x.Value.PlayerId == reporter, x.Value.Disconnected || x.Value.IsDead); - return area; - }) - .ToArray(); + throw new NotImplementedException(); } - public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) { - switch (call) + 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; } default: - { - _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerMeetingHud), call); - break; - } + return await UnregisteredCall(call, sender); } + + return true; } - public override bool Serialize(IMessageWriter writer, bool initialState) + private void PopulateButtons(byte reporter) { - throw new NotImplementedException(); + _playerStates = Game.GameNet.GameData!.Players + .Select(x => + { + var area = new PlayerVoteArea(this, x.Key); + area.SetDead(x.Value.PlayerId == reporter, x.Value.Disconnected || x.Value.IsDead); + return area; + }) + .ToArray(); } - 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?.Controller != 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..8804bee90 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -2,9 +2,10 @@ using Impostor.Api; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Customization; -using Impostor.Api.Net; +using Impostor.Api.Net.Inner; 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 @@ -21,72 +22,52 @@ public async ValueTask SetNameAsync(string name) { PlayerInfo.PlayerName = name; - using var writer = _game.StartRpc(NetId, RpcCalls.SetName); + using var writer = Game.StartRpc(NetId, RpcCalls.SetName); writer.Write(name); - await _game.FinishRpcAsync(writer); + 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); - await _game.FinishRpcAsync(writer); + using var writer = Game.StartRpc(NetId, RpcCalls.SetColor); + 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); - await _game.FinishRpcAsync(writer); + using var writer = Game.StartRpc(NetId, RpcCalls.SetHat); + Rpc09SetHat.Serialize(writer, hat); + await Game.FinishRpcAsync(writer); } - public ValueTask SetHatAsync(HatType hatType) + public async ValueTask SetPetAsync(PetType pet) { - return SetHatAsync((uint)hatType); - } + PlayerInfo.Pet = pet; - public async ValueTask SetPetAsync(uint petId) - { - PlayerInfo.PetId = petId; - - using var writer = _game.StartRpc(NetId, RpcCalls.SetPet); - writer.WritePacked(petId); - await _game.FinishRpcAsync(writer); - } - - public ValueTask SetPetAsync(PetType petType) - { - return SetPetAsync((uint)petType); + using var writer = Game.StartRpc(NetId, RpcCalls.SetPet); + Rpc17SetPet.Serialize(writer, pet); + await Game.FinishRpcAsync(writer); } - 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); - await _game.FinishRpcAsync(writer); - } - - public ValueTask SetSkinAsync(SkinType skinType) - { - return SetSkinAsync((uint)skinType); + using var writer = Game.StartRpc(NetId, RpcCalls.SetSkin); + Rpc10SetSkin.Serialize(writer, skin); + await Game.FinishRpcAsync(writer); } public async ValueTask SendChatAsync(string text) { - using var writer = _game.StartRpc(NetId, RpcCalls.SendChat); + using var writer = Game.StartRpc(NetId, RpcCalls.SendChat); writer.Write(text); - await _game.FinishRpcAsync(writer); + await Game.FinishRpcAsync(writer); } public async ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null) @@ -96,43 +77,54 @@ public async ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? p player = this; } - using var writer = _game.StartRpc(NetId, RpcCalls.SendChat); + using var writer = Game.StartRpc(NetId, RpcCalls.SendChat); writer.Write(text); - await _game.FinishRpcAsync(writer, player.OwnerId); + await Game.FinishRpcAsync(writer, player.OwnerId); } - public async ValueTask SetMurderedByAsync(IClientPlayer impostor) + public async ValueTask MurderPlayerAsync(IInnerPlayerControl target) { - if (impostor.Character == null) + if (!PlayerInfo.IsImpostor) { - throw new ImpostorException("Character is null."); + throw new ImpostorProtocolException("Tried to murder a player, but murderer was not the impostor."); } - if (!impostor.Character.PlayerInfo.IsImpostor) + if (PlayerInfo.IsDead) { - 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 alive."); } - if (impostor.Character.PlayerInfo.IsDead) + if (target.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 target was not alive."); } + ((InnerPlayerControl)target).Die(DeathReason.Kill); + + using var writer = Game.StartRpc(NetId, RpcCalls.MurderPlayer); + Rpc12MurderPlayer.Serialize(writer, target); + await Game.FinishRpcAsync(writer); + + await _eventManager.CallAsync(new PlayerMurderEvent(Game, Game.GetClientPlayer(OwnerId)!, this, target)); + } + + public async ValueTask ExileAsync() + { if (PlayerInfo.IsDead) { - return; + throw new ImpostorProtocolException("Tried to exile a player, but target was not alive."); } // Update player. - Die(DeathReason.Kill); + Die(DeathReason.Exile); // Send RPC. - using var writer = _game.StartRpc(impostor.Character.NetId, RpcCalls.MurderPlayer); - writer.WritePacked(NetId); - await _game.FinishRpcAsync(writer); + using var writer = Game.StartRpc(NetId, RpcCalls.Exiled); + Rpc04Exiled.Serialize(writer); + await Game.FinishRpcAsync(writer); // Notify plugins. - await _eventManager.CallAsync(new PlayerMurderEvent(_game, impostor, impostor.Character, this)); + await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(OwnerId)!, this)); } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 1fe1dc833..06191a18f 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Impostor.Api; @@ -6,7 +8,10 @@ using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Customization; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; +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; @@ -17,18 +22,20 @@ namespace Impostor.Server.Net.Inner.Objects { internal partial class InnerPlayerControl : InnerNetObject { + private static readonly byte ColorsCount = (byte)Enum.GetValues().Length; + private readonly ILogger _logger; private readonly IEventManager _eventManager; - private readonly Game _game; + private readonly IDateTimeProvider _dateTimeProvider; - public InnerPlayerControl(ILogger logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game) + public InnerPlayerControl(Game game, ILogger logger, IServiceProvider serviceProvider, IEventManager eventManager, IDateTimeProvider dateTimeProvider) : base(game) { _logger = logger; _eventManager = eventManager; - _game = game; + _dateTimeProvider = dateTimeProvider; - Physics = ActivatorUtilities.CreateInstance(serviceProvider, this, _eventManager, _game); - NetworkTransform = ActivatorUtilities.CreateInstance(serviceProvider, this, _game); + Physics = ActivatorUtilities.CreateInstance(serviceProvider, this, _eventManager, game); + NetworkTransform = ActivatorUtilities.CreateInstance(serviceProvider, this, game); Components.Add(this); Components.Add(Physics); @@ -45,523 +52,551 @@ public InnerPlayerControl(ILogger logger, IServiceProvider s public InnerCustomNetworkTransform NetworkTransform { get; } + [AllowNull] 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(); + } + + 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"); - } - - 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; - } - } - - if (_game.GameState == GameStates.Starting) - { - await _game.StartedAsync(); + return false; } + Rpc03SetInfected.Deserialize(reader, out var infectedIds); + await HandleSetInfected(infectedIds); break; } - // Player was voted out. - case RpcCalls.Exiled: + case RpcCalls.CheckName: { - if (!sender.IsHost) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} but was not a host"); - } - - if (target != null) + 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; + Rpc05CheckName.Deserialize(reader, out var name); + return await HandleCheckName(sender, name); } - // Validates the player name at the host. - case RpcCalls.CheckName: + case RpcCalls.SetName: { - 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) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to the wrong player"); - } - - var name = reader.ReadString(); + Rpc06SetName.Deserialize(reader, out var name); + return await HandleSetName(sender, name); + } - if (name.Length > 10) + case RpcCalls.CheckColor: + { + if (!await ValidateOwnership(call, sender) || !await ValidateCmd(call, sender, target)) { - 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"); - } + Rpc07CheckColor.Deserialize(reader, out var color); + return await HandleCheckColor(sender, color); + } - if (sender.Client.Name != name) + case RpcCalls.SetColor: + { + if (!await ValidateHost(call, sender)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with name not matching his name from handshake"); + return false; } - PlayerInfo.RequestedPlayerName = name; - break; + Rpc08SetColor.Deserialize(reader, out var color); + return await HandleSetColor(sender, color); } - // Update the name of a player. - case RpcCalls.SetName: + case RpcCalls.SetHat: { - if (!sender.IsHost) + if (!await ValidateOwnership(call, sender)) { - 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(); + Rpc09SetHat.Deserialize(reader, out var hat); + return await HandleSetHat(sender, hat); + } - if (sender.IsOwner(this)) + case RpcCalls.SetSkin: + { + 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++; - } - } + Rpc10SetSkin.Deserialize(reader, out var skin); + return await HandleSetSkin(sender, skin); + } - if (name != expected) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with incorrect name"); - } + case RpcCalls.ReportDeadBody: + { + if (!await ValidateOwnership(call, sender)) + { + return false; } - PlayerInfo.PlayerName = name; - PlayerInfo.RequestedPlayerName = null; + Rpc11ReportDeadBody.Deserialize(reader, out var targetId); break; } - // Validates the color at the host. - case RpcCalls.CheckColor: + case RpcCalls.MurderPlayer: { - if (!sender.IsOwner(this)) + if (!await ValidateOwnership(call, sender) || !await ValidateImpostor(call, sender, PlayerInfo)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to an unowned {nameof(InnerPlayerControl)}"); + return false; } - if (target == null || !target.IsHost) + Rpc12MurderPlayer.Deserialize(reader, Game, out var murdered); + return await HandleMurderPlayer(sender, murdered); + } + + case RpcCalls.SendChat: + { + if (!await ValidateOwnership(call, sender)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to the wrong player"); + return false; } - var color = reader.ReadByte(); + Rpc13SendChat.Deserialize(reader, out var message); + return await HandleSendChat(sender, message); + } - if (color > Enum.GetValues().Length) + case RpcCalls.StartMeeting: + { + if (!await ValidateHost(call, sender)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} with invalid color"); + return false; } - PlayerInfo.RequestedColorId = color; + Rpc14StartMeeting.Deserialize(reader, out var targetId); + await HandleStartMeeting(targetId); break; } - // Update the color of a player. - case RpcCalls.SetColor: + case RpcCalls.SetScanner: { - 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) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} to a specific player instead of broadcast"); - } - - var color = reader.ReadByte(); + Rpc15SetScanner.Deserialize(reader, out var on, out var scannerCount); + break; + } - if (sender.IsOwner(this)) + case RpcCalls.SendChatNote: + { + if (!await ValidateOwnership(call, sender)) { - 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"); - } + return false; } - else - { - if (PlayerInfo.RequestedColorId == null) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} for a player that didn't request it"); - } - var expected = PlayerInfo.RequestedColorId!.Value; - - while (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.ColorId == expected)) - { - expected = (byte)((expected + 1) % Enum.GetValues().Length); - } - - if (color != expected) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} with incorrect color"); - } - } - - PlayerInfo.ColorId = color; - PlayerInfo.RequestedColorId = null; + Rpc16SendChatNote.Deserialize(reader, out var playerId, out var chatNoteType); break; } - // Update the hat of a player. - case RpcCalls.SetHat: + case RpcCalls.SetPet: { - if (!sender.IsOwner(this)) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to an unowned {nameof(InnerPlayerControl)}"); - } - - if (target != null) + if (!await ValidateOwnership(call, sender)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast"); + return false; } - PlayerInfo.HatId = reader.ReadPackedUInt32(); - break; + Rpc17SetPet.Deserialize(reader, out var pet); + return await HandleSetPet(sender, pet); } - case RpcCalls.SetSkin: + case RpcCalls.SetStartCounter: { - if (!sender.IsOwner(this)) + if (!await ValidateOwnership(call, sender)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetSkin)} to an unowned {nameof(InnerPlayerControl)}"); + return false; } - if (target != null) + Rpc18SetStartCounter.Deserialize(reader, out var sequenceId, out var startCounter); + return await HandleSetStartCounter(sender, sequenceId, startCounter); + } + + case RpcCalls.UsePlatform: + { + if (!await ValidateOwnership(call, sender)) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast"); + return false; } - PlayerInfo.SkinId = reader.ReadPackedUInt32(); + Rpc32UsePlatform.Deserialize(reader); break; } - // TODO: (ANTICHEAT) Location check? - // only called by a non-host player on to start meeting - case RpcCalls.ReportDeadBody: - { - if (!sender.IsOwner(this)) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to an unowned {nameof(InnerPlayerControl)}"); - } + default: + return await UnregisteredCall(call, sender); + } - if (target != null) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to a specific player instead of broadcast"); - } + return true; + } + + internal void Die(DeathReason reason) + { + PlayerInfo.IsDead = true; + PlayerInfo.LastDeathReason = reason; + } + private async ValueTask HandleCompleteTask(ClientPlayer sender, uint taskId) + { + var task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId); - var deadBodyPlayerId = reader.ReadByte(); - // deadBodyPlayerId == byte.MaxValue -- means emergency call by button + 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)}"); + } + } - break; + 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) + { + player.IsImpostor = true; } + } - // TODO: (ANTICHEAT) Cooldown check? - case RpcCalls.MurderPlayer: + if (Game.GameState == GameStates.Starting) + { + await Game.StartedAsync(); + } + } + + 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")) { - if (!sender.IsOwner(this)) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to an unowned {nameof(InnerPlayerControl)}"); - } + return false; + } + } - if (target != null) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to a specific player instead of broadcast"); - } + if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed)) + { + if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name containing illegal characters")) + { + return false; + } + } - if (!sender.Character.PlayerInfo.IsImpostor) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} as crewmate"); - } + if (sender.Client.Name != name) + { + if (await sender.Client.ReportCheatAsync(RpcCalls.CheckName, "Client sent name not matching his name from handshake")) + { + return false; + } + } - if (!sender.Character.PlayerInfo.CanMurder(_game)) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} too fast"); - } + RequestedPlayerName.Enqueue(name); - sender.Character.PlayerInfo.LastMurder = DateTimeOffset.UtcNow; + return true; + } - var player = reader.ReadNetObject(_game); - if (!player.PlayerInfo.IsDead) + 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; + } + } + + if (sender.IsOwner(this)) + { + if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name)) + { + if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name that is already used")) { - player.Die(DeathReason.Kill); - await _eventManager.CallAsync(new PlayerMurderEvent(_game, sender, this, player)); + return false; } - - break; } - case RpcCalls.SendChat: + if (sender.Client.Name != name) { - if (!sender.IsOwner(this)) + if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name not matching his name from handshake")) { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to an unowned {nameof(InnerPlayerControl)}"); + return false; } + } + } + else + { + if (!RequestedPlayerName.Any()) + { + _logger.LogWarning($"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it"); + return false; + } + + var expected = RequestedPlayerName.Dequeue(); - if (target != null) + 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.SendChat)} to a specific player instead of broadcast"); - } + string text = expected + " " + i; - var chat = reader.ReadString(); + if (Game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text)) + { + expected = text; + break; + } - await _eventManager.CallAsync(new PlayerChatEvent(_game, sender, this, chat)); - break; + i++; + } } - case RpcCalls.StartMeeting: + if (name != expected) { - if (!sender.IsHost) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} but was not a host"); - } + _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.StartMeeting)} to a specific player instead of broadcast"); - } + PlayerInfo.PlayerName = name; - // 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 HandleCheckColor(ClientPlayer sender, ColorType color) + { + if ((byte)color > ColorsCount) + { + if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, "Client sent invalid color")) + { + return false; } + } - case RpcCalls.SetScanner: - { - if (!sender.IsOwner(this)) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to an unowned {nameof(InnerPlayerControl)}"); - } + RequestedColorId.Enqueue(color); - if (target != null) - { - throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to a specific player instead of broadcast"); - } + return true; + } - var on = reader.ReadBoolean(); - var count = reader.ReadByte(); - 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.SendChatNote: + 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.SendChatNote)} to an unowned {nameof(InnerPlayerControl)}"); + return false; } + } + } + else + { + if (!RequestedColorId.Any()) + { + _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) + { + if (!PlayerInfo.CanMurder(Game, _dateTimeProvider)) + { + 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 = _dateTimeProvider.UtcNow; + + if (target != null && !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..763782a32 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using Impostor.Api; using Impostor.Api.Games; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Customization; using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects @@ -13,23 +15,19 @@ public InnerPlayerInfo(byte playerId) PlayerId = playerId; } - public InnerPlayerControl Controller { get; internal set; } + public InnerPlayerControl? Controller { get; internal set; } public byte PlayerId { get; } - public string PlayerName { get; internal set; } + public string PlayerName { get; internal set; } = string.Empty; - 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; } @@ -39,18 +37,18 @@ public InnerPlayerInfo(byte playerId) public DeathReason LastDeathReason { get; internal set; } - public List Tasks { get; internal set; } + public List Tasks { get; internal set; } = new List(0); public DateTimeOffset LastMurder { get; set; } - public bool CanMurder(IGame game) + public bool CanMurder(IGame game, IDateTimeProvider dateTimeProvider) { if (!IsImpostor) { return false; } - return DateTimeOffset.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown; + return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown; } public void Serialize(IMessageWriter writer) @@ -61,15 +59,23 @@ 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.ReadPackedInt32(); + Hat = (HatType)reader.ReadPackedUInt32(); + Pet = (PetType)reader.ReadPackedUInt32(); + Skin = (SkinType)reader.ReadPackedUInt32(); + var flag = reader.ReadByte(); Disconnected = (flag & 1) > 0; IsImpostor = (flag & 2) > 0; IsDead = (flag & 4) > 0; + var taskCount = reader.ReadByte(); + + if (Tasks.Count != taskCount) + { + Tasks = new List(taskCount); + } + for (var i = 0; i < taskCount; i++) { Tasks[i] ??= new InnerGameData.TaskInfo(); diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs deleted file mode 100644 index b1a3f18f9..000000000 --- a/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -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.Server.Net.Inner.Objects.Systems; -using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; -using Impostor.Server.Net.State; -using Microsoft.Extensions.Logging; - -namespace Impostor.Server.Net.Inner.Objects -{ - internal class InnerShipStatus : InnerNetObject, IInnerShipStatus - { - private readonly ILogger _logger; - private readonly Game _game; - private readonly Dictionary _systems; - - public InnerShipStatus(ILogger logger, Game game) - { - _logger = logger; - _game = game; - - _systems = new Dictionary - { - [SystemTypes.Electrical] = new SwitchSystem(), - [SystemTypes.MedBay] = new MedScanSystem(), - [SystemTypes.Reactor] = new ReactorSystemType(), - [SystemTypes.LifeSupp] = new LifeSuppSystemType(), - [SystemTypes.Security] = new SecurityCameraSystemType(), - [SystemTypes.Comms] = new HudOverrideSystemType(), - [SystemTypes.Doors] = new DoorsSystemType(_game), - }; - - _systems.Add(SystemTypes.Sabotage, new SabotageSystemType(new[] - { - (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) - { - throw new NotImplementedException(); - } - - 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(InnerShipStatus)} as non-host"); - } - - if (target != null) - { - throw new ImpostorCheatException($"Client attempted to send {nameof(InnerShipStatus)} data to a specific player, must be broadcast"); - } - - if (initialState) - { - // TODO: (_systems[SystemTypes.Doors] as DoorsSystemType).SetDoors(); - foreach (var systemType in SystemTypeHelpers.AllTypes) - { - if (_systems.TryGetValue(systemType, out var system)) - { - system.Deserialize(reader, true); - } - } - } - else - { - var count = reader.ReadPackedUInt32(); - - foreach (var systemType in SystemTypeHelpers.AllTypes) - { - // TODO: Not sure what is going on here, check. - if ((count & 1 << (int)(systemType & (SystemTypes.ShipTasks | SystemTypes.Doors))) != 0L) - { - if (_systems.TryGetValue(systemType, out var system)) - { - system.Deserialize(reader, false); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs new file mode 100644 index 000000000..3ed285c10 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Inner.Objects.ShipStatus; +using Impostor.Server.Net.Inner.Objects.Systems; +using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; +using Impostor.Server.Net.State; + +namespace Impostor.Server.Net.Inner.Objects.ShipStatus +{ + internal class InnerAirshipStatus : InnerShipStatus, IInnerAirshipStatus + { + public InnerAirshipStatus(Game game) : base(game) + { + } + + public override IMapData Data => IMapData.Maps[MapTypes.Airship]; + + public override Dictionary Doors { get; } = new Dictionary(21); + + public override float SpawnRadius => throw new NotSupportedException(); + + public override Vector2 InitialSpawnCenter => throw new NotSupportedException(); + + public override Vector2 MeetingSpawnCenter => throw new NotSupportedException(); + + public Vector2 PreSpawnLocation { get; } = new Vector2(-25f, 40f); + + public Vector2[] SpawnLocations { get; } = + { + new Vector2(-0.7f, 8.5f), // Brig + new Vector2(-0.7f, -1.0f), // Engine + new Vector2(15.5f, 0.0f), // MainHall + new Vector2(-7.0f, -11.5f), // Kitchen + new Vector2(20.0f, 10.5f), // Records + new Vector2(33.5f, -1.5f), // CargoBay + }; + + public override Vector2 GetSpawnLocation(InnerPlayerControl player, int numPlayers, bool initialSpawn) + { + return new Vector2(-25, 40); + } + + protected override void AddSystems(Dictionary systems) + { + base.AddSystems(systems); + + systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors)); + systems.Add(SystemTypes.Comms, new HudOverrideSystemType()); + systems.Add(SystemTypes.GapRoom, new MovingPlatformBehaviour()); + systems.Add(SystemTypes.Reactor, new HeliSabotageSystemType()); + systems.Add(SystemTypes.Decontamination, new ElectricalDoors(Doors)); + systems.Add(SystemTypes.Decontamination2, new AutoDoorsSystemType(Doors)); + systems.Add(SystemTypes.Security, new SecurityCameraSystemType()); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs new file mode 100644 index 000000000..6f1879de0 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Numerics; +using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Inner.Objects.ShipStatus; +using Impostor.Server.Net.Inner.Objects.Systems; +using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; +using Impostor.Server.Net.State; + +namespace Impostor.Server.Net.Inner.Objects.ShipStatus +{ + internal class InnerMiraShipStatus : InnerShipStatus, IInnerMiraShipStatus + { + public InnerMiraShipStatus(Game game) : base(game) + { + } + + public override IMapData Data => IMapData.Maps[MapTypes.MiraHQ]; + + public override Dictionary Doors { get; } = new Dictionary(0); + + public override float SpawnRadius => 1.55f; + + public override Vector2 InitialSpawnCenter { get; } = new Vector2(-4.4f, 2.2f); + + public override Vector2 MeetingSpawnCenter { get; } = new Vector2(24.043f, 1.72f); + + protected override void AddSystems(Dictionary systems) + { + base.AddSystems(systems); + + systems.Add(SystemTypes.Comms, new HudOverrideSystemType()); + systems.Add(SystemTypes.Reactor, new ReactorSystemType()); + systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType()); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs new file mode 100644 index 000000000..3274727ed --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Numerics; +using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Inner.Objects.ShipStatus; +using Impostor.Server.Net.Inner.Objects.Systems; +using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; +using Impostor.Server.Net.State; + +namespace Impostor.Server.Net.Inner.Objects.ShipStatus +{ + internal class InnerPolusShipStatus : InnerShipStatus, IInnerPolusShipStatus + { + public InnerPolusShipStatus(Game game) : base(game) + { + } + + public override IMapData Data => IMapData.Maps[MapTypes.Polus]; + + public override Dictionary Doors { get; } = new Dictionary(12); + + public override float SpawnRadius => 1f; + + public override Vector2 InitialSpawnCenter { get; } = new Vector2(16.64f, -2.46f); + + public override Vector2 MeetingSpawnCenter { get; } = new Vector2(17.726f, -16.286f); + + public Vector2 MeetingSpawnCenter2 { get; } = new Vector2(-17.7f, -17.5f); + + public override Vector2 GetSpawnLocation(InnerPlayerControl player, int numPlayers, bool initialSpawn) + { + if (initialSpawn) + { + return base.GetSpawnLocation(player, numPlayers, initialSpawn); + } + + Vector2 position; + if (player.PlayerId < 5) + { + position = this.MeetingSpawnCenter + (new Vector2(1, 0) * player.PlayerId); + } + else + { + position = this.MeetingSpawnCenter2 + (new Vector2(1, 0) * (player.PlayerId - 5)); + } + + return position; + } + + protected override void AddSystems(Dictionary systems) + { + base.AddSystems(systems); + + systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors)); + systems.Add(SystemTypes.Comms, new HudOverrideSystemType()); + systems.Add(SystemTypes.Security, new SecurityCameraSystemType()); + systems.Add(SystemTypes.Laboratory, new ReactorSystemType()); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs new file mode 100644 index 000000000..3dd2d5413 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner; +using Impostor.Api.Net.Inner.Objects.ShipStatus; +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; + +namespace Impostor.Server.Net.Inner.Objects.ShipStatus +{ + internal abstract class InnerShipStatus : InnerNetObject, IInnerShipStatus + { + private readonly Dictionary _systems = new Dictionary(); + + protected InnerShipStatus(Game game) : base(game) + { + Components.Add(this); + } + + public abstract IMapData Data { get; } + + public abstract Dictionary Doors { get; } + + public abstract float SpawnRadius { get; } + + public abstract Vector2 InitialSpawnCenter { get; } + + public abstract Vector2 MeetingSpawnCenter { get; } + + public override ValueTask OnSpawnAsync() + { + for (var i = 0; i < Doors.Count; i++) + { + Doors.Add(i, false); + } + + AddSystems(_systems); + _systems.Add(SystemTypes.Sabotage, new SabotageSystemType(_systems.Values.OfType().ToArray())); + + return base.OnSpawnAsync(); + } + + 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) || !await ValidateBroadcast(CheatContext.Deserialize, sender, target)) + { + return; + } + + while (reader.Position < reader.Length) + { + var messageReader = reader.ReadMessage(); + var type = (SystemTypes)messageReader.Tag; + if (_systems.TryGetValue(type, out var value)) + { + value.Deserialize(messageReader, initialState); + } + } + } + + 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(call, 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; + } + + default: + return await UnregisteredCall(call, sender); + } + + return true; + } + + public virtual Vector2 GetSpawnLocation(InnerPlayerControl player, int numPlayers, bool initialSpawn) + { + var vector = new Vector2(0, 1); + vector = Rotate(vector, (player.PlayerId - 1) * (360f / numPlayers)); + vector *= this.SpawnRadius; + return (initialSpawn ? this.InitialSpawnCenter : this.MeetingSpawnCenter) + vector + new Vector2(0f, 0.3636f); + } + + protected virtual void AddSystems(Dictionary systems) + { + systems.Add(SystemTypes.Electrical, new SwitchSystem()); + systems.Add(SystemTypes.MedBay, new MedScanSystem()); + } + + private static Vector2 Rotate(Vector2 self, float degrees) + { + var f = 0.017453292f * degrees; + var cos = MathF.Cos(f); + var sin = MathF.Sin(f); + + return new Vector2((self.X * cos) - (sin * self.Y), (self.X * sin) + (cos * self.Y)); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs new file mode 100644 index 000000000..6670aad68 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Numerics; +using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Inner.Objects.ShipStatus; +using Impostor.Server.Net.Inner.Objects.Systems; +using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; +using Impostor.Server.Net.State; + +namespace Impostor.Server.Net.Inner.Objects.ShipStatus +{ + internal class InnerSkeldShipStatus : InnerShipStatus, IInnerSkeldShipStatus + { + public InnerSkeldShipStatus(Game game) : base(game) + { + } + + public override IMapData Data { get; } = IMapData.Maps[MapTypes.Skeld]; + + public override Dictionary Doors { get; } = new Dictionary(13); + + public override float SpawnRadius => 1.6f; + + public override Vector2 InitialSpawnCenter { get; } = new Vector2(-0.72f, 0.62f); + + public override Vector2 MeetingSpawnCenter { get; } = new Vector2(-0.72f, 0.62f); + + protected override void AddSystems(Dictionary systems) + { + base.AddSystems(systems); + + systems.Add(SystemTypes.Doors, new AutoDoorsSystemType(Doors)); + systems.Add(SystemTypes.Comms, new HudOverrideSystemType()); + systems.Add(SystemTypes.Security, new SecurityCameraSystemType()); + systems.Add(SystemTypes.Reactor, new ReactorSystemType()); + systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType()); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs index e595b25a9..66a164c78 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs @@ -4,4 +4,4 @@ public interface IActivatable { bool IsActive { get; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs index a6ef88e58..441d7c35b 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs @@ -8,4 +8,4 @@ public interface ISystemType void Deserialize(IMessageReader reader, bool initialState); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs new file mode 100644 index 000000000..5a1457508 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class AutoDoorsSystemType : ISystemType + { + private readonly Dictionary _doors; + + public AutoDoorsSystemType(Dictionary doors) + { + _doors = doors; + } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + if (initialState) + { + for (var i = 0; i < _doors.Count; i++) + { + _doors[i] = reader.ReadBoolean(); + } + } + else + { + var num = reader.ReadPackedUInt32(); + + for (var i = 0; i < _doors.Count; i++) + { + if ((num & 1 << i) != 0) + { + _doors[i] = reader.ReadBoolean(); + } + } + } + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs index 64b1f5f01..2bc71301b 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using Impostor.Api.Games; using Impostor.Api.Innersloth; using Impostor.Api.Net.Messages; @@ -8,25 +7,12 @@ namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus { public class DoorsSystemType : ISystemType { - // TODO: AutoDoors + private readonly Dictionary _timers = new Dictionary(); private readonly Dictionary _doors; - public DoorsSystemType(IGame game) + public DoorsSystemType(Dictionary doors) { - var doorCount = game.Options.Map switch - { - MapTypes.Skeld => 13, - MapTypes.MiraHQ => 2, - MapTypes.Polus => 12, - _ => throw new ArgumentOutOfRangeException() - }; - - _doors = new Dictionary(doorCount); - - for (var i = 0; i < doorCount; i++) - { - _doors.Add(i, false); - } + _doors = doors; } public void Serialize(IMessageWriter writer, bool initialState) @@ -36,25 +22,19 @@ public void Serialize(IMessageWriter writer, bool initialState) public void Deserialize(IMessageReader reader, bool initialState) { - if (initialState) + var num = reader.ReadByte(); + for (var i = 0; i < num; i++) { - for (var i = 0; i < _doors.Count; i++) - { - _doors[i] = reader.ReadBoolean(); - } + var systemType = (SystemTypes)reader.ReadByte(); + var value = reader.ReadSingle(); + + _timers[systemType] = value; } - else - { - var num = reader.ReadPackedUInt32(); - for (var i = 0; i < _doors.Count; i++) - { - if ((num & 1 << i) != 0) - { - _doors[i] = reader.ReadBoolean(); - } - } + for (var j = 0; j < _doors.Count; j++) + { + _doors[j] = reader.ReadBoolean(); } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs new file mode 100644 index 000000000..cadb995ca --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class ElectricalDoors : ISystemType + { + private readonly Dictionary _doors; + + public ElectricalDoors(Dictionary doors) + { + _doors = doors; + } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + var num = reader.ReadUInt32(); + for (var i = 0; i < _doors.Count; i++) + { + _doors[i] = (num & (ulong)(1L << (i & 31))) > 0UL; + } + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs new file mode 100644 index 000000000..d72ed5b63 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class HeliSabotageSystemType : ISystemType, IActivatable + { + public HeliSabotageSystemType() + { + Countdown = 10000f; + ActiveConsoles = new HashSet>(); + CompletedConsoles = new HashSet(); + } + + public float Countdown { get; private set; } + + public float Timer { get; private set; } + + public HashSet> ActiveConsoles { get; } + + public HashSet CompletedConsoles { get; } + + public bool IsActive => Countdown < 10000.0; + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + Countdown = reader.ReadSingle(); + Timer = reader.ReadSingle(); + ActiveConsoles.Clear(); // TODO: Thread safety + CompletedConsoles.Clear(); // TODO: Thread safety + + var activeCount = reader.ReadPackedUInt32(); + + for (var i = 0; i < activeCount; i++) + { + ActiveConsoles.Add(new Tuple(reader.ReadByte(), reader.ReadByte())); + } + + var completedCount = reader.ReadPackedUInt32(); + + for (var i = 0; i < completedCount; i++) + { + CompletedConsoles.Add(reader.ReadByte()); + } + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs index 42aa8d379..4349233f0 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Net.Messages; +using System; +using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus { @@ -8,7 +9,7 @@ public class HudOverrideSystemType : ISystemType, IActivatable public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -16,4 +17,4 @@ public void Deserialize(IMessageReader reader, bool initialState) IsActive = reader.ReadBoolean(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs index f64402434..681ebe163 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus @@ -19,7 +20,7 @@ public LifeSuppSystemType() public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -41,4 +42,4 @@ public void Deserialize(IMessageReader reader, bool initialState) } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs index 007b9d0af..4c2287fee 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus @@ -14,7 +15,7 @@ public MedScanSystem() public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -29,4 +30,4 @@ public void Deserialize(IMessageReader reader, bool initialState) } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs new file mode 100644 index 000000000..82cfb1fbd --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs @@ -0,0 +1,22 @@ +using System; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class MovingPlatformBehaviour : ISystemType, IActivatable + { + public bool IsActive { get; private set; } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + var sid = reader.ReadByte(); + var targetId = reader.ReadUInt32(); + var isLeft = reader.ReadBoolean(); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs index 438091846..7bc2ab373 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs @@ -20,7 +20,7 @@ public ReactorSystemType() public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -36,4 +36,4 @@ public void Deserialize(IMessageReader reader, bool initialState) } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs index 193cfe80b..73c10c38f 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Net.Messages; +using System; +using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus { @@ -15,7 +16,7 @@ public SabotageSystemType(IActivatable[] specials) public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -23,4 +24,4 @@ public void Deserialize(IMessageReader reader, bool initialState) Timer = reader.ReadSingle(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs index df41b68ec..d79645560 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Net.Messages; +using System; +using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus { @@ -8,7 +9,7 @@ public class SecurityCameraSystemType : ISystemType public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -16,4 +17,4 @@ public void Deserialize(IMessageReader reader, bool initialState) InUse = reader.ReadByte(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs index 925774ab9..eb2b57260 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs @@ -1,4 +1,5 @@ -using Impostor.Api.Net.Messages; +using System; +using Impostor.Api.Net.Messages; namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus { @@ -14,7 +15,7 @@ public class SwitchSystem : ISystemType, IActivatable public void Serialize(IMessageWriter writer, bool initialState) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public void Deserialize(IMessageReader reader, bool initialState) @@ -24,4 +25,4 @@ public void Deserialize(IMessageReader reader, bool initialState) Value = reader.ReadByte(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Inner/SpawnFlags.cs b/src/Impostor.Server/Net/Inner/SpawnFlags.cs index 1860098ce..28c2a1ab9 100644 --- a/src/Impostor.Server/Net/Inner/SpawnFlags.cs +++ b/src/Impostor.Server/Net/Inner/SpawnFlags.cs @@ -8,4 +8,4 @@ public enum SpawnFlags : byte None = 0, IsClientCharacter = 1, } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Manager/ClientManager.Api.cs b/src/Impostor.Server/Net/Manager/ClientManager.Api.cs index 6cbe5bf61..1ff970dad 100644 --- a/src/Impostor.Server/Net/Manager/ClientManager.Api.cs +++ b/src/Impostor.Server/Net/Manager/ClientManager.Api.cs @@ -8,4 +8,4 @@ internal partial class ClientManager : IClientManager { IEnumerable IClientManager.Clients => _clients.Values; } -} \ 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..7a10181a7 100644 --- a/src/Impostor.Server/Net/Manager/ClientManager.cs +++ b/src/Impostor.Server/Net/Manager/ClientManager.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Impostor.Api.Events.Managers; using Impostor.Api.Innersloth; using Impostor.Api.Net; using Impostor.Api.Net.Messages; @@ -16,11 +17,10 @@ namespace Impostor.Server.Net.Manager { internal partial class ClientManager { - private static HashSet SupportedVersions { get; } = new HashSet + private static readonly HashSet SupportedVersions = new HashSet { - GameVersion.GetVersion(2020, 09, 07), // 2020.09.07 - 2020.09.22 - GameVersion.GetVersion(2020, 10, 08), // 2020.10.08 - GameVersion.GetVersion(2020, 11, 17), // 2020.11.17 + GameVersion.GetVersion(2021, 3, 25), // 2021.3.31s + GameVersion.GetVersion(2021, 4, 2), // 2021.4.2a }; private readonly ILogger _logger; @@ -100,4 +100,4 @@ public bool Validate(IClient client) && ReferenceEquals(client, registeredClient); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Manager/GameManager.cs b/src/Impostor.Server/Net/Manager/GameManager.cs index a37829e58..b10e79164 100644 --- a/src/Impostor.Server/Net/Manager/GameManager.cs +++ b/src/Impostor.Server/Net/Manager/GameManager.cs @@ -42,46 +42,9 @@ public GameManager(ILogger logger, IOptions config, I IEnumerable IGameManager.Games => _games.Select(kv => kv.Value); - IGame IGameManager.Find(GameCode code) => Find(code); + IGame? IGameManager.Find(GameCode code) => Find(code); - public async ValueTask CreateAsync(GameOptionsData options) - { - // TODO: Prevent duplicates when using server redirector using INodeProvider. - var (success, game) = await TryCreateAsync(options); - - for (int i = 0; i < 10 && !success; i++) - { - (success, game) = await TryCreateAsync(options); - } - - if (!success) - { - throw new ImpostorException("Could not create new game"); // TODO: Fix generic exception. - } - - return game; - } - - private async ValueTask<(bool success, Game game)> TryCreateAsync(GameOptionsData options) - { - var gameCode = _gameCodeFactory.Create(); - var gameCodeStr = gameCode.Code; - var game = ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options); - - if (await _nodeLocator.ExistsAsync(gameCodeStr) || !_games.TryAdd(gameCode, game)) - { - return (false, null); - } - - await _nodeLocator.SaveAsync(gameCodeStr, _publicIp); - _logger.LogDebug("Created game with code {0}.", game.Code); - - await _eventManager.CallAsync(new GameCreatedEvent(game)); - - return (true, game); - } - - public Game Find(GameCode code) + public Game? Find(GameCode code) { _games.TryGetValue(code, out var game); return game; @@ -98,7 +61,7 @@ public IEnumerable FindListings(MapFlags map, int impostorCount, GameKeywo x.Value.PlayerCount < x.Value.Options.MaxPlayers)) { // Check for options. - if (!map.HasFlag((MapFlags)(1 << game.Options.MapId))) + if (!map.HasFlag((MapFlags)(1 << (byte)game.Options.Map))) { continue; } @@ -146,5 +109,42 @@ public async ValueTask RemoveAsync(GameCode gameCode) await _eventManager.CallAsync(new GameDestroyedEvent(game)); } + + public async ValueTask CreateAsync(GameOptionsData options) + { + // TODO: Prevent duplicates when using server redirector using INodeProvider. + var (success, game) = await TryCreateAsync(options); + + for (var i = 0; i < 10 && !success; i++) + { + (success, game) = await TryCreateAsync(options); + } + + if (!success || game == null) + { + throw new ImpostorException("Could not create new game"); // TODO: Fix generic exception. + } + + return game; + } + + private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(GameOptionsData options) + { + var gameCode = _gameCodeFactory.Create(); + var gameCodeStr = gameCode.Code; + var game = ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options); + + if (await _nodeLocator.ExistsAsync(gameCodeStr) || !_games.TryAdd(gameCode, game)) + { + return (false, null); + } + + await _nodeLocator.SaveAsync(gameCodeStr, _publicIp); + _logger.LogDebug("Created game with code {0}.", game.Code); + + await _eventManager.CallAsync(new GameCreatedEvent(game)); + + return (true, game); + } } } diff --git a/src/Impostor.Server/Net/Matchmaker.cs b/src/Impostor.Server/Net/Matchmaker.cs index 64ece55f0..6af4f5599 100644 --- a/src/Impostor.Server/Net/Matchmaker.cs +++ b/src/Impostor.Server/Net/Matchmaker.cs @@ -2,6 +2,8 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Impostor.Api.Events.Managers; +using Impostor.Api.Net.Messages.C2S; using Impostor.Hazel; using Impostor.Hazel.Udp; using Impostor.Server.Net.Hazel; @@ -17,7 +19,7 @@ internal class Matchmaker private readonly ObjectPool _readerPool; private readonly ILogger _logger; private readonly ILogger _connectionLogger; - private UdpConnectionListener _connection; + private UdpConnectionListener? _connection; public Matchmaker( ILogger logger, @@ -37,25 +39,29 @@ public async ValueTask StartAsync(IPEndPoint ipEndPoint) { AddressFamily.InterNetwork => IPMode.IPv4, AddressFamily.InterNetworkV6 => IPMode.IPv6, - _ => throw new InvalidOperationException() + _ => throw new InvalidOperationException(), }; - _connection = new UdpConnectionListener(ipEndPoint, _readerPool, mode); - _connection.NewConnection = OnNewConnection; + _connection = new UdpConnectionListener(ipEndPoint, _readerPool, mode) + { + NewConnection = OnNewConnection, + }; await _connection.StartAsync(); } public async ValueTask StopAsync() { - await _connection.DisposeAsync(); + if (_connection != null) + { + await _connection.DisposeAsync(); + } } private async ValueTask OnNewConnection(NewConnectionEventArgs e) { // Handshake. - var clientVersion = e.HandshakeData.ReadInt32(); - var name = e.HandshakeData.ReadString(); + HandshakeC2S.Deserialize(e.HandshakeData, out var clientVersion, out var name, out _); var connection = new HazelConnection(e.Connection, _connectionLogger); diff --git a/src/Impostor.Server/Net/Redirector/ClientRedirector.cs b/src/Impostor.Server/Net/Redirector/ClientRedirector.cs index 3dad4b34d..22dc0a7d1 100644 --- a/src/Impostor.Server/Net/Redirector/ClientRedirector.cs +++ b/src/Impostor.Server/Net/Redirector/ClientRedirector.cs @@ -8,7 +8,6 @@ using Impostor.Server.Net.Hazel; using Impostor.Server.Net.Manager; using Serilog; -using ILogger = Serilog.ILogger; namespace Impostor.Server.Net.Redirector { @@ -22,11 +21,12 @@ internal class ClientRedirector : ClientBase public ClientRedirector( string name, + int gameVersion, HazelConnection connection, ClientManager clientManager, INodeProvider nodeProvider, INodeLocator nodeLocator) - : base(name, connection) + : base(name, gameVersion, connection) { _clientManager = clientManager; _nodeProvider = nodeProvider; @@ -51,13 +51,10 @@ public override async ValueTask HandleMessageAsync(IMessageReader reader, Messag case MessageFlags.JoinGame: { - Message01JoinGameC2S.Deserialize( - reader, - out var gameCode, - out _); + Message01JoinGameC2S.Deserialize(reader, out var gameCode); using var packet = MessageWriter.Get(MessageType.Reliable); - var endpoint = await _nodeLocator.FindAsync(GameCodeParser.IntToGameName(gameCode)); + var endpoint = await _nodeLocator.FindAsync(gameCode); if (endpoint == null) { Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.GameMissing); @@ -94,4 +91,4 @@ public override ValueTask HandleDisconnectAsync(string reason) return default; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Redirector/INodeLocator.cs b/src/Impostor.Server/Net/Redirector/INodeLocator.cs index 12563b9a2..1c3391a9d 100644 --- a/src/Impostor.Server/Net/Redirector/INodeLocator.cs +++ b/src/Impostor.Server/Net/Redirector/INodeLocator.cs @@ -5,10 +5,10 @@ namespace Impostor.Server.Net.Redirector { public interface INodeLocator { - ValueTask FindAsync(string gameCode); + ValueTask FindAsync(string gameCode); ValueTask SaveAsync(string gameCode, IPEndPoint endPoint); ValueTask RemoveAsync(string gameCode); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Redirector/INodeProvider.cs b/src/Impostor.Server/Net/Redirector/INodeProvider.cs index 318fcb2d6..fb7240c89 100644 --- a/src/Impostor.Server/Net/Redirector/INodeProvider.cs +++ b/src/Impostor.Server/Net/Redirector/INodeProvider.cs @@ -6,4 +6,4 @@ internal interface INodeProvider { IPEndPoint Get(); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs b/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs index fd4cd56f7..baef6db44 100644 --- a/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs +++ b/src/Impostor.Server/Net/Redirector/NodeLocatorNoOp.cs @@ -5,10 +5,10 @@ namespace Impostor.Server.Net.Redirector { public class NodeLocatorNoOp : INodeLocator { - public ValueTask FindAsync(string gameCode) => ValueTask.FromResult(default(IPEndPoint)); + public ValueTask FindAsync(string gameCode) => ValueTask.FromResult(default(IPEndPoint)); public ValueTask SaveAsync(string gameCode, IPEndPoint endPoint) => ValueTask.CompletedTask; public ValueTask RemoveAsync(string gameCode) => ValueTask.CompletedTask; } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs b/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs index 0b6fdff81..f4595e353 100644 --- a/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs +++ b/src/Impostor.Server/Net/Redirector/NodeLocatorRedis.cs @@ -16,7 +16,7 @@ public NodeLocatorRedis(ILogger logger, IDistributedCache cach _cache = cache; } - public async ValueTask FindAsync(string gameCode) + public async ValueTask FindAsync(string gameCode) { var entry = await _cache.GetStringAsync(gameCode); if (entry == null) @@ -40,4 +40,4 @@ public async ValueTask RemoveAsync(string gameCode) await _cache.RemoveAsync(gameCode); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs b/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs index 2539a8f2d..c75392adf 100644 --- a/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs +++ b/src/Impostor.Server/Net/Redirector/NodeLocatorUDP.cs @@ -14,9 +14,9 @@ public class NodeLocatorUdp : INodeLocator, IDisposable { private readonly ILogger _logger; private readonly bool _isMaster; - private readonly IPEndPoint _server; - private readonly UdpClient _client; - private readonly ConcurrentDictionary _availableNodes; + private readonly IPEndPoint? _server; + private readonly UdpClient? _client; + private readonly ConcurrentDictionary? _availableNodes; public NodeLocatorUdp(ILogger logger, IOptions config) { @@ -31,7 +31,7 @@ public NodeLocatorUdp(ILogger logger, IOptions {1}", gameCode, ip); - _availableNodes.AddOrUpdate( + _availableNodes!.AddOrUpdate( gameCode, - s => new AvailableNode - { - Endpoint = ip, - LastUpdated = DateTimeOffset.UtcNow, - }, + s => new AvailableNode(ip, DateTimeOffset.UtcNow), (s, node) => { node.Endpoint = ip; @@ -78,14 +74,14 @@ public void Update(IPEndPoint ip, string gameCode) } } - public ValueTask FindAsync(string gameCode) + public ValueTask FindAsync(string gameCode) { if (!_isMaster) { return ValueTask.FromResult(default(IPEndPoint)); } - if (_availableNodes.TryGetValue(gameCode, out var node)) + if (_availableNodes!.TryGetValue(gameCode, out var node)) { if (node.Expired) { @@ -93,7 +89,7 @@ public ValueTask FindAsync(string gameCode) return ValueTask.FromResult(default(IPEndPoint)); } - return ValueTask.FromResult(node.Endpoint); + return ValueTask.FromResult(node.Endpoint)!; } return ValueTask.FromResult(default(IPEndPoint)); @@ -106,14 +102,14 @@ public ValueTask RemoveAsync(string gameCode) return ValueTask.CompletedTask; } - _availableNodes.TryRemove(gameCode, out _); + _availableNodes!.TryRemove(gameCode, out _); return ValueTask.CompletedTask; } public ValueTask SaveAsync(string gameCode, IPEndPoint endPoint) { var data = Encoding.UTF8.GetBytes($"{gameCode},{endPoint}"); - _client.Send(data, data.Length, _server); + _client!.Send(data, data.Length, _server); return ValueTask.CompletedTask; } @@ -124,6 +120,12 @@ public void Dispose() private class AvailableNode { + public AvailableNode(IPEndPoint endpoint, DateTimeOffset lastUpdated) + { + Endpoint = endpoint; + LastUpdated = lastUpdated; + } + public IPEndPoint Endpoint { get; set; } public DateTimeOffset LastUpdated { get; set; } diff --git a/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs b/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs index 3706bb499..781e91f31 100644 --- a/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs +++ b/src/Impostor.Server/Net/Redirector/NodeLocatorUDPService.cs @@ -25,7 +25,7 @@ public NodeLocatorUdpService( _nodeLocator = (NodeLocatorUdp)nodeLocator; _logger = logger; - if (!IPEndPoint.TryParse(options.Value.Locator.UdpMasterEndpoint, out var endpoint)) + if (!IPEndPoint.TryParse(options.Value.Locator!.UdpMasterEndpoint, out var endpoint)) { throw new ArgumentException("UdpMasterEndpoint should be in the ip:port format."); } diff --git a/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs b/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs index 4e19c43cf..7e2982e80 100644 --- a/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs +++ b/src/Impostor.Server/Net/Redirector/NodeProviderConfig.cs @@ -40,4 +40,4 @@ public IPEndPoint Get() } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/State/ClientPlayer.cs b/src/Impostor.Server/Net/State/ClientPlayer.cs index 107edbfef..b615c064a 100644 --- a/src/Impostor.Server/Net/State/ClientPlayer.cs +++ b/src/Impostor.Server/Net/State/ClientPlayer.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Impostor.Api.Net; using Impostor.Api.Net.Inner; +using Impostor.Api.Unity; using Impostor.Server.Net.Inner.Objects; using Microsoft.Extensions.Logging; @@ -16,7 +17,7 @@ internal partial class ClientPlayer : IClientPlayer public ClientPlayer(ILogger logger, ClientBase client, Game game) { _logger = logger; - _spawnTimeout = new Timer(RunSpawnTimeout, null, -1, -1); + _spawnTimeout = new Timer(RunSpawnTimeout!, null, -1, -1); Game = game; Client = client; @@ -34,7 +35,9 @@ public ClientPlayer(ILogger logger, ClientBase client, Game game) public bool IsHost => Game?.Host == this; - public string Scene { get; internal set; } + public string? Scene { get; internal set; } + + public RuntimePlatform? Platform { get; internal set; } public void InitializeSpawnTimeout() { diff --git a/src/Impostor.Server/Net/State/Game.Api.cs b/src/Impostor.Server/Net/State/Game.Api.cs index f395be292..a35858568 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,14 +6,14 @@ using Impostor.Api.Innersloth; using Impostor.Api.Net; using Impostor.Api.Net.Inner; -using Impostor.Api.Net.Inner.Objects; -using Impostor.Server.Net.Inner; +using Impostor.Api.Net.Messages; +using Impostor.Hazel; namespace Impostor.Server.Net.State { internal partial class Game : IGame { - IClientPlayer IGame.Host => Host; + IClientPlayer? IGame.Host => Host; IGameNet IGame.GameNet => GameNet; @@ -25,7 +24,7 @@ public void BanIp(IPAddress ipAddress) public async ValueTask SyncSettingsAsync() { - if (Host.Character == null) + if (Host?.Character == null) { throw new ImpostorException("Attempted to set infected when the host was not spawned."); } @@ -47,23 +46,15 @@ public async ValueTask SyncSettingsAsync() } } - public async ValueTask SetInfectedAsync(IEnumerable players) + public async ValueTask SetPrivacyAsync(bool isPublic) { - if (Host.Character == null) - { - throw new ImpostorException("Attempted to set infected when the host was not spawned."); - } + IsPublic = isPublic; - using (var writer = StartRpc(Host.Character.NetId, RpcCalls.SetInfected)) + using (var writer = MessageWriter.Get(MessageType.Reliable)) { - writer.Write((byte)Host.Character.NetId); + WriteAlterGameMessage(writer, false, IsPublic); - foreach (var player in players) - { - writer.Write((byte)player.PlayerId); - } - - await FinishRpcAsync(writer); + await SendToAllAsync(writer); } } } diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index a84d9b59c..9a09c1b77 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -1,17 +1,16 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using Impostor.Api; -using Impostor.Api.Innersloth; +using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; -using Impostor.Api.Net.Messages.S2C; -using Impostor.Hazel; +using Impostor.Api.Unity; using Impostor.Server.Events.Meeting; using Impostor.Server.Events.Player; using Impostor.Server.Net.Inner; using Impostor.Server.Net.Inner.Objects; using Impostor.Server.Net.Inner.Objects.Components; +using Impostor.Server.Net.Inner.Objects.ShipStatus; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -34,183 +33,41 @@ internal partial class Game private static readonly Type[] SpawnableObjects = { - typeof(InnerShipStatus), // ShipStatus + typeof(InnerSkeldShipStatus), typeof(InnerMeetingHud), typeof(InnerLobbyBehaviour), typeof(InnerGameData), typeof(InnerPlayerControl), - typeof(InnerShipStatus), // HeadQuarters - typeof(InnerShipStatus), // PlanetMap - typeof(InnerShipStatus), // AprilShipStatus + typeof(InnerMiraShipStatus), + typeof(InnerPolusShipStatus), + typeof(InnerSkeldShipStatus), // April fools skeld + typeof(InnerAirshipStatus), }; private readonly List _allObjects = new List(); - private readonly Dictionary _allObjectsFast = new Dictionary(); - - private int _gamedataInitialized; - private bool _gamedataFakeReceived; - - private async ValueTask OnSpawnAsync(InnerNetObject netObj) - { - switch (netObj) - { - case InnerLobbyBehaviour lobby: - { - GameNet.LobbyBehaviour = lobby; - break; - } - case InnerGameData data: - { - GameNet.GameData = data; - break; - } - - case InnerVoteBanSystem voteBan: - { - GameNet.VoteBan = voteBan; - break; - } - - case InnerShipStatus shipStatus: - { - GameNet.ShipStatus = shipStatus; - break; - } - - case InnerPlayerControl control: - { - // Hook up InnerPlayerControl <-> IClientPlayer. - if (!TryGetPlayer(control.OwnerId, out var player)) - { - throw new ImpostorException("Failed to find player that spawned the InnerPlayerControl"); - } - - player.Character = control; - player.DisableSpawnTimeout(); - - // Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo. - control.PlayerInfo = GameNet.GameData.GetPlayerById(control.PlayerId)!; - - if (control.PlayerInfo == null) - { - GameNet.GameData.AddPlayer(control); - } - - if (control.PlayerInfo != null) - { - control.PlayerInfo!.Controller = control; - } - - await _eventManager.CallAsync(new PlayerSpawnedEvent(this, player, control)); - - break; - } - - case InnerMeetingHud meetingHud: - { - await _eventManager.CallAsync(new MeetingStartedEvent(this, meetingHud)); - break; - } - } - } - - private async ValueTask OnDestroyAsync(InnerNetObject netObj) - { - switch (netObj) - { - case InnerLobbyBehaviour: - { - GameNet.LobbyBehaviour = null; - break; - } - - case InnerGameData: - { - GameNet.GameData = null; - break; - } - - case InnerVoteBanSystem: - { - GameNet.VoteBan = null; - break; - } - - case InnerShipStatus: - { - GameNet.ShipStatus = null; - break; - } - - case InnerPlayerControl control: - { - // Remove InnerPlayerControl <-> IClientPlayer. - if (TryGetPlayer(control.OwnerId, out var player)) - { - player.Character = null; - } - - await _eventManager.CallAsync(new PlayerDestroyedEvent(this, player, control)); - - break; - } - } - } + private readonly Dictionary _allObjectsFast = new Dictionary(); - private async ValueTask InitGameDataAsync(ClientPlayer player) + public T? FindObjectByNetId(uint netId) + where T : IInnerNetObject { - if (Interlocked.Exchange(ref _gamedataInitialized, 1) != 0) + if (_allObjectsFast.TryGetValue(netId, out var obj)) { - return; + return (T)(IInnerNetObject)obj; } - /* - * The Among Us client on 20.9.22i spawns some components on the host side and - * only spawns these on other clients when someone else connects. This means that we can't - * parse data until someone connects because we don't know which component belongs to the NetId. - * - * We solve this by spawning a fake player and removing the player when the spawn GameData - * is received in HandleGameDataAsync. - */ - using (var message = MessageWriter.Get(MessageType.Reliable)) - { - // Spawn a fake player. - Message01JoinGameS2C.SerializeJoin(message, false, Code, FakeClientId, HostId); - - message.StartMessage(MessageFlags.GameData); - message.Write(Code); - message.StartMessage(GameDataTag.SceneChangeFlag); - message.WritePacked(FakeClientId); - message.Write("OnlineGame"); - message.EndMessage(); - message.EndMessage(); - - await player.Client.Connection.SendAsync(message); - } + return default; } public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPlayer sender, bool toPlayer) { // Find target player. - ClientPlayer target = null; + ClientPlayer? target = null; if (toPlayer) { var targetId = parent.ReadPackedInt32(); - if (targetId == FakeClientId && !_gamedataFakeReceived && sender.IsHost) - { - _gamedataFakeReceived = true; - - // Remove the fake client, we received the data. - using (var message = MessageWriter.Get(MessageType.Reliable)) - { - WriteRemovePlayerMessage(message, false, FakeClientId, (byte)DisconnectReason.ExitGame); - - await sender.Client.Connection.SendAsync(message); - } - } - else if (!TryGetPlayer(targetId, out target)) + if (!TryGetPlayer(targetId, out target)) { _logger.LogWarning("Player {0} tried to send GameData to unknown player {1}.", sender.Client.Id, targetId); return false; @@ -231,7 +88,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 +103,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,13 +121,16 @@ 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(); if (objectId < SpawnableObjects.Length) { - var innerNetObject = (InnerNetObject) ActivatorUtilities.CreateInstance(_serviceProvider, SpawnableObjects[objectId], this); + var innerNetObject = (InnerNetObject)ActivatorUtilities.CreateInstance(_serviceProvider, SpawnableObjects[objectId], this); var ownerClientId = reader.ReadPackedInt32(); // Prevent fake client from being broadcasted. @@ -277,7 +140,7 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl return false; } - innerNetObject.SpawnFlags = (SpawnFlags) reader.ReadByte(); + innerNetObject.SpawnFlags = (SpawnFlags)reader.ReadByte(); var components = innerNetObject.GetComponentsInChildren(); var componentsCount = reader.ReadPackedInt32(); @@ -322,7 +185,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); @@ -393,9 +256,27 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl break; } + case GameDataTag.ConsoleDeclareClientPlatformFlag: + { + var clientId = reader.ReadPackedInt32(); + var platform = (RuntimePlatform)reader.ReadPackedInt32(); + + if (clientId != sender.Client.Id) + { + if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.ConsoleDeclareClientPlatformFlag)), "Client sent info with wrong client id")) + { + return false; + } + } + + sender.Platform = platform; + + break; + } + default: { - _logger.LogTrace("Bad GameData tag {0}", reader.Tag); + _logger.LogWarning("Bad GameData tag {0}", reader.Tag); break; } } @@ -410,6 +291,116 @@ public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPl return true; } + private async ValueTask OnSpawnAsync(InnerNetObject netObj) + { + switch (netObj) + { + case InnerLobbyBehaviour lobby: + { + GameNet.LobbyBehaviour = lobby; + break; + } + + case InnerGameData data: + { + GameNet.GameData = data; + break; + } + + case InnerVoteBanSystem voteBan: + { + GameNet.VoteBan = voteBan; + break; + } + + case InnerShipStatus shipStatus: + { + GameNet.ShipStatus = shipStatus; + break; + } + + case InnerPlayerControl control: + { + // Hook up InnerPlayerControl <-> IClientPlayer. + if (!TryGetPlayer(control.OwnerId, out var player)) + { + throw new ImpostorException("Failed to find player that spawned the InnerPlayerControl"); + } + + player.Character = control; + player.DisableSpawnTimeout(); + + // Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo. + var playerInfo = GameNet.GameData!.GetPlayerById(control.PlayerId) ?? GameNet.GameData.AddPlayer(control); + + if (playerInfo != null) + { + playerInfo.Controller = control; + control.PlayerInfo = playerInfo; + } + + await _eventManager.CallAsync(new PlayerSpawnedEvent(this, player, control)); + + break; + } + + case InnerMeetingHud meetingHud: + { + foreach (var player in _players.Values) + { + player.Character?.NetworkTransform.OnPlayerSpawn(); + } + + await _eventManager.CallAsync(new MeetingStartedEvent(this, meetingHud)); + break; + } + } + + await netObj.OnSpawnAsync(); + } + + private async ValueTask OnDestroyAsync(InnerNetObject netObj) + { + switch (netObj) + { + case InnerLobbyBehaviour: + { + GameNet.LobbyBehaviour = null; + break; + } + + case InnerGameData: + { + GameNet.GameData = null; + break; + } + + case InnerVoteBanSystem: + { + GameNet.VoteBan = null; + break; + } + + case InnerShipStatus: + { + GameNet.ShipStatus = null; + break; + } + + case InnerPlayerControl control: + { + // Remove InnerPlayerControl <-> IClientPlayer. + if (TryGetPlayer(control.OwnerId, out var player)) + { + player.Character = null; + await _eventManager.CallAsync(new PlayerDestroyedEvent(this, player, control)); + } + + break; + } + } + } + private bool AddNetObject(InnerNetObject obj) { if (_allObjectsFast.ContainsKey(obj.NetId)) @@ -434,16 +425,5 @@ private void RemoveNetObject(InnerNetObject obj) obj.NetId = uint.MaxValue; } - - public T FindObjectByNetId(uint netId) - where T : InnerNetObject - { - if (_allObjectsFast.TryGetValue(netId, out var obj)) - { - return (T) obj; - } - - return null; - } } } diff --git a/src/Impostor.Server/Net/State/Game.Incoming.cs b/src/Impostor.Server/Net/State/Game.Incoming.cs index 4bf1c43c0..6e1620803 100644 --- a/src/Impostor.Server/Net/State/Game.Incoming.cs +++ b/src/Impostor.Server/Net/State/Game.Incoming.cs @@ -27,90 +27,6 @@ public async ValueTask HandleStartGame(IMessageReader message) await _eventManager.CallAsync(new GameStartingEvent(this)); } - public async ValueTask AddClientAsync(ClientBase client) - { - var hasLock = false; - - try - { - hasLock = await _clientAddLock.WaitAsync(TimeSpan.FromMinutes(1)); - - if (hasLock) - { - return await AddClientSafeAsync(client); - } - } - finally - { - if (hasLock) - { - _clientAddLock.Release(); - } - } - - return GameJoinResult.FromError(GameJoinError.InvalidClient); - } - - private async ValueTask AddClientSafeAsync(ClientBase client) - { - // Check if the IP of the player is banned. - if (client.Connection != null && _bannedIps.Contains(client.Connection.EndPoint.Address)) - { - return GameJoinResult.FromError(GameJoinError.Banned); - } - - var player = client.Player; - - // Check if; - // - The player is already in this game. - // - The game is full. - if (player?.Game != this && _players.Count >= Options.MaxPlayers) - { - return GameJoinResult.FromError(GameJoinError.GameFull); - } - - if (GameState == GameStates.Starting || GameState == GameStates.Started) - { - return GameJoinResult.FromError(GameJoinError.GameStarted); - } - - if (GameState == GameStates.Destroyed) - { - return GameJoinResult.FromError(GameJoinError.GameDestroyed); - } - - var isNew = false; - - if (player == null || player.Game != this) - { - var clientPlayer = new ClientPlayer(_serviceProvider.GetRequiredService>(), client, this); - - if (!_clientManager.Validate(client)) - { - return GameJoinResult.FromError(GameJoinError.InvalidClient); - } - - isNew = true; - player = clientPlayer; - client.Player = clientPlayer; - } - - // Check current player state. - if (player.Limbo == LimboStates.NotLimbo) - { - return GameJoinResult.FromError(GameJoinError.InvalidLimbo); - } - - if (GameState == GameStates.Ended) - { - await HandleJoinGameNext(player, isNew); - return GameJoinResult.CreateSuccess(player); - } - - await HandleJoinGameNew(player, isNew); - return GameJoinResult.CreateSuccess(player); - } - public async ValueTask HandleEndGame(IMessageReader message, GameOverReason gameOverReason) { GameState = GameStates.Ended; @@ -179,6 +95,30 @@ public async ValueTask HandleKickPlayer(int playerId, bool isBan) await SendToAllExceptAsync(message, playerId); } + public async ValueTask AddClientAsync(ClientBase client) + { + var hasLock = false; + + try + { + hasLock = await _clientAddLock.WaitAsync(TimeSpan.FromMinutes(1)); + + if (hasLock) + { + return await AddClientSafeAsync(client); + } + } + finally + { + if (hasLock) + { + _clientAddLock.Release(); + } + } + + return GameJoinResult.FromError(GameJoinError.InvalidClient); + } + private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew) { _logger.LogInformation("{0} - Player {1} ({2}) is joining.", Code, sender.Client.Name, sender.Client.Id); @@ -203,6 +143,66 @@ private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew) } } + private async ValueTask AddClientSafeAsync(ClientBase client) + { + // Check if the IP of the player is banned. + if (_bannedIps.Contains(client.Connection.EndPoint.Address)) + { + return GameJoinResult.FromError(GameJoinError.Banned); + } + + var player = client.Player; + + // Check if; + // - The player is already in this game. + // - The game is full. + if (player?.Game != this && _players.Count >= Options.MaxPlayers) + { + return GameJoinResult.FromError(GameJoinError.GameFull); + } + + if (GameState == GameStates.Starting || GameState == GameStates.Started) + { + return GameJoinResult.FromError(GameJoinError.GameStarted); + } + + if (GameState == GameStates.Destroyed) + { + return GameJoinResult.FromError(GameJoinError.GameDestroyed); + } + + var isNew = false; + + if (player == null || player.Game != this) + { + var clientPlayer = new ClientPlayer(_serviceProvider.GetRequiredService>(), client, this); + + if (!_clientManager.Validate(client)) + { + return GameJoinResult.FromError(GameJoinError.InvalidClient); + } + + isNew = true; + player = clientPlayer; + client.Player = clientPlayer; + } + + // Check current player state. + if (player.Limbo == LimboStates.NotLimbo) + { + return GameJoinResult.FromError(GameJoinError.InvalidLimbo); + } + + if (GameState == GameStates.Ended) + { + await HandleJoinGameNext(player, isNew); + return GameJoinResult.CreateSuccess(player); + } + + await HandleJoinGameNew(player, isNew); + return GameJoinResult.CreateSuccess(player); + } + private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew) { _logger.LogInformation("{0} - Player {1} ({2}) is rejoining.", Code, sender.Client.Name, sender.Client.Id); diff --git a/src/Impostor.Server/Net/State/Game.Outgoing.cs b/src/Impostor.Server/Net/State/Game.Outgoing.cs index 21b606701..b5b654eaf 100644 --- a/src/Impostor.Server/Net/State/Game.Outgoing.cs +++ b/src/Impostor.Server/Net/State/Game.Outgoing.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Impostor.Api.Innersloth; using Impostor.Api.Net; +using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.S2C; using Impostor.Hazel; @@ -55,7 +56,7 @@ internal IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int targetCl writer.StartMessage(GameDataTag.RpcFlag); writer.WritePacked(targetNetId); - writer.Write((byte) callId); + writer.Write((byte)callId); return writer; } diff --git a/src/Impostor.Server/Net/State/Game.State.cs b/src/Impostor.Server/Net/State/Game.State.cs index e31177607..a2105f146 100644 --- a/src/Impostor.Server/Net/State/Game.State.cs +++ b/src/Impostor.Server/Net/State/Game.State.cs @@ -25,7 +25,6 @@ private async ValueTask PlayerAdd(ClientPlayer player) if (HostId == -1) { HostId = player.Client.Id; - await InitGameDataAsync(player); } await _eventManager.CallAsync(new GamePlayerJoinedEvent(this, player)); @@ -51,8 +50,15 @@ private async ValueTask PlayerRemove(int playerId, bool isBan = false) player.Client.Player = null; + // Host migration. + if (HostId == playerId) + { + await MigrateHost(); + await _eventManager.CallAsync(new GameHostChangedEvent(this, player, Host)); + } + // Game is empty, remove it. - if (_players.IsEmpty) + if (_players.IsEmpty || Host == null) { GameState = GameStates.Destroyed; @@ -61,13 +67,7 @@ private async ValueTask PlayerRemove(int playerId, bool isBan = false) return true; } - // Host migration. - if (HostId == playerId) - { - await MigrateHost(); - } - - if (isBan && player.Client.Connection != null) + if (isBan) { BanIp(player.Client.Connection.EndPoint.Address); } @@ -98,10 +98,15 @@ private async ValueTask MigrateHost() if (host == null) { - await EndAsync(); 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 +138,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..2d28eeadd 100644 --- a/src/Impostor.Server/Net/State/Game.cs +++ b/src/Impostor.Server/Net/State/Game.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; +using System.Numerics; using System.Threading.Tasks; using Impostor.Api.Events.Managers; using Impostor.Api.Games; @@ -59,23 +61,25 @@ public Game( public bool IsPublic { get; private set; } + public string? DisplayName { get; set; } + public int HostId { get; private set; } public GameStates GameState { get; private set; } - internal GameNet GameNet { get; } - public GameOptionsData Options { get; } public IDictionary Items { get; } public int PlayerCount => _players.Count; - public ClientPlayer Host => _players[HostId]; + public ClientPlayer? Host => _players.GetValueOrDefault(HostId); public IEnumerable Players => _players.Select(p => p.Value); - public bool TryGetPlayer(int id, out ClientPlayer player) + internal GameNet GameNet { get; } + + public bool TryGetPlayer(int id, [MaybeNullWhen(false)] out ClientPlayer player) { if (_players.TryGetValue(id, out var result)) { @@ -87,26 +91,25 @@ public bool TryGetPlayer(int id, out ClientPlayer player) return false; } - public IClientPlayer GetClientPlayer(int clientId) + 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) { + foreach (var player in _players.Values) + { + player.Character?.NetworkTransform.OnPlayerSpawn(); + await player.Character!.NetworkTransform.SetPositionAsync(player, GameNet.ShipStatus!.GetSpawnLocation(player.Character, PlayerCount, true), Vector2.Zero); + } + GameState = GameStates.Started; - return _eventManager.CallAsync(new GameStartedEvent(this)); + await _eventManager.CallAsync(new GameStartedEvent(this)); } - - return default; - } - - public ValueTask EndAsync() - { - return _gameManager.RemoveAsync(Code); } private ValueTask BroadcastJoinMessage(IMessageWriter message, bool clear, ClientPlayer player) @@ -120,7 +123,7 @@ private IEnumerable GetConnections(Func f { return Players .Where(filter) - .Select(p => p.Client.Connection); + .Select(p => p.Client.Connection)!; } } } diff --git a/src/Impostor.Server/Net/State/GameNet.Api.cs b/src/Impostor.Server/Net/State/GameNet.Api.cs index 34ea0fe64..3af856ce4 100644 --- a/src/Impostor.Server/Net/State/GameNet.Api.cs +++ b/src/Impostor.Server/Net/State/GameNet.Api.cs @@ -1,17 +1,18 @@ using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Inner.Objects.ShipStatus; namespace Impostor.Server.Net.State { /// internal partial class GameNet : IGameNet { - IInnerLobbyBehaviour IGameNet.LobbyBehaviour => LobbyBehaviour; + IInnerLobbyBehaviour? IGameNet.LobbyBehaviour => LobbyBehaviour; - IInnerGameData IGameNet.GameData => GameData; + IInnerGameData? IGameNet.GameData => GameData; - IInnerVoteBanSystem IGameNet.VoteBan => VoteBan; + IInnerVoteBanSystem? IGameNet.VoteBan => VoteBan; - IInnerShipStatus IGameNet.ShipStatus => ShipStatus; + IInnerShipStatus? IGameNet.ShipStatus => ShipStatus; } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Net/State/GameNet.cs b/src/Impostor.Server/Net/State/GameNet.cs index c5542f9d0..bb7a8e253 100644 --- a/src/Impostor.Server/Net/State/GameNet.cs +++ b/src/Impostor.Server/Net/State/GameNet.cs @@ -1,16 +1,17 @@ using Impostor.Server.Net.Inner.Objects; using Impostor.Server.Net.Inner.Objects.Components; +using Impostor.Server.Net.Inner.Objects.ShipStatus; namespace Impostor.Server.Net.State { internal partial class GameNet { - public InnerLobbyBehaviour LobbyBehaviour { get; internal set; } + public InnerLobbyBehaviour? LobbyBehaviour { get; internal set; } - public InnerGameData GameData { get; internal set; } + public InnerGameData? GameData { get; internal set; } - public InnerVoteBanSystem VoteBan { get; internal set; } + public InnerVoteBanSystem? VoteBan { get; internal set; } - public InnerShipStatus ShipStatus { get; internal set; } + public InnerShipStatus? ShipStatus { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/AssemblyInformation.cs b/src/Impostor.Server/Plugins/AssemblyInformation.cs index 5f6aee113..df1bf84ea 100644 --- a/src/Impostor.Server/Plugins/AssemblyInformation.cs +++ b/src/Impostor.Server/Plugins/AssemblyInformation.cs @@ -6,7 +6,7 @@ namespace Impostor.Server.Plugins { public class AssemblyInformation : IAssemblyInformation { - private Assembly _assembly; + private Assembly? _assembly; public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin) { @@ -35,4 +35,4 @@ public Assembly Load(AssemblyLoadContext context) return _assembly; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/IAssemblyInformation.cs b/src/Impostor.Server/Plugins/IAssemblyInformation.cs index fb36e92a5..f724cf29e 100644 --- a/src/Impostor.Server/Plugins/IAssemblyInformation.cs +++ b/src/Impostor.Server/Plugins/IAssemblyInformation.cs @@ -11,4 +11,4 @@ public interface IAssemblyInformation Assembly Load(AssemblyLoadContext context); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs b/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs index 720367cc9..5f7d0bbc1 100644 --- a/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs +++ b/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs @@ -22,4 +22,4 @@ public Assembly Load(AssemblyLoadContext context) return _assembly; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/PluginConfig.cs b/src/Impostor.Server/Plugins/PluginConfig.cs index 22dc9e90d..66afceb33 100644 --- a/src/Impostor.Server/Plugins/PluginConfig.cs +++ b/src/Impostor.Server/Plugins/PluginConfig.cs @@ -8,4 +8,4 @@ public class PluginConfig public List LibraryPaths { get; set; } = new List(); } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/PluginInformation.cs b/src/Impostor.Server/Plugins/PluginInformation.cs index e6a5b6c81..f9f61a1fe 100644 --- a/src/Impostor.Server/Plugins/PluginInformation.cs +++ b/src/Impostor.Server/Plugins/PluginInformation.cs @@ -8,9 +8,9 @@ public class PluginInformation { private readonly ImpostorPluginAttribute _attribute; - public PluginInformation(IPluginStartup startup, Type pluginType) + public PluginInformation(IPluginStartup? startup, Type pluginType) { - _attribute = pluginType.GetCustomAttribute(); + _attribute = pluginType.GetCustomAttribute()!; Startup = startup; PluginType = pluginType; @@ -24,15 +24,15 @@ public PluginInformation(IPluginStartup startup, Type pluginType) public string Version => _attribute.Version; - public IPluginStartup Startup { get; } + public IPluginStartup? Startup { get; } public Type PluginType { get; } - public IPlugin Instance { get; set; } + public IPlugin? Instance { get; set; } public override string ToString() { return $"{Package} {Name} ({Version}) by {Author}"; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/PluginLoader.cs b/src/Impostor.Server/Plugins/PluginLoader.cs index 4e728868f..90b9caf83 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; @@ -25,6 +24,8 @@ public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConf // Add the plugins and libraries. var pluginPaths = new List(config.Paths); var libraryPaths = new List(config.LibraryPaths); + CheckPaths(pluginPaths); + CheckPaths(libraryPaths); var rootFolder = Directory.GetCurrentDirectory(); @@ -46,7 +47,7 @@ public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConf // Some plugins may be referencing another Impostor.Api version and try to load it. // We want to only use the one shipped with the server. - if (name.Name.Equals("Impostor.Api")) + if (name.Name == "Impostor.Api") { return typeof(IPlugin).Assembly; } @@ -103,24 +104,35 @@ public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConf plugin.First())); } - foreach (var plugin in plugins.Where(plugin => plugin.Startup != null)) + foreach (var plugin in plugins) { - plugin.Startup.ConfigureHost(builder); + plugin.Startup?.ConfigureHost(builder); } builder.ConfigureServices(services => { services.AddHostedService(provider => ActivatorUtilities.CreateInstance(provider, plugins)); - foreach (var plugin in plugins.Where(plugin => plugin.Startup != null)) + foreach (var plugin in plugins) { - plugin.Startup.ConfigureServices(services); + plugin.Startup?.ConfigureServices(services); } }); return builder; } + private static void CheckPaths(IEnumerable paths) + { + foreach (var path in paths) + { + if (!Directory.Exists(path)) + { + Logger.Warning("Path {path} was specified in the PluginLoader configuration, but this folder doesn't exist!", path); + } + } + } + private static void RegisterAssemblies( IEnumerable paths, Matcher matcher, diff --git a/src/Impostor.Server/Plugins/PluginLoaderException.cs b/src/Impostor.Server/Plugins/PluginLoaderException.cs index 64424a14e..740cc4686 100644 --- a/src/Impostor.Server/Plugins/PluginLoaderException.cs +++ b/src/Impostor.Server/Plugins/PluginLoaderException.cs @@ -10,16 +10,16 @@ public PluginLoaderException() { } - protected PluginLoaderException(SerializationInfo info, StreamingContext context) : base(info, context) + public PluginLoaderException(string? message) : base(message) { } - public PluginLoaderException(string? message) : base(message) + public PluginLoaderException(string? message, Exception? innerException) : base(message, innerException) { } - public PluginLoaderException(string? message, Exception? innerException) : base(message, innerException) + protected PluginLoaderException(SerializationInfo info, StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Plugins/PluginLoaderService.cs b/src/Impostor.Server/Plugins/PluginLoaderService.cs index 0afbc2239..40e13b5b8 100644 --- a/src/Impostor.Server/Plugins/PluginLoaderService.cs +++ b/src/Impostor.Server/Plugins/PluginLoaderService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Impostor.Api.Plugins; @@ -32,7 +31,7 @@ public async Task StartAsync(CancellationToken cancellationToken) _logger.LogInformation("Enabling plugin {0}.", plugin); // Create instance and inject services. - plugin.Instance = (IPlugin) ActivatorUtilities.CreateInstance(_serviceProvider, plugin.PluginType); + plugin.Instance = (IPlugin)ActivatorUtilities.CreateInstance(_serviceProvider, plugin.PluginType); // Enable plugin. await plugin.Instance.EnableAsync(); @@ -41,20 +40,25 @@ public async Task StartAsync(CancellationToken cancellationToken) _logger.LogInformation( _plugins.Count == 1 ? "Loaded {0} plugin." - : "Loaded {0} plugins.", _plugins.Count); + : "Loaded {0} plugins.", + _plugins.Count + ); } public async Task StopAsync(CancellationToken cancellationToken) { // Disable all plugins with a valid instance set. // In the case of a failed startup, some can be null. - foreach (var plugin in _plugins.Where(plugin => plugin.Instance != null)) + foreach (var plugin in _plugins) { - _logger.LogInformation("Disabling plugin {0}.", plugin); + if (plugin.Instance != null) + { + _logger.LogInformation("Disabling plugin {0}.", plugin); - // Disable plugin. - await plugin.Instance.DisableAsync(); + // Disable plugin. + await plugin.Instance.DisableAsync(); + } } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Program.cs b/src/Impostor.Server/Program.cs index d11694f71..d82a23626 100644 --- a/src/Impostor.Server/Program.cs +++ b/src/Impostor.Server/Program.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Games.Managers; @@ -58,7 +59,7 @@ private static int Main(string[] args) try { - Log.Information("Starting Impostor v{0}", DotnetUtils.GetVersion()); + Log.Information("Starting Impostor v{0}", DotnetUtils.Version); CreateHostBuilder(args).Build().Run(); return 0; } @@ -112,14 +113,27 @@ private static IHostBuilder CreateHostBuilder(string[] args) .GetSection(ServerRedirectorConfig.Section) .Get() ?? new ServerRedirectorConfig(); + var announcementsServer = host.Configuration + .GetSection(AnnouncementsServerConfig.Section) + .Get() ?? new AnnouncementsServerConfig(); + + var authServer = host.Configuration + .GetSection(AuthServerConfig.Section) + .Get() ?? new AuthServerConfig(); + + services.AddSingleton(); + 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(AuthServerConfig.Section)); services.Configure(host.Configuration.GetSection(ServerRedirectorConfig.Section)); if (redirector.Enabled) { - if (!string.IsNullOrEmpty(redirector.Locator.Redis)) + if (!string.IsNullOrEmpty(redirector.Locator?.Redis)) { // When joining a game, it retrieves the game server ip from redis. // When a game has been created on this node, it stores the game code with its ip in redis. @@ -132,7 +146,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) options.InstanceName = "ImpostorRedis"; }); } - else if (!string.IsNullOrEmpty(redirector.Locator.UdpMasterEndpoint)) + else if (!string.IsNullOrEmpty(redirector.Locator?.UdpMasterEndpoint)) { services.AddSingleton(); @@ -191,12 +205,23 @@ 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(); + } + + if (authServer.Enabled) + { + services.AddHostedService(); + } }) .UseSerilog() .UseConsoleLifetime() diff --git a/src/Impostor.Server/ProjectRules.ruleset b/src/Impostor.Server/ProjectRules.ruleset deleted file mode 100644 index 3654bc34c..000000000 --- a/src/Impostor.Server/ProjectRules.ruleset +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Impostor.Server/Properties/AssemblyInfo.cs b/src/Impostor.Server/Properties/AssemblyInfo.cs index 84c51585a..eed7af613 100644 --- a/src/Impostor.Server/Properties/AssemblyInfo.cs +++ b/src/Impostor.Server/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Impostor.Benchmarks")] -[assembly:InternalsVisibleTo("Impostor.Tests")] -[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")] +[assembly: InternalsVisibleTo("Impostor.Benchmarks")] +[assembly: InternalsVisibleTo("Impostor.Tests")] +[assembly: InternalsVisibleTo("Impostor.Tools.ServerReplay")] diff --git a/src/Impostor.Server/RealDateTimeProvider.cs b/src/Impostor.Server/RealDateTimeProvider.cs new file mode 100644 index 000000000..506e0c0d9 --- /dev/null +++ b/src/Impostor.Server/RealDateTimeProvider.cs @@ -0,0 +1,10 @@ +using System; +using Impostor.Api; + +namespace Impostor.Server +{ + public class RealDateTimeProvider : IDateTimeProvider + { + public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; + } +} diff --git a/src/Impostor.Server/Recorder/ClientRecorder.cs b/src/Impostor.Server/Recorder/ClientRecorder.cs index 5763c70b1..31af22a5d 100644 --- a/src/Impostor.Server/Recorder/ClientRecorder.cs +++ b/src/Impostor.Server/Recorder/ClientRecorder.cs @@ -16,8 +16,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, int gameVersion, HazelConnection connection, PacketRecorder recorder) + : base(logger, antiCheatOptions, clientManager, gameManager, name, gameVersion, connection) { _recorder = recorder; _isFirst = true; diff --git a/src/Impostor.Server/Recorder/PacketRecorder.cs b/src/Impostor.Server/Recorder/PacketRecorder.cs index af2ea4f1f..12985f1a2 100644 --- a/src/Impostor.Server/Recorder/PacketRecorder.cs +++ b/src/Impostor.Server/Recorder/PacketRecorder.cs @@ -8,6 +8,7 @@ using Impostor.Api.Net.Messages; using Impostor.Server.Config; using Impostor.Server.Net; +using Impostor.Server.Utils; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; @@ -16,7 +17,7 @@ namespace Impostor.Server.Recorder { /// - /// Records all packets received in . + /// Records all packets received in . /// internal class PacketRecorder : BackgroundService { @@ -24,12 +25,13 @@ internal class PacketRecorder : BackgroundService private readonly ILogger _logger; private readonly ObjectPool _pool; private readonly Channel _channel; + private DateTimeOffset _startTime; public PacketRecorder(ILogger logger, IOptions options, ObjectPool pool) { var name = $"session_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}.dat"; - _path = Path.Combine(options.Value.GameRecorderPath, name); + _path = Path.Combine(options.Value.GameRecorderPath!, name); _logger = logger; _pool = pool; @@ -40,31 +42,6 @@ public PacketRecorder(ILogger logger, IOptions opti }); } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _logger.LogInformation("PacketRecorder is enabled, writing packets to {0}.", _path); - - var writer = File.Open(_path, FileMode.CreateNew, FileAccess.Write, FileShare.Read); - - // Handle messages. - try - { - while (!stoppingToken.IsCancellationRequested) - { - var result = await _channel.Reader.ReadAsync(stoppingToken); - - await writer.WriteAsync(result, stoppingToken); - await writer.FlushAsync(stoppingToken); - } - } - catch (TaskCanceledException) - { - } - - // Clean up. - await writer.DisposeAsync(); - } - public async Task WriteConnectAsync(ClientRecorder client) { _logger.LogTrace("Writing Connect."); @@ -73,11 +50,11 @@ public async Task WriteConnectAsync(ClientRecorder client) try { - WriteHeader(context, RecordedPacketType.Connect); + WritePacketHeader(context, RecordedPacketType.Connect); WriteClient(context, client, true); WriteLength(context); - await WriteAsync(context.Stream); + await WriteAsync(context.Stream!); } finally { @@ -93,12 +70,12 @@ public async Task WriteDisconnectAsync(ClientRecorder client, string reason) try { - WriteHeader(context, RecordedPacketType.Disconnect); + WritePacketHeader(context, RecordedPacketType.Disconnect); WriteClient(context, client, false); context.Writer.Write(reason); WriteLength(context); - await WriteAsync(context.Stream); + await WriteAsync(context.Stream!); } finally { @@ -114,12 +91,12 @@ public async Task WriteMessageAsync(ClientRecorder client, IMessageReader reader try { - WriteHeader(context, RecordedPacketType.Message); + WritePacketHeader(context, RecordedPacketType.Message); WriteClient(context, client, false); WritePacket(context, reader, messageType); WriteLength(context); - await WriteAsync(context.Stream); + await WriteAsync(context.Stream!); } finally { @@ -135,12 +112,58 @@ public async Task WriteGameCreatedAsync(ClientRecorder client, GameCode gameCode try { - WriteHeader(context, RecordedPacketType.GameCreated); + WritePacketHeader(context, RecordedPacketType.GameCreated); WriteClient(context, client, false); WriteGameCode(context, gameCode); WriteLength(context); - await WriteAsync(context.Stream); + await WriteAsync(context.Stream!); + } + finally + { + _pool.Return(context); + } + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _startTime = DateTimeOffset.UtcNow; + _logger.LogInformation("PacketRecorder is enabled, writing packets to {0}.", _path); + + var writer = File.Open(_path, FileMode.CreateNew, FileAccess.Write, FileShare.Read); + + await WriteFileHeaderAsync(); + + // Handle messages. + try + { + while (!stoppingToken.IsCancellationRequested) + { + var result = await _channel.Reader.ReadAsync(stoppingToken); + + await writer.WriteAsync(result, stoppingToken); + await writer.FlushAsync(stoppingToken); + } + } + catch (TaskCanceledException) + { + } + + // Clean up. + await writer.DisposeAsync(); + } + + private async Task WriteFileHeaderAsync() + { + var context = _pool.Get(); + + try + { + context.Writer.Write((uint)ServerReplayVersion.Initial); + context.Writer.Write(_startTime.ToUnixTimeMilliseconds()); + context.Writer.Write(DotnetUtils.Version); + + await WriteAsync(context.Stream!); } finally { @@ -148,14 +171,18 @@ public async Task WriteGameCreatedAsync(ClientRecorder client, GameCode gameCode } } - private static void WriteHeader(PacketSerializationContext context, RecordedPacketType type) + private void WritePacketHeader(PacketSerializationContext context, RecordedPacketType type) { // Length placeholder. - context.Writer.Write((int) 0); - context.Writer.Write((byte) type); + context.Writer.Write(0); + + // Timestamp relative to recording start time. + context.Writer.Write((uint)(DateTimeOffset.UtcNow - _startTime).TotalMilliseconds); + + context.Writer.Write((byte)type); } - private static void WriteClient(PacketSerializationContext context, ClientBase client, bool full) + private void WriteClient(PacketSerializationContext context, ClientBase client, bool full) { var address = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345); var addressBytes = address.Address.GetAddressBytes(); @@ -164,32 +191,33 @@ private static void WriteClient(PacketSerializationContext context, ClientBase c if (full) { - context.Writer.Write((byte) addressBytes.Length); + context.Writer.Write((byte)addressBytes.Length); context.Writer.Write(addressBytes); - context.Writer.Write((ushort) address.Port); + context.Writer.Write((ushort)address.Port); context.Writer.Write(client.Name); + context.Writer.Write(client.GameVersion); } } - private static void WritePacket(PacketSerializationContext context, IMessageReader reader, MessageType messageType) + private void WritePacket(PacketSerializationContext context, IMessageReader reader, MessageType messageType) { - context.Writer.Write((byte) messageType); - context.Writer.Write((byte) reader.Tag); - context.Writer.Write((int) reader.Length); + context.Writer.Write((byte)messageType); + context.Writer.Write((byte)reader.Tag); + context.Writer.Write((int)reader.Length); context.Writer.Write(reader.Buffer, reader.Offset, reader.Length); } - private static void WriteGameCode(PacketSerializationContext context, in GameCode gameCode) + private void WriteGameCode(PacketSerializationContext context, in GameCode gameCode) { context.Writer.Write(gameCode.Code); } - private static void WriteLength(PacketSerializationContext context) + private void WriteLength(PacketSerializationContext context) { var length = context.Stream.Position; context.Stream.Position = 0; - context.Writer.Write((int) length); + context.Writer.Write((int)length); context.Stream.Position = length; } diff --git a/src/Impostor.Server/Recorder/PacketSerializationContext.cs b/src/Impostor.Server/Recorder/PacketSerializationContext.cs index 07755f6b0..2290bcb42 100644 --- a/src/Impostor.Server/Recorder/PacketSerializationContext.cs +++ b/src/Impostor.Server/Recorder/PacketSerializationContext.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Text; namespace Impostor.Server.Recorder @@ -8,33 +9,25 @@ public class PacketSerializationContext private const int InitialStreamSize = 0x100; private const int MaximumStreamSize = 0x100000; - private MemoryStream _memory; - private BinaryWriter _writer; + private MemoryStream? _memory; + private BinaryWriter? _writer; + [AllowNull] public MemoryStream Stream { get { - if (_memory == null) - { - _memory = new MemoryStream(InitialStreamSize); - } - - return _memory; + return _memory ??= new MemoryStream(InitialStreamSize); } private set => _memory = value; } + [AllowNull] public BinaryWriter Writer { get { - if (_writer == null) - { - _writer = new BinaryWriter(Stream, Encoding.UTF8, true); - } - - return _writer; + return _writer ??= new BinaryWriter(Stream, Encoding.UTF8, true); } private set => _writer = value; } @@ -53,4 +46,4 @@ public void Reset() } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs b/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs index 17b355f54..e45948e84 100644 --- a/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs +++ b/src/Impostor.Server/Recorder/PacketSerializationContextPooledObjectPolicy.cs @@ -15,4 +15,4 @@ public bool Return(PacketSerializationContext obj) return true; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Recorder/RecordedPacketType.cs b/src/Impostor.Server/Recorder/RecordedPacketType.cs index a8a20bca2..336cd57e7 100644 --- a/src/Impostor.Server/Recorder/RecordedPacketType.cs +++ b/src/Impostor.Server/Recorder/RecordedPacketType.cs @@ -5,6 +5,6 @@ internal enum RecordedPacketType : byte Connect = 1, Disconnect = 2, Message = 3, - GameCreated = 4 + GameCreated = 4, } -} \ No newline at end of file +} diff --git a/src/Impostor.Server/Recorder/ServerReplayVersion.cs b/src/Impostor.Server/Recorder/ServerReplayVersion.cs new file mode 100644 index 000000000..ce77e3c7d --- /dev/null +++ b/src/Impostor.Server/Recorder/ServerReplayVersion.cs @@ -0,0 +1,18 @@ +namespace Impostor.Server.Recorder +{ + /// + /// Version of the server replay data format. + /// + public enum ServerReplayVersion + { + /// + /// Initial version. + /// + Initial = 1, + + /// + /// Latest version. + /// + Latest = Initial, + } +} diff --git a/src/Impostor.Server/Utils/DotnetUtils.cs b/src/Impostor.Server/Utils/DotnetUtils.cs index 48a0ac0a8..25a4c7cc9 100644 --- a/src/Impostor.Server/Utils/DotnetUtils.cs +++ b/src/Impostor.Server/Utils/DotnetUtils.cs @@ -2,19 +2,22 @@ namespace Impostor.Server.Utils { - internal static class DotnetUtils + public static class DotnetUtils { - private const string DefaultUnknownBuild = "UNKNOWN"; + private static string? _version; - public static string GetVersion() + public static string Version { - var attribute = typeof(DotnetUtils).Assembly.GetCustomAttribute(); - if (attribute != null) + get { - return attribute.InformationalVersion; - } + if (_version == null) + { + var attribute = typeof(DotnetUtils).Assembly.GetCustomAttribute(); + _version = attribute != null ? attribute.InformationalVersion : "UNKNOWN"; + } - return DefaultUnknownBuild; + return _version; + } } } } 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.Server/config.json b/src/Impostor.Server/config.json index d477c7452..1f68f968f 100644 --- a/src/Impostor.Server/config.json +++ b/src/Impostor.Server/config.json @@ -6,6 +6,7 @@ "ListenPort": 22023 }, "AntiCheat": { + "Enabled": true, "BanIpFromGame": true } -} \ No newline at end of file +} diff --git a/src/Impostor.Tests/Events/EventManagerTests.cs b/src/Impostor.Tests/Events/EventManagerTests.cs index d222d79b9..a510803a5 100644 --- a/src/Impostor.Tests/Events/EventManagerTests.cs +++ b/src/Impostor.Tests/Events/EventManagerTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Net.Sockets; using System.Threading.Tasks; using Impostor.Api.Events; using Impostor.Api.Events.Managers; @@ -11,10 +10,10 @@ namespace Impostor.Tests.Events { public class EventManagerTests { - public static readonly IEnumerable TestModes = new [] + public static readonly IEnumerable TestModes = new[] { new object[] { TestMode.Service }, - new object[] { TestMode.Temporary } + new object[] { TestMode.Temporary }, }; [Theory] @@ -38,14 +37,14 @@ public async Task CallPriority(TestMode mode) await eventManager.CallAsync(new SetValueEvent(1)); - Assert.Equal(new [] + Assert.Equal(new[] { EventPriority.Monitor, EventPriority.Highest, EventPriority.High, EventPriority.Normal, EventPriority.Low, - EventPriority.Lowest + EventPriority.Lowest, }, listener.Priorities); } @@ -78,10 +77,10 @@ public async Task CancelPriority(TestMode mode) await eventManager.CallAsync(new SetValueEvent(1)); - Assert.Equal(new [] + Assert.Equal(new[] { EventPriority.Monitor, - EventPriority.Highest + EventPriority.Highest, }, listener.Priorities); } @@ -115,7 +114,7 @@ private static IEventManager CreatEventManager(TestMode mode, params IEventListe public enum TestMode { Service, - Temporary + Temporary, } public interface ISetValueEvent : IEventCancelable diff --git a/src/Impostor.Tests/GameCodeTests.cs b/src/Impostor.Tests/GameCodeTests.cs index de9812351..6f0c23b4f 100644 --- a/src/Impostor.Tests/GameCodeTests.cs +++ b/src/Impostor.Tests/GameCodeTests.cs @@ -1,5 +1,4 @@ using Impostor.Api.Innersloth; - using Xunit; namespace Impostor.Tests @@ -26,4 +25,4 @@ public void CodeV2() Assert.Equal(codeInt, GameCodeParser.GameNameToInt(code)); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Tests/Hazel/MessageReaderTests.cs b/src/Impostor.Tests/Hazel/MessageReaderTests.cs index e7fa0dff8..c0ecbdd7b 100644 --- a/src/Impostor.Tests/Hazel/MessageReaderTests.cs +++ b/src/Impostor.Tests/Hazel/MessageReaderTests.cs @@ -72,7 +72,7 @@ public void ReadProperBool() public void ReadProperString() { const string Test1 = "Hello"; - string Test2 = new string(' ', 1024); + var Test2 = new string(' ', 1024); var msg = new MessageWriter(2048); msg.StartMessage(1); msg.Write(Test1); @@ -128,18 +128,18 @@ public void CopyMessage() var msg = new MessageWriter(2048); msg.StartMessage(1); - msg.StartMessage(2); - msg.Write(Test1); - msg.Write(Test2); - msg.StartMessage(2); - msg.Write(Test1); - msg.Write(Test2); - msg.StartMessage(2); - msg.Write(Test1); - msg.Write(Test2); - msg.EndMessage(); - msg.EndMessage(); - msg.EndMessage(); + msg.StartMessage(2); + msg.Write(Test1); + msg.Write(Test2); + msg.StartMessage(2); + msg.Write(Test1); + msg.Write(Test2); + msg.StartMessage(2); + msg.Write(Test1); + msg.Write(Test2); + msg.EndMessage(); + msg.EndMessage(); + msg.EndMessage(); msg.EndMessage(); // Read message. @@ -217,7 +217,7 @@ public void CopyToMessage() 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6F, 0x70, - 0x79, 0x69, 0x6E, 0x67, 0x2E + 0x79, 0x69, 0x6E, 0x67, 0x2E, }; var readerPool = CreateReaderPool(); @@ -301,15 +301,15 @@ public void RemoveMessage() var messageWriter = new MessageWriter(1024); messageWriter.StartMessage(0); - messageWriter.StartMessage(1); - messageWriter.Write("HiTest1"); - messageWriter.StartMessage(2); - messageWriter.Write("RemoveMe!"); - messageWriter.EndMessage(); - messageWriter.EndMessage(); - messageWriter.StartMessage(2); - messageWriter.Write("HiTest2"); - messageWriter.EndMessage(); + messageWriter.StartMessage(1); + messageWriter.Write("HiTest1"); + messageWriter.StartMessage(2); + messageWriter.Write("RemoveMe!"); + messageWriter.EndMessage(); + messageWriter.EndMessage(); + messageWriter.StartMessage(2); + messageWriter.Write("HiTest2"); + messageWriter.EndMessage(); messageWriter.EndMessage(); // Copy buffer. diff --git a/src/Impostor.Tests/Hazel/MessageWriterTests.cs b/src/Impostor.Tests/Hazel/MessageWriterTests.cs index 83d67da9d..f15819112 100644 --- a/src/Impostor.Tests/Hazel/MessageWriterTests.cs +++ b/src/Impostor.Tests/Hazel/MessageWriterTests.cs @@ -35,4 +35,4 @@ static void WriteSomeData(MessageWriter oldVer) } } } -} \ No newline at end of file +} diff --git a/src/Impostor.Tests/Impostor.Tests.csproj b/src/Impostor.Tests/Impostor.Tests.csproj index 3f96bf6b5..a4ccd6bf6 100644 --- a/src/Impostor.Tests/Impostor.Tests.csproj +++ b/src/Impostor.Tests/Impostor.Tests.csproj @@ -1,20 +1,23 @@ - - net5.0 - false - + + net5.0 + false + - - - - - - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + diff --git a/src/Impostor.Tools.Proxy/HexUtils.cs b/src/Impostor.Tools.Proxy/HexUtils.cs index 79517b4d2..f04e226b0 100644 --- a/src/Impostor.Tools.Proxy/HexUtils.cs +++ b/src/Impostor.Tools.Proxy/HexUtils.cs @@ -8,28 +8,28 @@ public static class HexUtils public static string HexDump(byte[] bytes, int bytesPerLine = 16) { if (bytes == null) return ""; - int bytesLength = bytes.Length; + var bytesLength = bytes.Length; - char[] HexChars = "0123456789ABCDEF".ToCharArray(); + var HexChars = "0123456789ABCDEF".ToCharArray(); - int firstHexColumn = - 8 // 8 characters for the address - + 3; // 3 spaces + var firstHexColumn = + 8 // 8 characters for the address + + 3; // 3 spaces - int firstCharColumn = firstHexColumn - + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space - + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th - + 2; // 2 spaces + var firstCharColumn = firstHexColumn + + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space + + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th + + 2; // 2 spaces - int lineLength = firstCharColumn - + bytesPerLine // - characters to show the ascii value - + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2) + var lineLength = firstCharColumn + + bytesPerLine // - characters to show the ascii value + + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2) - char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); - int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; - StringBuilder result = new StringBuilder(expectedLines * lineLength); + var line = (new string(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + var expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; + var result = new StringBuilder(expectedLines * lineLength); - for (int i = 0; i < bytesLength; i += bytesPerLine) + for (var i = 0; i < bytesLength; i += bytesPerLine) { line[0] = HexChars[(i >> 28) & 0xF]; line[1] = HexChars[(i >> 24) & 0xF]; @@ -40,10 +40,10 @@ public static string HexDump(byte[] bytes, int bytesPerLine = 16) line[6] = HexChars[(i >> 4) & 0xF]; line[7] = HexChars[(i >> 0) & 0xF]; - int hexColumn = firstHexColumn; - int charColumn = firstCharColumn; + var hexColumn = firstHexColumn; + var charColumn = firstCharColumn; - for (int j = 0; j < bytesPerLine; j++) + for (var j = 0; j < bytesPerLine; j++) { if (j > 0 && (j & 7) == 0) hexColumn++; if (i + j >= bytesLength) @@ -54,17 +54,20 @@ public static string HexDump(byte[] bytes, int bytesPerLine = 16) } else { - byte b = bytes[i + j]; + var b = bytes[i + j]; line[hexColumn] = HexChars[(b >> 4) & 0xF]; line[hexColumn + 1] = HexChars[b & 0xF]; line[charColumn] = (b < 32 ? '·' : (char)b); } + hexColumn += 3; charColumn++; } + result.Append(line); } + return result.ToString(); } } -} \ No newline at end of file +} diff --git a/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj b/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj index 8f523dc38..11360170f 100644 --- a/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj +++ b/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj @@ -1,17 +1,19 @@ - - Exe - net5.0 - + + Exe + net5.0 + - - - - + + + + NU1701 + + + + + + - - - - diff --git a/src/Impostor.Tools.Proxy/Program.cs b/src/Impostor.Tools.Proxy/Program.cs index 468cdd15e..c7f9cfac6 100644 --- a/src/Impostor.Tools.Proxy/Program.cs +++ b/src/Impostor.Tools.Proxy/Program.cs @@ -18,22 +18,22 @@ internal static class Program private static readonly Dictionary TagMap = new Dictionary { - {0, "HostGame"}, - {1, "JoinGame"}, - {2, "StartGame"}, - {3, "RemoveGame"}, - {4, "RemovePlayer"}, - {5, "GameData"}, - {6, "GameDataTo"}, - {7, "JoinedGame"}, - {8, "EndGame"}, - {9, "GetGameList"}, - {10, "AlterGame"}, - {11, "KickPlayer"}, - {12, "WaitForHost"}, - {13, "Redirect"}, - {14, "ReselectServer"}, - {16, "GetGameListV2"} + { 0, "HostGame" }, + { 1, "JoinGame" }, + { 2, "StartGame" }, + { 3, "RemoveGame" }, + { 4, "RemovePlayer" }, + { 5, "GameData" }, + { 6, "GameDataTo" }, + { 7, "JoinedGame" }, + { 8, "EndGame" }, + { 9, "GetGameList" }, + { 10, "AlterGame" }, + { 11, "KickPlayer" }, + { 12, "WaitForHost" }, + { 13, "Redirect" }, + { 14, "ReselectServer" }, + { 16, "GetGameListV2" }, }; private static IServiceProvider _serviceProvider; @@ -46,7 +46,7 @@ private static void Main(string[] args) _serviceProvider = services.BuildServiceProvider(); _readerPool = _serviceProvider.GetRequiredService>(); - + var devices = LivePacketDevice.AllLocalMachine; if (devices.Count == 0) { @@ -77,7 +77,7 @@ private static void PacketHandler(Packet packet) var ip = packet.Ethernet.IpV4; var ipSrc = ip.Source.ToString(); var udp = ip.Udp; - + // True if this is our own packet. using (var stream = udp.Payload.ToMemoryStream()) { @@ -86,14 +86,14 @@ private static void PacketHandler(Packet packet) reader.Update(stream.ToArray()); var option = reader.Buffer[0]; - if (option == (byte) MessageType.Reliable) + if (option == (byte)MessageType.Reliable) { reader.Seek(reader.Position + 3); } - else if (option == (byte) UdpSendOption.Acknowledgement || - option == (byte) UdpSendOption.Ping || - option == (byte) UdpSendOption.Hello || - option == (byte) UdpSendOption.Disconnect) + else if (option == (byte)UdpSendOption.Acknowledgement || + option == (byte)UdpSendOption.Ping || + option == (byte)UdpSendOption.Hello || + option == (byte)UdpSendOption.Disconnect) { return; } @@ -101,9 +101,9 @@ private static void PacketHandler(Packet packet) { reader.Seek(reader.Position + 1); } - + var isSent = ipSrc.StartsWith("192."); - + while (true) { if (reader.Position >= reader.Length) @@ -120,7 +120,7 @@ private static void PacketHandler(Packet packet) { HandleToClient(ipSrc, message); } - + if (message.Position < message.Length) { Console.ForegroundColor = ConsoleColor.Red; @@ -160,6 +160,7 @@ private static void HandleToClient(string source, IMessageReader packet) { Console.WriteLine("- PlayerId " + packet.ReadPackedInt32()); } + break; case 10: Console.WriteLine("- GameCode " + packet.ReadInt32()); diff --git a/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs b/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs new file mode 100644 index 000000000..7bd4beb01 --- /dev/null +++ b/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs @@ -0,0 +1,10 @@ +using System; +using Impostor.Api; + +namespace Impostor.Tools.ServerReplay +{ + public class FakeDateTimeProvider : IDateTimeProvider + { + public DateTimeOffset UtcNow { get; set; } + } +} diff --git a/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj b/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj index 98fa689f8..3b46f3f87 100644 --- a/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj +++ b/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj @@ -6,11 +6,11 @@ - + - + diff --git a/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs b/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs index 1111b7e71..7dc9196fe 100644 --- a/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs +++ b/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs @@ -11,4 +11,4 @@ public GameCode Create() return Result; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs b/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs index 43f0257b6..c611c2949 100644 --- a/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs +++ b/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs @@ -16,16 +16,16 @@ public MockHazelConnection(IPEndPoint endPoint) public IPEndPoint EndPoint { get; } public bool IsConnected { get; } - public IClient? Client { get; set; } + public IClient Client { get; set; } public ValueTask SendAsync(IMessageWriter writer) { return ValueTask.CompletedTask; } - public ValueTask DisconnectAsync(string reason) + public ValueTask DisconnectAsync(string reason, IMessageWriter writer = null) { return ValueTask.CompletedTask; } } -} \ No newline at end of file +} diff --git a/src/Impostor.Tools.ServerReplay/Program.cs b/src/Impostor.Tools.ServerReplay/Program.cs index 5aaa954cd..16b756eaf 100644 --- a/src/Impostor.Tools.ServerReplay/Program.cs +++ b/src/Impostor.Tools.ServerReplay/Program.cs @@ -4,6 +4,7 @@ using System.IO; using System.Net; using System.Threading.Tasks; +using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Games.Managers; @@ -13,12 +14,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; @@ -40,6 +43,7 @@ internal static class Program private static MockGameCodeFactory _gameCodeFactory; private static ClientManager _clientManager; private static GameManager _gameManager; + private static FakeDateTimeProvider _fakeDateTimeProvider; private static async Task Main(string[] args) { @@ -51,7 +55,7 @@ private static async Task Main(string[] args) var stopwatch = Stopwatch.StartNew(); - foreach (var file in Directory.GetFiles(args[0])) + foreach (var file in Directory.GetFiles(args[0], "*.dat")) { // Clear. Connections.Clear(); @@ -65,6 +69,7 @@ private static async Task Main(string[] args) _gameCodeFactory = _serviceProvider.GetRequiredService(); _clientManager = _serviceProvider.GetRequiredService(); _gameManager = _serviceProvider.GetRequiredService(); + _fakeDateTimeProvider = _serviceProvider.GetRequiredService(); await using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var reader = new BinaryReader(stream)) @@ -82,6 +87,14 @@ private static ServiceProvider BuildServices() { var services = new ServiceCollection(); + services.AddSingleton(new ServerEnvironment + { + IsReplay = true, + }); + + services.AddSingleton(); + services.AddSingleton(p => p.GetRequiredService()); + services.AddLogging(builder => { builder.ClearProviders(); @@ -99,6 +112,7 @@ private static ServiceProvider BuildServices() services.AddSingleton(); services.AddSingleton(); + services.AddEventPools(); services.AddHazel(); return services.BuildServiceProvider(); @@ -106,6 +120,17 @@ private static ServiceProvider BuildServices() private static async Task ParseSession(BinaryReader reader) { + var protocolVersion = (ServerReplayVersion)reader.ReadUInt32(); + if (protocolVersion < ServerReplayVersion.Initial || protocolVersion > ServerReplayVersion.Latest) + { + throw new NotSupportedException("Session's protocol version is unsupported"); + } + + var startTime = _fakeDateTimeProvider.UtcNow = DateTimeOffset.FromUnixTimeMilliseconds(reader.ReadInt64()); + var serverVersion = reader.ReadString(); + + Logger.Information("Loaded session (server: {ServerVersion}, recorded at {StartTime})", serverVersion, startTime); + while (reader.BaseStream.Position < reader.BaseStream.Length) { var dataLength = reader.ReadInt32(); @@ -114,6 +139,7 @@ private static async Task ParseSession(BinaryReader reader) await using (var stream = new MemoryStream(data)) using (var readerInner = new BinaryReader(stream)) { + _fakeDateTimeProvider.UtcNow = startTime + TimeSpan.FromMilliseconds(readerInner.ReadUInt32()); await ParsePacket(readerInner); } } @@ -121,7 +147,7 @@ private static async Task ParseSession(BinaryReader reader) private static async Task ParsePacket(BinaryReader reader) { - var dataType = (RecordedPacketType) reader.ReadByte(); + var dataType = (RecordedPacketType)reader.ReadByte(); // Read client id. var clientId = reader.ReadInt32(); @@ -135,11 +161,12 @@ private static async Task ParsePacket(BinaryReader reader) var addressPort = reader.ReadUInt16(); var address = new IPEndPoint(new IPAddress(addressBytes), addressPort); var name = reader.ReadString(); + var gameVersion = reader.ReadInt32(); // Create and register connection. var connection = new MockHazelConnection(address); - await _clientManager.RegisterConnectionAsync(connection, name, 50516550); + await _clientManager.RegisterConnectionAsync(connection, name, gameVersion); // Store reference for ourselfs. Connections.Add(clientId, connection); @@ -169,7 +196,7 @@ private static async Task ParsePacket(BinaryReader reader) if (tag == MessageFlags.HostGame) { - GameOptions.Add(clientId, Message00HostGameC2S.Deserialize(message)); + GameOptions.Add(clientId, Message00HostGameC2S.Deserialize(message, out _)); } else if (Connections.TryGetValue(clientId, out var client)) { diff --git a/src/Impostor.Tools.ServerReplay/sessions/.gitkeep b/src/Impostor.Tools.ServerReplay/sessions/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat b/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat deleted file mode 100644 index e5f441bb7..000000000 Binary files a/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat and /dev/null differ diff --git a/src/Impostor.sln b/src/Impostor.sln index b9706de66..976ce4836 100644 --- a/src/Impostor.sln +++ b/src/Impostor.sln @@ -25,7 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{36AA EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Debugger", "Impostor.Plugins.Debugger\Impostor.Plugins.Debugger.csproj", "{ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Hazel", "Impostor.Hazel\Impostor.Hazel.csproj", "{671B753B-31AE-4C36-AD71-09CF00FA17CA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Hazel", "Impostor.Hazel\Hazel\Hazel.csproj", "{671B753B-31AE-4C36-AD71-09CF00FA17CA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{9F1919B0-915B-4749-9944-697DF7E7F67F}" EndProject diff --git a/src/ProjectRules.ruleset b/src/ProjectRules.ruleset new file mode 100644 index 000000000..5484352e8 --- /dev/null +++ b/src/ProjectRules.ruleset @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/stylecop.json b/src/stylecop.json new file mode 100644 index 000000000..216b35a2a --- /dev/null +++ b/src/stylecop.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "preserve" + }, + "layoutRules": { + "newlineAtEndOfFile": "require" + } + } +}