Compare commits

...
65 Commits
Author SHA1 Message Date
js6pak d1ffdb95e1 Update appveyor.yml
crappveyor
2022-06-11 15:41:48 +00:00
miniduikboot 4d11cf91ad Remove Auth- and AnnouncementsServer sections (#484) 2022-06-03 18:58:00 +00:00
miniduikboot 84aba52198 Remove 75% of the question marks of PR #481 (#482) 2022-06-03 18:57:47 +00:00
js6pakandminiduikboot 6e4bb71289 Add Serilog.Settings.Configuration (#468)
* Add Serilog.Settings.Configuration

* Add docs section for logging

* Elaborate Serilog documentation

Co-authored-by: miniduikboot <mini@duikbo.at>
2022-06-03 20:31:52 +02:00
js6pak 8fec1a211e Merge pull request #483 from js6pak/fix-disconnect-reason
Fix non custom disconnect reasons
2022-06-03 20:31:10 +02:00
js6pak 85a2fce6c1 Fix non custom disconnect reasons 2022-06-03 20:17:59 +02:00
miniduikboot 224fdd1d5b Allow adding custom tasks on standard maps (#481)
This is needed for mods that add tasks to standard maps, but also mods
that add custom maps: because their custom netobjects don't get
registered Impostor thinks people are still on the old map when people
switch to the custom map after playing a game.
2022-05-25 19:53:32 +02:00
miniduikboot 380ba46f11 Upgrade to .NET 6 (#479)
* Upgrade to .NET 6

.NET 5 has gone EOL, so move to a supported version.

* Upgrade Docker container as well
* Update docs for .NET 6
* Re-add hack for System.IO.Pipelines
* Bump dependency versions
* Update the deps of the other projects too
* Only declare TargetFramework once
* Dockerfile: also copy over Directory.Build.props
* Cake: pick up target framework automatically
* Upgrade Cake as well
2022-05-25 19:52:22 +02:00
miniduikboot aeefc40ef1 Merge pull request #474 from SubmergedAmongUs/master
Add handling for null ShipStatus
2022-04-20 18:49:37 +02:00
AlexejheroYTB 743b8ed5cb Remove null check for ShipStatus known to not be null 2022-04-20 08:07:59 +03:00
AlexejheroYTB ed008872f6 Add handling for null ShipStatus 2022-04-16 17:11:58 +03:00
js6pak 4797d899af Merge pull request #471 from Impostor/feature/2022.3.29 2022-04-09 12:42:51 +00:00
js6pak c834013bd3 Simplify Message07JoinedGameS2CPre20220202 2022-04-07 16:58:33 +02:00
js6pak 70c180fb19 Make GameOptionsData de/serialization less confusing 2022-04-07 11:58:16 +02:00
miniduikboot 729368d777 Do not add ProductUserId/Friendcode on <2.24
Because it is fine to add data to the end of messages the new fields
could be added to Message 1 just fine, but because Message 7 contains a
loop, we need to send a different message to old clients there.
2022-04-05 20:05:06 +02:00
miniduikboot 5c06d1dd4d fixup! Add empty ProductUserId and FriendCode to packets 2022-04-05 19:52:25 +02:00
miniduikboot 446e5b8705 Add empty ProductUserId and FriendCode to packets
They are not sent to the server and no clue how to retrieve them out of band.
2022-04-05 19:52:25 +02:00
miniduikboot ae00ff9295 Barebones port 2022-04-05 19:52:25 +02:00
miniduikboot 400592b99c Merge pull request #467 from miniduikboot/fix/race-dc-send-all-except
Do not broadcast messages to disconnected clients
2022-03-26 10:35:54 +01:00
miniduikboot 9906c409da Do not broadcast messages to disconnected clients
This method is used when players have disconnected. If multiple players
disconnect at once, this method leads to exceptions.
2022-03-17 21:35:08 +01:00
miniduikboot c26d03a8bc Merge pull request #464 from miniduikboot/fix/custom-dc-msg
Fix/custom dc msg
2022-02-28 18:04:47 +01:00
miniduikboot ca48d672d5 Reword obsoletion message 2022-02-26 20:33:02 +01:00
miniduikboot 0b431dc5fa Change DisconnectAsync to use extension instead 2022-02-26 20:31:45 +01:00
miniduikboot 0b0c8274aa Turn DisconnectConnection into an extension 2022-02-26 20:31:23 +01:00
miniduikboot 36f8255e26 Fixup 608414326d
Oops, I accidentally forgot to remove the previous line when adding the
file to my commit
2022-02-26 19:08:57 +01:00
miniduikboot 919b094018 Deprecate JoinGame errors
Starting from version 2021.11.9, join errors no longer exist, instead a
client will show the message "An unknown error disconnected you from the
server". As a workaround, the connection is disconnected entirely when
an error occurs, which allows us to show a custom message.
2022-02-26 19:08:57 +01:00
js6pak 6125fd7006 Merge pull request #465 from miniduikboot/fix/2-28-bump 2022-02-26 17:26:54 +01:00
miniduikboot a3c0c8cacf Bump network version 2022-02-25 07:05:22 +01:00
miniduikboot b252bf2ee7 Merge pull request #460 from miniduikboot/feature/queryplatformids
Add QueryPlatformIds
2022-02-14 22:12:47 +01:00
miniduikboot 9b5a63e030 Add QueryPlatformIds
Some platforms (notable the Microsoft Store version) ask for this before connecting.
2022-02-02 22:17:47 +01:00
miniduikboot 6a8f089576 Merge pull request #455 from miniduikboot/fix/i18n-nicknames
Fix/i18n nicknames
2022-01-27 19:23:30 +01:00
miniduikboot d994323083 Merge pull request #458 from miniduikboot/fix/ac-engineer-snapto
AC: Allow engineers to move to a different vent.
2022-01-27 19:22:59 +01:00
miniduikboot 3f374da44b Partial revert of cc8d5d2: Readd length check
Oversized nicknames can cause bigger issues, so it is desireable to
still block them. .Length counts codepoints, not bytes, which means
that 10 character Japanese nicknames, which are configurable ingame,
should still be allowed.
2022-01-27 18:24:02 +01:00
miniduikboot bfd2efa716 AC: Allow engineers to move to a different vent. 2022-01-27 18:10:46 +01:00
miniduikboot 6867dec7ee Merge pull request #453 from miniduikboot/fix/ga-half-cooldown
AC CheckMurder: Require half of cooldown
2022-01-14 23:06:28 +01:00
miniduikboot 7132dc2d38 Add comment to clarify why this exists 2022-01-14 19:47:58 +01:00
miniduikboot 682bfac558 Merge pull request #452 from miniduikboot/fix/host-migrate
Before this commit, when the host of a game disconnects, the InnerPlayerInfo from the player is removed, which causes the new host to disconnect because of a deserialization error. This PR fixes this issue
2022-01-14 18:21:16 +01:00
miniduikboot cc8d5d2a45 Remove name length/validity checks from JoinGame
Length and validity are checked also checked by the anticheat. In case
Impostor goes out of sync with the allowed characters again, this can
just be fixed by disabling the Anticheat.

Check for NullOrWhiteSpace is kept because an empty username could be annoying
2022-01-10 20:54:45 +01:00
miniduikboot e6a35344c3 Sync IsCharAllowed with Innersloth implementation 2022-01-10 20:54:45 +01:00
miniduikboot db52b48cd6 AC CheckMurder: Require half of cooldown
When the Guardian Angel protects a player, the impostor that just missed
their kill gets back half of their cooldown. Keeping track of whether or
not the player was protected is more difficult due to how the Guardian
Angel is implemented, so instead we halve the minimum time between kill
in order to minimize false positives.
2022-01-08 19:20:11 +01:00
miniduikboot 50a526a02d Clamp taskCount to the length of the task list
New tasks cannot be created at this point because we don't know the
underlying tasks. So we try to maintain the tasks we do know of.
2022-01-08 17:42:05 +01:00
miniduikboot 608414326d Do not destroy PlayerControl if game is starting
This player may still get assigned tasks, which could lead to
deserialization problems.
2022-01-08 17:41:52 +01:00
miniduikboot d8bcb52c90 Mark game as started when RPC SetRole is called
This used to be done when SetInfected was called, but this RPC is no
longer used since 2021.11.9 in favor of this RPC.
2022-01-08 17:41:14 +01:00
miniduikboot d7d7547182 Merge pull request #442 from Impostor/2021.11.9
2021.11.9 and 2021.12.14 support
2021-12-21 23:27:38 +01:00
js6pak 64e27c3e21 Set RoleType in PlayerInfo.Deserialize 2021-12-15 15:36:01 +01:00
js6pak a0b433500c Don't do public methods in interfaces 2021-12-15 15:35:46 +01:00
js6pak c51a1f9c1f Readd HandleSet{Pet/Hat/Skin} 2021-12-15 15:33:01 +01:00
js6pak e15ac51dcf Implement cosmetic rpc methods 2021-12-15 15:22:18 +01:00
js6pak bde3a22f6e Document IClient.PlatformSpecificData 2021-12-15 14:52:22 +01:00
js6pak 6118e6b7b8 Change RoleOptionsData floats to be bytes as they can't ever be floats anyway 2021-12-15 14:51:07 +01:00
miniduikboot 32ecfedfba Add initial support for 2021.12.14 2021-12-14 21:38:26 +01:00
miniduikboot b6fe79cc8b Add RPC's for CheckProtect and ProtectPlayer 2021-11-22 23:04:56 +01:00
miniduikboot aeaffc849a Implement new MurderPlayer logic
In 2021.11.9, the host now uses MurderPlayer instead of impostors.
Impostor now use CheckMurder to request the host to kill a player. This
allows the host to prevent the target of the Guardian Angel from dying.

However, if the impostor that is checking the kill is also the host, AU
internally processes the CheckMurder RPC and sends the MurderPlayer RPC
first, *then* it sends the CheckMurder RPC.
2021-11-22 22:12:34 +01:00
miniduikboot a15f1b6a4c Add platform data to GameList 2021-11-15 22:55:57 +01:00
js6pak afb7051218 Fix ServerReplay 2021-11-13 19:51:52 +01:00
miniduikboot 8a0e9e876e Bump to 1.7 2021-11-12 23:12:47 +01:00
miniduikboot 22f1d74cdf Allow Shapeshifters to use RPC Shapeshift 2021-11-12 23:12:47 +01:00
miniduikboot e6d38f4b72 Allow Engineers to vent 2021-11-12 22:57:29 +01:00
miniduikboot 93c80c1412 Implement Rpc 44: SetRole 2021-11-12 22:56:35 +01:00
miniduikboot c3e0157209 Add name/psd/level to JoinGame- and JoinedGameS2C
Two players can now connect to the same lobby
2021-11-12 21:11:48 +01:00
miniduikboot 0769752407 Remove initial state flag from InnerGameData 2021-11-10 23:04:21 +01:00
miniduikboot fba1a894cc Implement SetActivePodType 2021-11-10 21:23:00 +01:00
js6pak 4d1aec9070 WIP 2021-11-10 20:46:56 +01:00
js6pak e0c7086973 RIP AnnouncementsServer 2021-11-10 13:52:15 +01:00
js6pak 125da11d61 [skip ci] Auto deploy github releases 2021-08-05 14:24:51 +02:00
102 changed files with 1200 additions and 1050 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"cake.tool": {
"version": "1.1.0",
"version": "2.2.0",
"commands": [
"dotnet-cake"
]
+4 -3
View File
@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:5.0 AS build
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:6.0 AS build
# See for all possible platforms
# https://github.com/containerd/containerd/blob/master/platforms/platforms.go#L17
@@ -12,6 +12,7 @@ WORKDIR /source
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/Hazel/Hazel.csproj ./src/Impostor.Hazel/Hazel/Hazel.csproj
COPY src/Directory.Build.props ./src/Directory.Build.props
RUN case "$TARGETARCH" in \
amd64) NETCORE_PLATFORM='linux-x64';; \
@@ -35,8 +36,8 @@ RUN case "$TARGETARCH" in \
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
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/runtime:6.0
WORKDIR /app
COPY --from=build /app ./
EXPOSE 22023/udp
ENTRYPOINT ["./Impostor.Server"]
ENTRYPOINT ["./Impostor.Server"]
+22 -10
View File
@@ -20,11 +20,12 @@ image: Ubuntu2004
install:
- git submodule update --init --recursive
- ps: dotnet tool restore
- dotnet tool restore
build_script:
- ps: dotnet cake build.cake --bootstrap
- ps: dotnet cake build.cake --pack
- pwsh: if ($env:APPVEYOR_REPO_TAG_NAME) { Update-AppveyorBuild -Version "$($env:APPVEYOR_REPO_TAG_NAME -replace "v")" }
- dotnet cake build.cake --bootstrap
- dotnet cake build.cake --pack
test: off
@@ -34,13 +35,24 @@ artifacts:
- path: ./build/*.nupkg
- path: ./build/*.snupkg
# deploy:
# - provider: NuGet
# artifact: /.*(\.|\.s)nupkg/
# on:
# branch: dev
# api_key:
# secure: 4OHXl+m1KVi60hB1j54MpdP8tVv0UNfkdF4rVP+2AhAjN4a2MVT+Bl90gJZ96xHF
deploy:
# - provider: NuGet
# artifact: /.*(\.|\.s)nupkg/
# on:
# branch: master
# api_key:
# secure: 4OHXl+m1KVi60hB1j54MpdP8tVv0UNfkdF4rVP+2AhAjN4a2MVT+Bl90gJZ96xHF
- provider: GitHub
tag: $(APPVEYOR_REPO_TAG_NAME)
release: $(APPVEYOR_PROJECT_NAME) $(APPVEYOR_BUILD_VERSION)
description: 'DRAFT'
auth_token:
secure: vyQ+zijv9g99wEgIdSC8uPSlnFvH3KZiD0yE7mNA3r3z2OY4lTEF6bpuUvtjoaM3
artifact: /.*/
draft: true
on:
APPVEYOR_REPO_TAG: true
only_commits:
files:
+13 -15
View File
@@ -1,6 +1,6 @@
#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"
#addin "nuget:?package=SharpZipLib&Version=1.3.3"
#addin "nuget:?package=Cake.Compression&Version=0.3.0"
#addin "nuget:?package=Cake.FileHelpers&Version=5.0.0"
var buildId = EnvironmentVariable("GITHUB_RUN_NUMBER") ?? EnvironmentVariable("APPVEYOR_BUILD_VERSION");
var buildRelease = EnvironmentVariable("APPVEYOR_REPO_TAG") == "true";
@@ -10,7 +10,7 @@ var buildDir = MakeAbsolute(Directory("./build"));
var target = Argument("target", "Deploy");
var configuration = Argument("configuration", "Release");
var msbuildSettings = new DotNetCoreMSBuildSettings();
var msbuildSettings = new DotNetMSBuildSettings();
if (buildRelease)
{
@@ -31,10 +31,9 @@ private void ImpostorPublish(string name, string project, string runtime, bool i
var projBuildDir = buildDir.Combine(name + "_" + runtime);
var projBuildName = name + "_" + buildVersion + "_" + runtime;
DotNetCorePublish(project, new DotNetCorePublishSettings {
DotNetPublish(project, new DotNetPublishSettings {
Configuration = configuration,
NoRestore = true,
Framework = "net5.0",
Runtime = runtime,
SelfContained = false,
PublishSingleFile = true,
@@ -64,7 +63,7 @@ private void ImpostorPublishNF(string name, string project) {
var projBuildDir = buildDir.Combine(name + "_" + runtime);
var projBuildZip = buildDir.CombineWithFilePath(name + "_" + buildVersion + "_" + runtime + ".zip");
DotNetCorePublish(project, new DotNetCorePublishSettings {
DotNetPublish(project, new DotNetPublishSettings {
Configuration = configuration,
NoRestore = true,
Framework = "net472",
@@ -90,18 +89,17 @@ Task("Clean")
Task("Restore")
.Does(() => {
DotNetCoreRestore("./src/Impostor.sln");
DotNetRestore("./src/Impostor.sln");
});
Task("Replay")
.Does(() => {
// D:\Projects\GitHub\Impostor\Impostor\src\Impostor.Tools.ServerReplay\sessions
DotNetCoreRun(
DotNetRun(
"./src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj",
"./src/Impostor.Tools.ServerReplay/sessions", new DotNetCoreRunSettings {
"./src/Impostor.Tools.ServerReplay/sessions", new DotNetRunSettings {
Configuration = configuration,
NoRestore = true,
Framework = "net5.0"
});
});
@@ -111,7 +109,7 @@ Task("Build")
.IsDependentOn("Replay")
.Does(() => {
// Tests.
DotNetCoreBuild("./src/Impostor.Tests/Impostor.Tests.csproj", new DotNetCoreBuildSettings {
DotNetBuild("./src/Impostor.Tests/Impostor.Tests.csproj", new DotNetBuildSettings {
Configuration = configuration,
});
@@ -123,7 +121,7 @@ Task("Build")
ImpostorPublish("Impostor-Server", "./src/Impostor.Server/Impostor.Server.csproj", "linux-arm64", true);
// API.
DotNetCorePack("./src/Impostor.Api/Impostor.Api.csproj", new DotNetCorePackSettings {
DotNetPack("./src/Impostor.Api/Impostor.Api.csproj", new DotNetPackSettings {
Configuration = configuration,
OutputDirectory = buildDir,
IncludeSource = true,
@@ -135,7 +133,7 @@ Task("Build")
Task("Test")
.IsDependentOn("Build")
.Does(() => {
DotNetCoreTest("./src/Impostor.Tests/Impostor.Tests.csproj", new DotNetCoreTestSettings {
DotNetTest("./src/Impostor.Tests/Impostor.Tests.csproj", new DotNetTestSettings {
Configuration = configuration,
NoBuild = true
});
@@ -151,4 +149,4 @@ Task("Deploy")
// EXECUTION
//////////////////////////////////////////////////////////////////////
RunTarget(target);
RunTarget(target);
+2 -2
View File
@@ -1,6 +1,6 @@
# Building from source
The solution contains the Impostor server and its dependencies, like Hazel and the plugin API. The server is built using [.NET 5](https://dotnet.microsoft.com/download/dotnet/5.0).
The solution contains the Impostor server and its dependencies, like Hazel and the plugin API. The server is built using [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0).
## Cloning Impostor
@@ -14,7 +14,7 @@ git clone https://github.com/Impostor/Impostor.git
### Dependencies
- [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0)
- [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
- [Rider](https://www.jetbrains.com/rider/) or [Visual Studio](https://visualstudio.microsoft.com/vs/) (Optional, only if you want the full IDE experience)
### Build using the CLI
+2 -2
View File
@@ -6,11 +6,11 @@ There are currently two modes to run the Impostor server. The first way, Single
### Without Docker
1. Install the [.NET 5.0 runtime](https://dotnet.microsoft.com/download). Installing the SDK also works, but is not necessary unless you plan on developing Impostor or Impostor plugins. If you're asked to pick between a console, desktop or server runtime, the console runtime is enough.
1. Install the [.NET 6.0 runtime](https://dotnet.microsoft.com/download/6.0). Installing the SDK also works, but is not necessary unless you plan on developing Impostor or Impostor plugins. If you're asked to pick between a console, desktop or server runtime, the console runtime is enough.
2. Find the [latest release](https://github.com/Impostor/Impostor/releases) or the [latest CI build](https://ci.appveyor.com/project/Impostor/Impostor/branch/master/artifacts).
3. Download the version for your OS (linux/win/osx). Impostor is built for multiple CPU-architectures, you most likely want the x64 version, unless you are running on a Raspberry Pi or another device with an ARM processor.
4. Extract the zip.
5. Modify `config.json` to your liking. Documentation can be found [here](Server-configuration.md) *(this step is mandatory if you want to expose this server to other devices)*
5. Modify `config.json` to your liking. Documentation can be found [here](Server-configuration.md) _(this step is mandatory if you want to expose this server to other devices)_
6. Run `Impostor.Server` (Linux/macOS) or `Impostor.Server.exe` (Windows)
### Using Docker
+36 -25
View File
@@ -22,30 +22,6 @@ Impostor has an Anticheat that makes it possible to kick cheaters from games aut
| **Enabled** | `true` | Whether the anticheat should be enabled. |
| **BanIpFromGame** | `true` | When anticheat is enabled and a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. |
### AnnouncementsServer
The Announcement Server is used to show update announcements to clients. Using the Plugin API you can create a plugin that serves custom announcements.
| Key | Default | Value |
| -------------- | --------- | --------------------------------------------------- |
| **Enabled** | `false` | Whether the announcements server should be enabled. |
| **ListenIp** | `0.0.0.0` | See Required Server Configuration. |
| **ListenPort** | `22025` | See Required Server Configuration. |
### AuthServer
The Authentication Server is used since Among Us version 2021.3.31 and is used by clients to identify themselves. If you're playing with modded Among Us clients, you can enable this server to skip the 5 second wait to join.
| Key | Default | Value |
| --------------- | ---------------------- | ---------------------------------------------------- |
| **Enabled** | `false` | Whether the authentication server should be enabled. |
| **ListenIp** | `0.0.0.0` | See Required Server Configuration. |
| **ListenPort** | `22025` | See Required Server Configuration. |
| **Certificate** | `dtls/certificate.pem` | The public certificate used to authenticate with. |
| **PrivateKey** | `dtls/key.pem` | The corresponding private key. |
On Linux you can generate a certificate by running `openssl req -x509 -newkey rsa:2048 -keyout dtls/key.pem -out dtls/certificate.pem -days 3650 -nodes`. It does not need to be signed.
### Debug
The Debug configuration is used to enable the game recorder. This is mostly useful when developing Impostor.
@@ -53,7 +29,7 @@ The Debug configuration is used to enable the game recorder. This is mostly usef
| Key | Default | Value |
| ----------------------- | ------- | -------------------------------------------- |
| **GameRecorderEnabled** | `false` | Enables the Game Recorder. |
| **GameRecorderPath** | *empty* | Path where the recorded games will be saved. |
| **GameRecorderPath** | _empty_ | Path where the recorded games will be saved. |
### ServerRedirector
@@ -68,6 +44,41 @@ In a multi-node setup these values need to be specified. Note that most people d
| **>UdpMasterEndpoint** | | On the master, this value acts as a listen ip and port. On a node, this should be the public ip and port of the master. Format `127.0.0.1:32320`. |
| **Nodes** | | An array containing public ips and ports of nodes. Only needs to be set on the master. See above for an example. |
### Serilog (Logging)
Impostor's log framework, Serilog, can be configured in the config file. You can change its default log level and you can add additional sinks.
| Key | Default | Value |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **MinimumLevel** | `Information` | Minimum log level for a message to be printed. If a log entry is as severe or more severe than this level, it will be printed. From most severe to least severe: `Fatal`,`Error`, `Warning`, `Information`, `Debug`, `Verbose` |
| **Using** | `[]` | List of additional Serilog Sinks assemblies to load. |
| **WriteTo** | `[]` | Additional logging sinks. See the Serilog documentation or the example in this section. Serilog |
For more information, check the [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) documentation.
For example, to add logging to a file, you should add the following snippet to your configuration:
```json
"Serilog": {
"Using": [
"Serilog.Sinks.File"
],
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "logs/log.txt",
"rollingInterval": "Day"
}
}
]
}
```
Next to that, you also need to copy over Serilog.Sinks.File.dll from [NuGet](https://www.nuget.org/packages/Serilog.Sinks.File/). See the [Serilog.Sinks.File documentation](https://github.com/serilog/serilog-sinks-file#json-appsettingsjson-configuration) for a list of parameters that can be configured.
Other Serilog sinks are also supported, but are out of scope for this documentation.
## Config providers
### File
+6 -5
View File
@@ -19,23 +19,24 @@ Impostor has support for plugins. This document will help you to setup a develop
- [Impostor versions](#impostor-versions)
- [9. Missing/invalid data or want more functions?](#9-missinginvalid-data-or-want-more-functions)
## 1. Install .NET Core SDK
## 1. Install .NET SDK
Download and install the latest .NET Core SDK.
Download and install the latest .NET SDK.
https://dotnet.microsoft.com/download
## 2. Create a C# project
The first step is creating a new C# project, it must be a **Class Library (.NET Standard)**. The target framework can be any of those compatible with .NET 5, which includes:
The first step is creating a new C# project, it must be a **Class Library (.NET Standard)**. The target framework can be any of those compatible with .NET 6, which includes:
- .NET Standard 2.0
- .NET Standard 2.1
- .NET 5
- .NET 6
For more information about compatibility, see https://docs.microsoft.com/en-us/dotnet/standard/net-standard.
> At the moment of writing this document, I recommend you to use **.NET Standard 2.1**. This should give you enough functionality. If not, upgrade to .NET 5.
> At the moment of writing this document, I recommend you to use **.NET Standard 2.1**. This should give you enough functionality. If not, upgrade to .NET 6.
When the project has been created, you should have `Class.cs` and `Project.csproj` files. Your `Project.csproj` should look something like this.
@@ -336,4 +337,4 @@ Create an issue:
- [Suggest a function](https://github.com/Impostor/Impostor/issues/new?template=3--api-suggestion.md)
- [Data is invalid](https://github.com/Impostor/Impostor/issues/new?template=4--api-invalid.md)
- [Data is unavailable](https://github.com/Impostor/Impostor/issues/new?template=5--api-missing.md)
- [Other](https://github.com/Impostor/Impostor/issues/new?template=6--api-other.md)
- [Other](https://github.com/Impostor/Impostor/issues/new?template=6--api-other.md)
+2 -1
View File
@@ -1,6 +1,7 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.6.0</VersionPrefix>
<TargetFramework>net6.0</TargetFramework>
<VersionPrefix>1.7.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<EnforceCodeStyleInBuild Condition="$(MSBuildProjectName)!='Hazel'">true</EnforceCodeStyleInBuild>
</PropertyGroup>
@@ -1,38 +0,0 @@
using Impostor.Api.Innersloth;
namespace Impostor.Api.Events.Announcements
{
/// <summary>
/// Event fired after client requests a announcement.
/// </summary>
public interface IAnnouncementRequestEvent : IEvent
{
public interface IResponse
{
/// <summary>
/// Gets or sets a value indicating whether announcement should be loaded from client's cache, can save some bytes.
/// </summary>
public bool UseCached { get; set; }
/// <summary>
/// Gets or sets announcement, should be null when <see cref="UseCached" /> is set to true.
/// </summary>
public Announcement? Announcement { get; set; }
}
/// <summary>
/// Gets client's last announcement id.
/// </summary>
public int Id { get; }
/// <summary>
/// Gets client's language.
/// </summary>
public Language Language { get; }
/// <summary>
/// Gets or sets plugin made response.
/// </summary>
public IResponse Response { get; set; }
}
}
@@ -16,6 +16,13 @@ namespace Impostor.Api
return original[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ReadInt16(this ref ReadOnlySpan<byte> input)
{
var original = Advance<short>(ref input);
return BinaryPrimitives.ReadInt16LittleEndian(original);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt32(this ref ReadOnlySpan<byte> input)
{
+2 -2
View File
@@ -27,8 +27,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Nullable" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -1,14 +0,0 @@
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;
}
}
}
@@ -1,121 +0,0 @@
namespace Impostor.Api.Innersloth.Customization
{
public enum HatType : uint
{
NoHat = 0,
Astronaut = 1,
BaseballCap = 2,
BrainSlug = 3,
BushHat = 4,
CaptainsHat = 5,
DoubleTopHat = 6,
Flowerpot = 7,
Goggles = 8,
HardHat = 9,
Military = 10,
PaperHat = 11,
PartyHat = 12,
Police = 13,
Stethescope = 14,
TopHat = 15,
TowelWizard = 16,
Ushanka = 17,
Viking = 18,
WallCap = 19,
Snowman = 20,
Reindeer = 21,
Lights = 22,
Santa = 23,
Tree = 24,
Present = 25,
Candycanes = 26,
ElfHat = 27,
NewYears2018 = 28,
WhiteHat = 29,
Crown = 30,
Eyebrows = 31,
HaloHat = 32,
HeroCap = 33,
PipCap = 34,
PlungerHat = 35,
ScubaHat = 36,
StickminHat = 37,
StrawHat = 38,
TenGallonHat = 39,
ThirdEyeHat = 40,
ToiletPaperHat = 41,
Toppat = 42,
BlackFedora = 43,
SkiGoggles = 44,
Headphones = 45,
MaskHat = 46,
PaperMask = 47,
Security = 48,
StrapHat = 49,
Banana = 50,
Beanie = 51,
Bear = 52,
Cheese = 53,
Cherry = 54,
Egg = 55,
GreenFedora = 56,
Flamingo = 57,
FlowerPin = 58,
Helmet = 59,
Plant = 60,
BatEyes = 61,
BatWings = 62,
Horns = 63,
Mohawk = 64,
Pumpkin = 65,
ScaryBag = 66,
Witch = 67,
Wolf = 68,
Pirate = 69,
Plague = 70,
Machete = 71,
Fred = 72,
MinerCap = 73,
WinterHat = 74,
Archae = 75,
Antenna = 76,
Balloon = 77,
BirdNest = 78,
BlackBelt = 79,
Caution = 80,
Chef = 81,
CopHat = 82,
DoRag = 83,
DumSticker = 84,
Fez = 85,
GeneralHat = 86,
GreyThing = 87,
HunterCap = 88,
JungleHat = 89,
MiniCrewmate = 90,
NinjaMask = 91,
RamHorns = 92,
SnowCrewmate = 93,
GeoffHat = 94,
DaveCap = 95,
EllieRose = 96,
SvenHat = 97,
BurtHat = 98,
EllryMohawk = 99,
ThomasMonoc = 100,
Wizard = 101,
Fredrick = 102,
MrMacbeth = 103,
ToppatHenry = 104,
ToppatEllie = 105,
GeoffreyPlumb = 106,
RightHandMan = 107,
AngryEyebrows = 108,
ChocolateIce = 109,
HeartPin = 110,
Ponytail = 111,
RubberGlove = 112,
UnicornHorn = 113,
Zipper = 114,
}
}
@@ -1,18 +0,0 @@
namespace Impostor.Api.Innersloth.Customization
{
public enum PetType : uint
{
NoPet = 0,
Alien = 1,
Crewmate = 2,
Doggy = 3,
Stickmin = 4,
Hamster = 5,
Robot = 6,
Ufo = 7,
Ellie = 8,
Squig = 9,
Bedcrab = 10,
Glitch = 11,
}
}
@@ -0,0 +1,56 @@
using Impostor.Api.Net.Messages;
namespace Impostor.Api.Innersloth.Customization
{
public class PlayerOutfit
{
public ColorType Color { get; internal set; } = (ColorType)(-1);
public string HatId { get; internal set; } = "missing";
public string PetId { get; internal set; } = "missing";
public string SkinId { get; internal set; } = "missing";
public string VisorId { get; internal set; } = "missing";
public string NamePlateId { get; internal set; } = "missing";
public string PlayerName { get; internal set; } = "missing";
public bool IsIncomplete
{
get
{
if (!string.IsNullOrEmpty(PlayerName) && Color != (ColorType)(-1) && HatId != "missing" && PetId != "missing" && SkinId != "missing" && VisorId != "missing")
{
return NamePlateId == "missing";
}
return true;
}
}
public void Serialize(IMessageWriter writer)
{
writer.Write(PlayerName);
writer.WritePacked((int)Color);
writer.Write(HatId);
writer.Write(PetId);
writer.Write(SkinId);
writer.Write(VisorId);
writer.Write(NamePlateId);
}
public void Deserialize(IMessageReader reader)
{
PlayerName = reader.ReadString();
Color = (ColorType)reader.ReadPackedInt32();
HatId = reader.ReadString();
PetId = reader.ReadString();
SkinId = reader.ReadString();
VisorId = reader.ReadString();
NamePlateId = reader.ReadString();
}
}
}
@@ -0,0 +1,8 @@
namespace Impostor.Api.Innersloth.Customization
{
public enum PlayerOutfitType
{
Default,
Shapeshifted,
}
}
@@ -1,25 +0,0 @@
namespace Impostor.Api.Innersloth.Customization
{
public enum SkinType : uint
{
None = 0,
Astro = 1,
Capt = 2,
Mech = 3,
Military = 4,
Police = 5,
Science = 6,
SuitB = 7,
SuitW = 8,
Wall = 9,
Hazmat = 10,
Security = 11,
Tarmac = 12,
Miner = 13,
Winter = 14,
Archae = 15,
Prisoner = 16,
CCC = 17,
RightHandMan = 18,
}
}
+34 -9
View File
@@ -9,7 +9,7 @@ namespace Impostor.Api.Innersloth
/// <summary>
/// The latest major version of the game client.
/// </summary>
public const int LatestVersion = 4;
public const int LatestVersion = 5;
/// <summary>
/// Gets or sets host's version of the game.
@@ -124,6 +124,11 @@ namespace Impostor.Api.Innersloth
/// </summary>
public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always;
/// <summary>
/// Gets or sets role options.
/// </summary>
public RoleOptionsData RoleOptions { get; set; } = new RoleOptionsData();
/// <summary>
/// Gets or sets a value indicating whether the GameOptions are the default ones.
/// </summary>
@@ -166,24 +171,34 @@ namespace Impostor.Api.Innersloth
writer.Write((uint)VotingTime);
writer.Write((bool)IsDefaults);
if (version > 1)
if (version >= 2)
{
writer.Write((byte)EmergencyCooldown);
}
if (version > 2)
if (version >= 3)
{
writer.Write((bool)ConfirmImpostor);
writer.Write((bool)VisualTasks);
}
if (version > 3)
if (version >= 4)
{
writer.Write((bool)AnonymousVotes);
writer.Write((byte)TaskBarUpdate);
}
if (version > 4)
if (version >= 5)
{
RoleOptions.Serialize(writer);
}
if (version >= 6)
{
// Nothing was changed in V6
}
if (version > 6)
{
throw new ImpostorException($"Unknown GameOptionsData version {Version}.");
}
@@ -228,24 +243,34 @@ namespace Impostor.Api.Innersloth
IsDefaults = bytes.ReadBoolean();
if (Version > 1)
if (Version >= 2)
{
EmergencyCooldown = bytes.ReadByte();
}
if (Version > 2)
if (Version >= 3)
{
ConfirmImpostor = bytes.ReadBoolean();
VisualTasks = bytes.ReadBoolean();
}
if (Version > 3)
if (Version >= 4)
{
AnonymousVotes = bytes.ReadBoolean();
TaskBarUpdate = (TaskBarUpdate)bytes.ReadByte();
}
if (Version > 4)
if (Version >= 5)
{
RoleOptions = RoleOptionsData.Deserialize(bytes);
}
if (Version >= 6)
{
// Nothing was changed in V6
}
if (Version > 6)
{
throw new ImpostorException($"Unknown GameOptionsData version {Version}.");
}
@@ -0,0 +1,50 @@
using System;
using Impostor.Api.Net.Messages;
namespace Impostor.Api.Innersloth
{
public class PlatformSpecificData
{
public PlatformSpecificData(Platforms platform, string platformName, ulong? xboxPlatformId = null, ulong? psnPlatformId = null)
{
Platform = platform;
PlatformName = platformName;
XboxPlatformId = xboxPlatformId;
PsnPlatformId = psnPlatformId;
}
public PlatformSpecificData(IMessageReader reader)
{
Platform = (Platforms)reader.Tag;
PlatformName = reader.ReadString();
XboxPlatformId = Platform == Platforms.Xbox ? reader.ReadUInt64() : null;
PsnPlatformId = Platform == Platforms.Playstation ? reader.ReadUInt64() : null;
}
public Platforms Platform { get; }
public string PlatformName { get; }
public ulong? XboxPlatformId { get; }
public ulong? PsnPlatformId { get; }
public void Serialize(IMessageWriter writer)
{
writer.StartMessage((byte)Platform);
writer.Write(PlatformName);
switch (Platform)
{
case Platforms.Xbox:
writer.Write(XboxPlatformId ?? throw new NullReferenceException($"{nameof(XboxPlatformId)} shouldn't be null when {nameof(Platform)} is Xbox"));
break;
case Platforms.Playstation:
writer.Write(PsnPlatformId ?? throw new NullReferenceException($"{nameof(PsnPlatformId)} shouldn't be null when {nameof(Platform)} is Playstation"));
break;
}
writer.EndMessage();
}
}
}
+2
View File
@@ -11,5 +11,7 @@ namespace Impostor.Api.Innersloth
IPhone = 6,
Android = 7,
Switch = 8,
Xbox = 9,
Playstation = 10,
}
}
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.IO;
using Impostor.Api.Net.Messages;
namespace Impostor.Api.Innersloth
{
public class RoleOptionsData
{
public bool ShapeshifterLeaveSkin { get; set; }
public byte ShapeshifterCooldown { get; set; } = 10;
public byte ShapeshifterDuration { get; set; } = 30;
public byte ScientistCooldown { get; set; } = 15;
public byte ScientistBatteryCharge { get; set; } = 5;
public byte GuardianAngelCooldown { get; set; } = 60;
public bool ImpostorsCanSeeProtect { get; set; }
public byte ProtectionDurationSeconds { get; set; } = 10;
public byte EngineerCooldown { get; set; } = 30;
public byte EngineerInVentMaxTime { get; set; } = 15;
public Dictionary<RoleTypes, RoleRate> RoleRates { get; } = new Dictionary<RoleTypes, RoleRate>();
public static RoleOptionsData Deserialize(ReadOnlySpan<byte> span)
{
var roleOptionsData = new RoleOptionsData();
var num = span.ReadInt32();
for (var i = 0; i < num; i++)
{
var key = (RoleTypes)span.ReadInt16();
var roleRate = new RoleRate(span.ReadByte(), span.ReadByte());
roleOptionsData.RoleRates[key] = roleRate;
}
roleOptionsData.ShapeshifterLeaveSkin = span.ReadBoolean();
roleOptionsData.ShapeshifterCooldown = span.ReadByte();
roleOptionsData.ShapeshifterDuration = span.ReadByte();
roleOptionsData.ScientistCooldown = span.ReadByte();
roleOptionsData.GuardianAngelCooldown = span.ReadByte();
roleOptionsData.EngineerCooldown = span.ReadByte();
roleOptionsData.EngineerInVentMaxTime = span.ReadByte();
roleOptionsData.ScientistBatteryCharge = span.ReadByte();
roleOptionsData.ProtectionDurationSeconds = span.ReadByte();
roleOptionsData.ImpostorsCanSeeProtect = span.ReadBoolean();
return roleOptionsData;
}
public static RoleOptionsData Deserialize(IMessageReader reader)
{
var roleOptionsData = new RoleOptionsData();
var num = reader.ReadPackedInt32();
for (var i = 0; i < num; i++)
{
var key = (RoleTypes)reader.ReadInt16();
var roleRate = new RoleRate(reader.ReadByte(), reader.ReadByte());
roleOptionsData.RoleRates[key] = roleRate;
}
roleOptionsData.ShapeshifterLeaveSkin = reader.ReadBoolean();
roleOptionsData.ShapeshifterCooldown = reader.ReadByte();
roleOptionsData.ShapeshifterDuration = reader.ReadByte();
roleOptionsData.ScientistCooldown = reader.ReadByte();
roleOptionsData.GuardianAngelCooldown = reader.ReadByte();
roleOptionsData.EngineerCooldown = reader.ReadByte();
roleOptionsData.EngineerInVentMaxTime = reader.ReadByte();
roleOptionsData.ScientistBatteryCharge = reader.ReadByte();
roleOptionsData.ProtectionDurationSeconds = reader.ReadByte();
roleOptionsData.ImpostorsCanSeeProtect = reader.ReadBoolean();
return roleOptionsData;
}
public void Serialize(IMessageWriter writer)
{
writer.WritePacked(RoleRates.Count);
foreach (var roleRate in RoleRates)
{
writer.Write((ushort)roleRate.Key);
writer.Write((byte)roleRate.Value.MaxCount);
writer.Write((byte)roleRate.Value.Chance);
}
writer.Write(ShapeshifterLeaveSkin);
writer.Write(ShapeshifterCooldown);
writer.Write(ShapeshifterDuration);
writer.Write(ScientistCooldown);
writer.Write(GuardianAngelCooldown);
writer.Write(EngineerCooldown);
writer.Write(EngineerInVentMaxTime);
writer.Write(ScientistBatteryCharge);
writer.Write(ProtectionDurationSeconds);
writer.Write(ImpostorsCanSeeProtect);
}
public void Serialize(BinaryWriter writer)
{
writer.Write(RoleRates.Count);
foreach (var roleRate in RoleRates)
{
writer.Write((ushort)roleRate.Key);
writer.Write((byte)roleRate.Value.MaxCount);
writer.Write((byte)roleRate.Value.Chance);
}
writer.Write(ShapeshifterLeaveSkin);
writer.Write(ShapeshifterCooldown);
writer.Write(ShapeshifterDuration);
writer.Write(ScientistCooldown);
writer.Write(GuardianAngelCooldown);
writer.Write(EngineerCooldown);
writer.Write(EngineerInVentMaxTime);
writer.Write(ScientistBatteryCharge);
writer.Write(ProtectionDurationSeconds);
writer.Write(ImpostorsCanSeeProtect);
}
public readonly struct RoleRate
{
public readonly int MaxCount;
public readonly int Chance;
public RoleRate(int maxCount, int chance)
{
MaxCount = maxCount;
Chance = chance;
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace Impostor.Api.Innersloth
{
public enum RoleTypes : ushort
{
Crewmate,
Impostor,
Scientist,
Engineer,
GuardianAngel,
Shapeshifter,
}
}
+13 -1
View File
@@ -4,7 +4,19 @@
{
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 <= '힣');
return i == ' ' ||
(i >= 'A' && i <= 'Z') ||
(i >= 'a' && i <= 'z') ||
(i >= '0' && i <= '9') ||
// U+00C0 to U+00FF (Latin-1 Supplement)
(i >= 'À' && i <= 'ÿ') ||
// U+0400 to U+045F (Cyrillic)
(i >= 'Ѐ' && i <= 'џ') ||
// U+2C61 to U+D7A3 (CJK)
(i >= 'ⱡ' && i <= '힣');
}
}
}
+11 -1
View File
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Messages;
@@ -65,6 +65,16 @@ namespace Impostor.Api.Net
/// </summary>
IClientPlayer? Player { get; }
/// <summary>
/// Gets the version of the game the client is using.
/// </summary>
int GameVersion { get; }
/// <summary>
/// Gets platform specific data of the <see cref="IClient" />.
/// </summary>
PlatformSpecificData PlatformSpecificData { get; }
ValueTask<bool> ReportCheatAsync(CheatContext context, string message);
ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
@@ -49,25 +49,25 @@ namespace Impostor.Api.Net.Inner.Objects
/// Sets the hat of the current <see cref="IInnerPlayerControl" />.
/// Visible to all players.
/// </summary>
/// <param name="hatType">An hat for the player.</param>
/// <param name="hatId">An hat for the player.</param>
/// <returns>Task that must be awaited.</returns>
ValueTask SetHatAsync(HatType hatType);
ValueTask SetHatAsync(string hatId);
/// <summary>
/// Sets the pet of the current <see cref="IInnerPlayerControl" />.
/// Visible to all players.
/// </summary>
/// <param name="petType">A pet for the player.</param>
/// <param name="petId">A pet for the player.</param>
/// <returns>Task that must be awaited.</returns>
ValueTask SetPetAsync(PetType petType);
ValueTask SetPetAsync(string petId);
/// <summary>
/// Sets the skin of the current <see cref="IInnerPlayerControl" />.
/// Visible to all players.
/// </summary>
/// <param name="skinType">A skin for the player.</param>
/// <param name="skinId">A skin for the player.</param>
/// <returns>Task that must be awaited.</returns>
ValueTask SetSkinAsync(SkinType skinType);
ValueTask SetSkinAsync(string skinId);
/// <summary>
/// Send a chat message as the current <see cref="IInnerPlayerControl" />.
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
@@ -12,26 +12,6 @@ namespace Impostor.Api.Net.Inner.Objects
/// </summary>
string PlayerName { get; }
/// <summary>
/// Gets the color of the player.
/// </summary>
ColorType Color { get; }
/// <summary>
/// Gets the hat of the player.
/// </summary>
HatType Hat { get; }
/// <summary>
/// Gets the pet of the player.
/// </summary>
PetType Pet { get; }
/// <summary>
/// Gets the skin of the player.
/// </summary>
SkinType Skin { get; }
/// <summary>
/// Gets a value indicating whether the player is an impostor.
/// </summary>
@@ -42,6 +22,12 @@ namespace Impostor.Api.Net.Inner.Objects
/// </summary>
bool IsDead { get; }
Dictionary<PlayerOutfitType, PlayerOutfit> Outfits { get; }
PlayerOutfitType CurrentOutfitType { get; set; }
PlayerOutfit CurrentOutfit { get; }
/// <summary>
/// Gets the reason why the player is dead in the current game.
/// </summary>
@@ -50,5 +36,7 @@ namespace Impostor.Api.Net.Inner.Objects
IEnumerable<ITaskInfo> Tasks { get; }
DateTimeOffset LastMurder { get; }
uint PlayerLevel { get; }
}
}
@@ -7,7 +7,7 @@ namespace Impostor.Api.Net.Inner.Objects
{
uint Id { get; }
ITask Task { get; }
ITask? Task { get; }
bool Complete { get; }
+11 -3
View File
@@ -11,15 +11,12 @@
SetName = 6,
CheckColor = 7,
SetColor = 8,
SetHat = 9,
SetSkin = 10,
ReportDeadBody = 11,
MurderPlayer = 12,
SendChat = 13,
StartMeeting = 14,
SetScanner = 15,
SendChatNote = 16,
SetPet = 17,
SetStartCounter = 18,
EnterVent = 19,
ExitVent = 20,
@@ -37,5 +34,16 @@
SendQuickChat = 33,
BootFromVent = 34,
UpdateSystem = 35,
SetLevel = 38,
SetHat = 39,
SetSkin = 40,
SetPet = 41,
SetVisor = 42,
SetNamePlate = 43,
SetRole = 44,
ProtectPlayer = 45,
Shapeshift = 46,
CheckMurder = 47,
CheckProtect = 48,
}
}
@@ -1,15 +0,0 @@
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)
{
}
}
}
@@ -1,19 +0,0 @@
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();
}
}
}
@@ -1,32 +0,0 @@
using System.Collections.Generic;
namespace Impostor.Api.Net.Messages.Announcements
{
public static class Message04AddLanguages
{
public static void Serialize(IMessageWriter writer, Dictionary<string, uint> languages)
{
writer.StartMessage(AnnouncementsMessageFlags.AddLanguages);
writer.Write((byte)languages.Count);
foreach (var (name, id) in languages)
{
writer.Write(name);
writer.Write(id);
}
writer.EndMessage();
}
public static void Deserialize(IMessageReader reader, out Dictionary<string, uint> languages)
{
var length = reader.ReadByte();
languages = new Dictionary<string, uint>(length);
for (var i = 0; i < length; i++)
{
languages[reader.ReadString()] = reader.ReadUInt32();
}
}
}
}
@@ -1,15 +0,0 @@
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();
}
}
}
@@ -1,9 +0,0 @@
namespace Impostor.Api.Net.Messages
{
public class AnnouncementsMessageFlags
{
public const byte UseCache = 0;
public const byte SetUpdate = 1;
public const byte AddLanguages = 4;
}
}
@@ -1,15 +0,0 @@
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();
}
}
}
@@ -1,14 +0,0 @@
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();
}
}
}
@@ -4,18 +4,14 @@ 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, out Language language, out QuickChatModes chatMode)
public static void Deserialize(IMessageReader reader, out int clientVersion, out string name, out Language language, out QuickChatModes chatMode, out PlatformSpecificData? platformSpecificData)
{
clientVersion = reader.ReadInt32();
name = reader.ReadString();
if (clientVersion >= Version.V1)
{
lastNonceReceived = reader.ReadUInt32();
}
else
{
lastNonceReceived = null;
reader.ReadUInt32(); // lastNonceReceived, always 0 since 2021.11.9
}
if (clientVersion >= Version.V2)
@@ -28,6 +24,22 @@ namespace Impostor.Api.Net.Messages.C2S
language = Language.English;
chatMode = QuickChatModes.FreeChatOrQuickChat;
}
if (clientVersion >= Version.V3)
{
using var platformReader = reader.ReadMessage();
platformSpecificData = new PlatformSpecificData(platformReader);
reader.ReadInt32(); // crossplayFlags, not used yet
}
else
{
platformSpecificData = null;
}
if (clientVersion >= Version.V4)
{
reader.ReadByte(); // purpose unknown, seems hardcoded to 0
}
}
private static class Version
@@ -35,6 +47,10 @@ namespace Impostor.Api.Net.Messages.C2S
public static readonly int V1 = GameVersion.GetVersion(2021, 4, 25);
public static readonly int V2 = GameVersion.GetVersion(2021, 6, 30);
public static readonly int V3 = GameVersion.GetVersion(2021, 11, 9);
public static readonly int V4 = GameVersion.GetVersion(2021, 12, 14);
}
}
}
@@ -8,6 +8,7 @@ namespace Impostor.Api.Net.Messages.C2S
{
writer.StartMessage(MessageFlags.HostGame);
gameOptionsData.Serialize(writer);
writer.Write(int.MaxValue); // crossplayFlags
writer.EndMessage();
}
@@ -15,12 +16,11 @@ namespace Impostor.Api.Net.Messages.C2S
/// Deserialize a packet.
/// </summary>
/// <param name="reader"><see cref="IMessageReader" /> with <see cref="IMessageReader.Tag" /> 0.</param>
/// <param name="chatMode">The chat type selected in the client of the player.</param>
/// <returns>Deserialized <see cref="GameOptionsData" />.</returns>
public static GameOptionsData Deserialize(IMessageReader reader, out QuickChatModes chatMode)
public static GameOptionsData Deserialize(IMessageReader reader)
{
var gameOptionsData = GameOptionsData.DeserializeCreate(reader);
chatMode = (QuickChatModes)reader.ReadByte();
reader.ReadInt32(); // crossplayFlags, not used yet
return gameOptionsData;
}
@@ -13,6 +13,7 @@ namespace Impostor.Api.Net.Messages.C2S
public static void Deserialize(IMessageReader reader, out GameCode gameCode)
{
gameCode = reader.ReadInt32();
reader.ReadBoolean(); // no crossplay
}
}
}
@@ -12,9 +12,15 @@ namespace Impostor.Api.Net.Messages.C2S
public static void Deserialize(IMessageReader reader, out GameOptionsData options, out QuickChatModes chatMode)
{
reader.ReadPackedInt32(); // Hardcoded 0.
var version = reader.ReadPackedInt32();
if (version != 2)
{
throw new NotSupportedException($"Version {version} of {nameof(Message16GetGameListC2S)} is not supported");
}
options = GameOptionsData.DeserializeCreate(reader);
chatMode = (QuickChatModes)reader.ReadByte();
reader.ReadInt32(); // crossplayFlags, not used yet
}
}
}
@@ -0,0 +1,17 @@
using System;
namespace Impostor.Api.Net.Messages.C2S
{
public class Message21SetActivePodType
{
public static void Serialize(IMessageWriter writer)
{
throw new NotImplementedException();
}
public static void Deserialize(IMessageReader reader, out string podType)
{
podType = reader.ReadString();
}
}
}
@@ -0,0 +1,18 @@
using System;
using Impostor.Api.Games;
namespace Impostor.Api.Net.Messages.C2S
{
public class Message22QueryPlatformIdsC2S
{
public static void Serialize(IMessageWriter writer)
{
throw new NotImplementedException();
}
public static void Deserialize(IMessageReader reader, out GameCode gameCode)
{
gameCode = reader.ReadInt32();
}
}
}
@@ -48,6 +48,10 @@ namespace Impostor.Api.Net.Messages
int ReadInt32();
ulong ReadUInt64();
long ReadInt64();
float ReadSingle();
string ReadString(int length);
@@ -68,6 +68,18 @@ namespace Impostor.Api.Net.Messages
/// <param name="value">Value to write.</param>
void Write(int value);
/// <summary>
/// Writes an ulong to the message.
/// </summary>
/// <param name="value">Value to write.</param>
void Write(ulong value);
/// <summary>
/// Writes an ulong to the message.
/// </summary>
/// <param name="value">Value to write.</param>
void Write(long value);
/// <summary>
/// Writes a float to the message.
/// </summary>
@@ -16,7 +16,13 @@
public const byte WaitForHost = 12;
public const byte Redirect = 13;
public const byte ReselectServer = 14;
public const byte GetGameList = 9;
public const byte GetGameListV2 = 16;
public const byte ReportPlayer = 17;
public const byte QuickMatch = 18;
public const byte QuickMatchHost = 19;
public const byte SetGameSession = 20;
public const byte SetActivePodType = 21;
public const byte QueryPlatformIds = 22;
public const byte QueryLobbyInfo = 23;
}
}
@@ -1,17 +0,0 @@
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();
}
}
}
@@ -1,17 +0,0 @@
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();
}
}
}
@@ -1,17 +0,0 @@
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();
}
}
}
@@ -0,0 +1,15 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc39SetHat
{
public static void Serialize(IMessageWriter writer, string hat)
{
writer.Write(hat);
}
public static void Deserialize(IMessageReader reader, out string hat)
{
hat = reader.ReadString();
}
}
}
@@ -0,0 +1,15 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc40SetSkin
{
public static void Serialize(IMessageWriter writer, string skin)
{
writer.Write(skin);
}
public static void Deserialize(IMessageReader reader, out string skin)
{
skin = reader.ReadString();
}
}
}
@@ -0,0 +1,15 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc41SetPet
{
public static void Serialize(IMessageWriter writer, string pet)
{
writer.Write(pet);
}
public static void Deserialize(IMessageReader reader, out string pet)
{
pet = reader.ReadString();
}
}
}
@@ -0,0 +1,17 @@
using Impostor.Api.Innersloth;
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc44SetRole
{
public static void Serialize(IMessageWriter writer, RoleTypes role)
{
writer.Write((ushort)role);
}
public static void Deserialize(IMessageReader reader, out RoleTypes role)
{
role = (RoleTypes)reader.ReadUInt16();
}
}
}
@@ -0,0 +1,21 @@
using Impostor.Api.Games;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc45ProtectPlayer
{
public static void Serialize(IMessageWriter writer, IInnerPlayerControl playerControl, ColorType color)
{
writer.Write(playerControl.NetId);
writer.Write((byte)color);
}
public static void Deserialize(IMessageReader reader, IGame game, out IInnerPlayerControl? playerControl, out ColorType color)
{
playerControl = reader.ReadNetObject<IInnerPlayerControl>(game);
color = (ColorType)reader.ReadByte();
}
}
}
@@ -0,0 +1,20 @@
using Impostor.Api.Games;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc46Shapeshift
{
public static void Serialize(IMessageWriter writer, IInnerPlayerControl playerControl, bool shouldAnimate)
{
writer.Write(playerControl.NetId);
writer.Write(shouldAnimate);
}
public static void Deserialize(IMessageReader reader, IGame game, out IInnerPlayerControl? playerControl, out bool shouldAnimate)
{
playerControl = reader.ReadNetObject<IInnerPlayerControl>(game);
shouldAnimate = reader.ReadBoolean();
}
}
}
@@ -0,0 +1,18 @@
using Impostor.Api.Games;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc47CheckMurder
{
public static void Serialize(IMessageWriter writer, IInnerPlayerControl playerControl)
{
writer.Write(playerControl.NetId);
}
public static void Deserialize(IMessageReader reader, IGame game, out IInnerPlayerControl? playerControl)
{
playerControl = reader.ReadNetObject<IInnerPlayerControl>(game);
}
}
}
@@ -0,0 +1,18 @@
using Impostor.Api.Games;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc48CheckProtect
{
public static void Serialize(IMessageWriter writer, IInnerPlayerControl playerControl)
{
writer.Write(playerControl.NetId);
}
public static void Deserialize(IMessageReader reader, IGame game, out IInnerPlayerControl? playerControl)
{
playerControl = reader.ReadNetObject<IInnerPlayerControl>(game);
}
}
}
@@ -1,11 +1,11 @@
using System;
using System;
using Impostor.Api.Innersloth;
namespace Impostor.Api.Net.Messages.S2C
{
public class Message01JoinGameS2C
{
public static void SerializeJoin(IMessageWriter writer, bool clear, int gameCode, int playerId, int hostId)
public static void SerializeJoin(IMessageWriter writer, bool clear, int gameCode, IClientPlayer player, int hostId)
{
if (clear)
{
@@ -14,11 +14,19 @@ namespace Impostor.Api.Net.Messages.S2C
writer.StartMessage(MessageFlags.JoinGame);
writer.Write(gameCode);
writer.Write(playerId);
writer.Write(player.Client.Id);
writer.Write(hostId);
writer.Write(player.Client.Name);
player.Client.PlatformSpecificData.Serialize(writer);
writer.WritePacked(player.Character?.PlayerInfo.PlayerLevel ?? 1);
// ProductUserId and FriendCode are not yet known, so set them to an empty string
writer.Write(string.Empty);
writer.Write(string.Empty);
writer.EndMessage();
}
[Obsolete("JoinGame errors are no longer used by 2021.11.9 and up, disconnect clients instead.")]
public static void SerializeError(IMessageWriter writer, bool clear, DisconnectReason reason, string? message = null)
{
if (clear)
@@ -1,10 +1,10 @@
using System;
using System;
namespace Impostor.Api.Net.Messages.S2C
{
public static class Message07JoinedGameS2C
{
public static void Serialize(IMessageWriter writer, bool clear, int gameCode, int playerId, int hostId, int[] otherPlayerIds)
public static void Serialize(IMessageWriter writer, bool clear, int gameCode, int playerId, int hostId, IClientPlayer[] otherPlayers, bool post20220202 = true)
{
if (clear)
{
@@ -15,11 +15,21 @@ namespace Impostor.Api.Net.Messages.S2C
writer.Write(gameCode);
writer.Write(playerId);
writer.Write(hostId);
writer.WritePacked(otherPlayerIds.Length);
writer.WritePacked(otherPlayers.Length);
foreach (var id in otherPlayerIds)
foreach (var ply in otherPlayers)
{
writer.WritePacked(id);
writer.WritePacked(ply.Client.Id);
writer.Write(ply.Client.Name);
ply.Client.PlatformSpecificData.Serialize(writer);
writer.WritePacked(ply.Character?.PlayerInfo.PlayerLevel ?? 1);
if (post20220202)
{
// ProductUserId and FriendCode are not yet known, so set them to an empty string
writer.Write(string.Empty);
writer.Write(string.Empty);
}
}
writer.EndMessage();
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Impostor.Api.Games;
@@ -25,6 +25,9 @@ namespace Impostor.Api.Net.Messages.S2C
writer.Write((byte)game.Options.Map);
writer.Write((byte)game.Options.NumImpostors);
writer.Write((byte)game.Options.MaxPlayers);
var platform = game.Host?.Client.PlatformSpecificData;
writer.Write((byte)(platform?.Platform ?? 0));
writer.Write(platform?.PlatformName ?? string.Empty);
writer.EndMessage();
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
namespace Impostor.Api.Net.Messages.S2C
{
public class Message22QueryPlatformIdsS2C
{
public static void Serialize(IMessageWriter writer, GameCode gameCode, IEnumerable<PlatformSpecificData> playerSpecificDatas)
{
writer.StartMessage(MessageFlags.QueryPlatformIds);
writer.Write(gameCode);
foreach (var playerSpecificData in playerSpecificDatas)
{
playerSpecificData.Serialize(writer);
}
writer.EndMessage();
}
public static void Deserialize(IMessageReader reader)
{
throw new NotImplementedException();
}
}
}
@@ -39,7 +39,7 @@ namespace Impostor.Api.Net.Messages.S2C
if (hasReason)
{
var inner = reader.ReadMessage();
using var inner = reader.ReadMessage();
reason = (DisconnectReason)inner.ReadByte();
message = reason == DisconnectReason.Custom ? inner.ReadString() : null;
}
@@ -2,12 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
</ItemGroup>
<ItemGroup>
@@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
@@ -10,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
</ItemGroup>
</Project>
@@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
<ProjectReference Include="..\Impostor.Hazel\Hazel\Hazel.csproj" />
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<OutputType>Library</OutputType>
<Version>1.0.0</Version>
@@ -18,7 +18,6 @@ namespace Impostor.Plugins.Example
services.AddSingleton<IEventListener, ClientEventListener>();
services.AddSingleton<IEventListener, PlayerEventListener>();
services.AddSingleton<IEventListener, MeetingEventListener>();
services.AddSingleton<IEventListener, AnnouncementsListener>();
}
}
}
@@ -1,26 +0,0 @@
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!");
}
}
}
}
@@ -40,9 +40,10 @@ namespace Impostor.Plugins.Example.Handlers
{
// Modify player properties.
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));
// TODO Rewrite using cosmetics source generator
// 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));
}
@@ -74,9 +75,9 @@ namespace Impostor.Plugins.Example.Handlers
if (e.Message == "look")
{
await e.PlayerControl.SetColorAsync(ColorType.Pink);
await e.PlayerControl.SetHatAsync(HatType.Cheese);
await e.PlayerControl.SetSkinAsync(SkinType.Police);
await e.PlayerControl.SetPetAsync(PetType.Ufo);
await e.PlayerControl.SetHatAsync("hat_pk05_Cheese");
await e.PlayerControl.SetSkinAsync("skin_Police");
await e.PlayerControl.SetPetAsync("pet_alien1");
}
if (e.Message == "snap")
@@ -1,22 +0,0 @@
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);
}
}
}
@@ -1,27 +0,0 @@
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);
}
}
}
@@ -1,27 +0,0 @@
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 bool UseCached { get; set; } = false;
public Announcement? Announcement { get; set; } = null;
}
}
}
@@ -0,0 +1,31 @@
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages.S2C;
using Impostor.Hazel;
namespace Impostor.Server
{
public static class IHazelConnectionExtensions
{
/// <summary>
/// Disconnect a connection using a custom message.
/// </summary>
/// <param name="connection">The connection to disconnect.</param>
/// <param name="reason">The reason to disconnect with.</param>
/// <param name="message">The custom message to disconnect with if <paramref name="reason"/> is <see cref="DisconnectReason.Custom"/>.</param>
/// <returns>Task that should be awaited to ensure disconnection.</returns>
public static async ValueTask CustomDisconnectAsync(this IHazelConnection connection, DisconnectReason reason, string? message = null)
{
if (!connection.IsConnected)
{
return;
}
using var writer = MessageWriter.Get();
MessageDisconnect.Serialize(writer, true, reason, message);
await connection.DisconnectAsync(reason.ToString(), writer);
}
}
}
+10 -10
View File
@@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm;linux-arm64;osx-x64</RuntimeIdentifiers>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<ApplicationIcon>icon.ico</ApplicationIcon>
@@ -25,18 +24,19 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.4" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.1.2" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.333">
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="6.0.5" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.5" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- HACK: Some plugins depend on System.IO.Pipelines and expect the version to be 5.0.0.0, but .NET 5 only ships version 5.0.0.1. It is pretty hard to convince MSBuild to build against the correct DLL, so we special case it here. Remove once we move past .NET 5 -->
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
<!-- HACK: Some plugins depend on System.IO.Pipelines and expect the version to be 6.0.0.0, but Hazel still depends on version 5.0.0.0. Figure out why this happens. -->
<PackageReference Include="System.IO.Pipelines" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
@@ -1,106 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api.Events.Managers;
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<AnnouncementsService> _logger;
private readonly AnnouncementsServerConfig _config;
private readonly ObjectPool<MessageReader> _readerPool;
private readonly IEventManager _eventManager;
private UdpConnectionListener? _connection;
public AnnouncementsService(ILogger<AnnouncementsService> logger, IOptions<AnnouncementsServerConfig> config, ObjectPool<MessageReader> 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);
}
await e.Connection.Disconnect(null);
}
}
}
-88
View File
@@ -1,88 +0,0 @@
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<AuthService> _logger;
private readonly AuthServerConfig _config;
private readonly ObjectPool<MessageReader> _readerPool;
private readonly IEventManager _eventManager;
private DtlsConnectionListener? _connection;
public AuthService(ILogger<AuthService> logger, IOptions<AuthServerConfig> config, ObjectPool<MessageReader> 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);
}
}
}
+34 -3
View File
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Games;
@@ -24,8 +25,8 @@ namespace Impostor.Server.Net
private readonly GameManager _gameManager;
private readonly ICustomMessageManager<ICustomRootMessage> _customMessageManager;
public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, string name, int gameVersion, Language language, QuickChatModes chatMode, IHazelConnection connection)
: base(name, gameVersion, language, chatMode, connection)
public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, string name, int gameVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData, IHazelConnection connection)
: base(name, gameVersion, language, chatMode, platformSpecificData, connection)
{
_logger = logger;
_antiCheatConfig = antiCheatOptions.Value;
@@ -64,7 +65,7 @@ namespace Impostor.Server.Net
case MessageFlags.HostGame:
{
// Read game settings.
var gameInfo = Message00HostGameC2S.Deserialize(reader, out _);
var gameInfo = Message00HostGameC2S.Deserialize(reader);
// Create game.
var game = await _gameManager.CreateAsync(this, gameInfo);
@@ -257,6 +258,19 @@ namespace Impostor.Server.Net
break;
}
case MessageFlags.SetActivePodType:
{
Message21SetActivePodType.Deserialize(reader, out _);
break;
}
case MessageFlags.QueryPlatformIds:
{
Message22QueryPlatformIdsC2S.Deserialize(reader, out var gameCode);
await OnQueryPlatformIds(gameCode);
break;
}
default:
if (_customMessageManager.TryGet(flag, out var customRootMessage))
{
@@ -348,5 +362,22 @@ namespace Impostor.Server.Net
return Connection.SendAsync(message);
}
/// <summary>
/// Triggered when the connected client requests the PlatformSpecificData.
/// </summary>
/// <param name="code">
/// The GameCode of the game whose platform id's are checked.
/// </param>
private ValueTask OnQueryPlatformIds(GameCode code)
{
using var message = MessageWriter.Get(MessageType.Reliable);
var playerSpecificData = _gameManager.Find(code)?.Players.Select(p => p.Client.PlatformSpecificData) ?? Enumerable.Empty<PlatformSpecificData>();
Message22QueryPlatformIdsS2C.Serialize(message, code, playerSpecificData);
return Connection.SendAsync(message);
}
}
}
+5 -12
View File
@@ -5,20 +5,19 @@ using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.S2C;
using Impostor.Hazel;
using Impostor.Server.Net.State;
namespace Impostor.Server.Net
{
internal abstract class ClientBase : IClient
{
protected ClientBase(string name, int gameVersion, Language language, QuickChatModes chatMode, IHazelConnection connection)
protected ClientBase(string name, int gameVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData, IHazelConnection connection)
{
Name = name;
GameVersion = gameVersion;
Language = language;
ChatMode = chatMode;
PlatformSpecificData = platformSpecificData;
Connection = connection;
Items = new ConcurrentDictionary<object, object>();
}
@@ -31,6 +30,8 @@ namespace Impostor.Server.Net
public QuickChatModes ChatMode { get; }
public PlatformSpecificData PlatformSpecificData { get; }
public int GameVersion { get; }
public IHazelConnection Connection { get; }
@@ -52,15 +53,7 @@ namespace Impostor.Server.Net
public async ValueTask DisconnectAsync(DisconnectReason reason, string? message = null)
{
if (!Connection.IsConnected)
{
return;
}
using var writer = MessageWriter.Get();
MessageDisconnect.Serialize(writer, true, reason, message);
await Connection.DisconnectAsync(message ?? reason.ToString(), writer);
await Connection.CustomDisconnectAsync(reason, message);
}
}
}
@@ -15,9 +15,9 @@ namespace Impostor.Server.Net.Factories
_serviceProvider = serviceProvider;
}
public ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode)
public ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData)
{
var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, clientVersion, language, chatMode, connection);
var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, clientVersion, language, chatMode, platformSpecificData, connection);
connection.Client = client;
return client;
}
@@ -5,6 +5,6 @@ namespace Impostor.Server.Net.Factories
{
internal interface IClientFactory
{
ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode);
ClientBase Create(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData);
}
}
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Server.Net.Inner.Objects;
@@ -85,6 +86,32 @@ namespace Impostor.Server.Net.Inner
return true;
}
protected async ValueTask<bool> ValidateCanVent(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, bool value = true)
{
if (playerInfo.CanVent != value)
{
if (await sender.Client.ReportCheatAsync(context, "Failed can vent check"))
{
return false;
}
}
return true;
}
protected async ValueTask<bool> ValidateRole(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, RoleTypes role)
{
if (playerInfo.RoleType != role)
{
if (await sender.Client.ReportCheatAsync(context, $"Failed role = {role} check"))
{
return false;
}
}
return true;
}
protected async ValueTask<bool> UnregisteredCall(CheatContext context, IClientPlayer sender)
{
if (await sender.Client.ReportCheatAsync(context, "Client sent unregistered call"))
@@ -120,25 +120,36 @@ namespace Impostor.Server.Net.Inner.Objects.Components
}
}
if (!await ValidateImpostor(call, sender, _playerControl.PlayerInfo))
if (!await ValidateCanVent(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 (Game.GameNet.ShipStatus == null)
{
if (await sender.Client.ReportCheatAsync(call, "Failed vent position check"))
// Cannot perform vent position check on unknown ship statuses
if (await sender.Client.ReportCheatAsync(call, "Failed vent position check on unknown map"))
{
return false;
}
}
else
{
await _eventManager.CallAsync(new PlayerVentEvent(Game, sender, _playerControl, vent));
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);
@@ -47,7 +47,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components
case RpcCalls.EnterVent:
case RpcCalls.ExitVent:
{
if (!await ValidateImpostor(call, sender, _playerControl.PlayerInfo))
if (!await ValidateCanVent(call, sender, _playerControl.PlayerInfo))
{
return false;
}
@@ -66,7 +66,17 @@ namespace Impostor.Server.Net.Inner.Objects.Components
throw new ArgumentOutOfRangeException(nameof(call), call, null);
}
if (!Game.GameNet.ShipStatus!.Data.Vents.TryGetValue(ventId, out var vent))
if (Game.GameNet.ShipStatus == null)
{
if (await sender.Client.ReportCheatAsync(call, "Client interacted with vent on unknown map"))
{
return false;
}
break;
}
if (!Game.GameNet.ShipStatus.Data.Vents.TryGetValue(ventId, out var vent))
{
if (await sender.Client.ReportCheatAsync(call, "Client interacted with nonexistent vent"))
{
@@ -12,7 +12,7 @@ namespace Impostor.Server.Net.Inner.Objects
private readonly InnerPlayerInfo _playerInfo;
private readonly IEventManager _eventManager;
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, ITask task)
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, ITask? task)
{
_playerInfo = playerInfo;
_eventManager = eventManager;
@@ -22,7 +22,7 @@ namespace Impostor.Server.Net.Inner.Objects
public uint Id { get; internal set; }
public ITask Task { get; internal set; }
public ITask? Task { get; internal set; }
public bool Complete { get; internal set; }
@@ -59,16 +59,18 @@ namespace Impostor.Server.Net.Inner.Objects
return;
}
if (initialState)
while (reader.Position < reader.Length)
{
var num = reader.ReadPackedInt32();
for (var i = 0; i < num; i++)
var inner = reader.ReadMessage();
var playerInfo = this.GetPlayerById(inner.Tag);
if (playerInfo != null)
{
var playerId = reader.ReadByte();
var playerInfo = new InnerPlayerInfo(playerId);
playerInfo.Deserialize(reader);
playerInfo.Deserialize(inner);
}
else
{
playerInfo = new InnerPlayerInfo(inner.Tag);
playerInfo.Deserialize(inner);
if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
{
@@ -76,28 +78,6 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
}
else
{
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<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
@@ -160,12 +140,9 @@ namespace Impostor.Server.Net.Inner.Objects
var taskId = 0u;
foreach (var taskTypeId in taskTypeIds.Span)
{
player.Tasks.Add(new TaskInfo(
player,
_eventManager,
taskId++,
Game.GameNet!.ShipStatus!.Data.Tasks[taskTypeId]
));
var mapTasks = Game.GameNet!.ShipStatus?.Data.Tasks;
var taskType = (mapTasks != null && mapTasks.ContainsKey(taskTypeId)) ? mapTasks[taskTypeId] : null;
player.Tasks.Add(new TaskInfo(player, _eventManager, taskId++, taskType));
}
}
}
@@ -125,6 +125,10 @@ namespace Impostor.Server.Net.Inner.Objects
}
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
foreach (var messageReader in states)
{
messageReader.Dispose();
}
// This would be a nice place to implement an anti cheat.
// But for whatever reason host sends VotingComplete before sending his vote.
@@ -29,37 +29,37 @@ namespace Impostor.Server.Net.Inner.Objects
public async ValueTask SetColorAsync(ColorType color)
{
PlayerInfo.Color = color;
PlayerInfo.CurrentOutfit.Color = color;
using var writer = Game.StartRpc(NetId, RpcCalls.SetColor);
Rpc08SetColor.Serialize(writer, color);
await Game.FinishRpcAsync(writer);
}
public async ValueTask SetHatAsync(HatType hat)
public async ValueTask SetHatAsync(string hatId)
{
PlayerInfo.Hat = hat;
PlayerInfo.CurrentOutfit.HatId = hatId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetHat);
Rpc09SetHat.Serialize(writer, hat);
Rpc39SetHat.Serialize(writer, hatId);
await Game.FinishRpcAsync(writer);
}
public async ValueTask SetPetAsync(PetType pet)
public async ValueTask SetPetAsync(string petId)
{
PlayerInfo.Pet = pet;
PlayerInfo.CurrentOutfit.PetId = petId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetPet);
Rpc17SetPet.Serialize(writer, pet);
Rpc41SetPet.Serialize(writer, petId);
await Game.FinishRpcAsync(writer);
}
public async ValueTask SetSkinAsync(SkinType skin)
public async ValueTask SetSkinAsync(string skinId)
{
PlayerInfo.Skin = skin;
PlayerInfo.CurrentOutfit.SkinId = skinId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetSkin);
Rpc10SetSkin.Serialize(writer, skin);
Rpc40SetSkin.Serialize(writer, skinId);
await Game.FinishRpcAsync(writer);
}
@@ -61,6 +61,9 @@ namespace Impostor.Server.Net.Inner.Objects
internal Queue<ColorType> RequestedColorId { get; } = new Queue<ColorType>();
/// <summary> Gets or sets target that was set by the last CheckMurder RPC. </summary>
internal IInnerPlayerControl? IsMurdering { get; set; } = null;
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
@@ -182,8 +185,8 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc09SetHat.Deserialize(reader, out var hat);
return await HandleSetHat(sender, hat);
Rpc39SetHat.Deserialize(reader, out var hat);
return true;
}
case RpcCalls.SetSkin:
@@ -193,8 +196,41 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc10SetSkin.Deserialize(reader, out var skin);
return await HandleSetSkin(sender, skin);
Rpc40SetSkin.Deserialize(reader, out var skin);
return true;
}
case RpcCalls.SetVisor:
{
if (!await ValidateOwnership(call, sender))
{
return false;
}
// Rpc42SetVistor.Deserialize(reader, out var visor);
return true;
}
case RpcCalls.SetNamePlate:
{
if (!await ValidateOwnership(call, sender))
{
return false;
}
// Rpc43SetNamePlate.Deserialize(reader, out var namePlate);
return true;
}
case RpcCalls.SetLevel:
{
if (!await ValidateOwnership(call, sender))
{
return false;
}
// Rpc38SetLevel.Deserialize(reader, out var level);
return true;
}
case RpcCalls.ReportDeadBody:
@@ -210,7 +246,7 @@ namespace Impostor.Server.Net.Inner.Objects
case RpcCalls.MurderPlayer:
{
if (!await ValidateOwnership(call, sender) || !await ValidateImpostor(call, sender, PlayerInfo))
if (!await ValidateHost(call, sender))
{
return false;
}
@@ -271,7 +307,7 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc17SetPet.Deserialize(reader, out var pet);
Rpc41SetPet.Deserialize(reader, out var pet);
return await HandleSetPet(sender, pet);
}
@@ -308,6 +344,70 @@ namespace Impostor.Server.Net.Inner.Objects
break;
}
case RpcCalls.SetRole:
{
if (!await ValidateHost(call, sender))
{
return false;
}
Rpc44SetRole.Deserialize(reader, out var role);
PlayerInfo.RoleType = role;
if (Game.GameState == GameStates.Starting)
{
await Game.StartedAsync();
}
break;
}
case RpcCalls.ProtectPlayer:
{
if (!await ValidateHost(call, sender))
{
return false;
}
Rpc45ProtectPlayer.Deserialize(reader, Game, out _, out _);
break;
}
case RpcCalls.Shapeshift:
{
if (!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Shapeshifter))
{
return false;
}
Rpc46Shapeshift.Deserialize(reader, Game, out _, out _);
break;
}
case RpcCalls.CheckMurder:
{
if (!await ValidateImpostor(call, sender, PlayerInfo))
{
return false;
}
Rpc47CheckMurder.Deserialize(reader, Game, out var murdered);
return await HandleCheckMurder(sender, murdered);
}
case RpcCalls.CheckProtect:
{
if (!await ValidateRole(call, sender, PlayerInfo, RoleTypes.GuardianAngel))
{
return false;
}
Rpc48CheckProtect.Deserialize(reader, Game, out _);
break;
}
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
@@ -343,7 +443,7 @@ namespace Impostor.Server.Net.Inner.Objects
var player = Game.GameNet.GameData!.GetPlayerById(infectedIds.Span[i]);
if (player != null)
{
player.IsImpostor = true;
// player.IsImpostor = true;
}
}
@@ -479,7 +579,7 @@ namespace Impostor.Server.Net.Inner.Objects
if (sender.IsOwner(this))
{
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == color))
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == color))
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client sent a color that is already used"))
{
@@ -497,7 +597,7 @@ namespace Impostor.Server.Net.Inner.Objects
var expected = RequestedColorId.Dequeue();
while (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == expected))
while (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == expected))
{
expected = (ColorType)(((byte)expected + 1) % ColorsCount);
}
@@ -510,45 +610,62 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
PlayerInfo.Color = color;
PlayerInfo.CurrentOutfit.Color = color;
return true;
}
private async ValueTask<bool> HandleSetHat(ClientPlayer sender, HatType hat)
private async ValueTask<bool> HandleSetHat(ClientPlayer sender, string hat)
{
if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetHat, "Client tried to change hat while not in lobby"))
{
return false;
}
PlayerInfo.Hat = hat;
PlayerInfo.CurrentOutfit.HatId = hat;
return true;
}
private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, SkinType skin)
private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, string skin)
{
if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, "Client tried to change skin while not in lobby"))
{
return false;
}
PlayerInfo.Skin = skin;
PlayerInfo.CurrentOutfit.SkinId = skin;
return true;
}
private async ValueTask<bool> HandleCheckMurder(ClientPlayer sender, IInnerPlayerControl? target)
{
if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
{
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, "Client tried to murder too fast"))
{
return false;
}
}
PlayerInfo.LastMurder = _dateTimeProvider.UtcNow - TimeSpan.FromMilliseconds(sender.Client.Connection.AveragePing);
if (target == null || target.PlayerInfo.IsImpostor)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, "Client tried to murder invalid target"))
{
return false;
}
}
IsMurdering = target;
return true;
}
private async ValueTask<bool> 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;
}
}
if (target == null || target.PlayerInfo.IsImpostor)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder invalid target"))
@@ -557,7 +674,14 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
PlayerInfo.LastMurder = _dateTimeProvider.UtcNow - TimeSpan.FromMilliseconds(sender.Client.Connection.AveragePing);
// If the host is also the impostor that committed the murder, CheckMurder is actually sent *after* the MurderPlayer RPC
if (sender.Character != this && target != IsMurdering)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Host tried to murder incorrect target"))
{
return false;
}
}
if (target != null && !target.PlayerInfo.IsDead)
{
@@ -565,6 +689,8 @@ namespace Impostor.Server.Net.Inner.Objects
await _eventManager.CallAsync(new PlayerMurderEvent(Game, sender, this, target));
}
IsMurdering = null;
return true;
}
@@ -582,14 +708,14 @@ namespace Impostor.Server.Net.Inner.Objects
await _eventManager.CallAsync(new PlayerStartMeetingEvent(Game, Game.GetClientPlayer(this.OwnerId)!, this, deadPlayer));
}
private async ValueTask<bool> HandleSetPet(ClientPlayer sender, PetType pet)
private async ValueTask<bool> HandleSetPet(ClientPlayer sender, string pet)
{
if (Game.GameState == GameStates.Started && await sender.Client.ReportCheatAsync(RpcCalls.SetPet, "Client tried to change pet while not in lobby"))
{
return false;
}
PlayerInfo.Pet = pet;
PlayerInfo.CurrentOutfit.PetId = pet;
return true;
}
@@ -21,17 +21,22 @@ namespace Impostor.Server.Net.Inner.Objects
public string PlayerName { get; internal set; } = string.Empty;
public ColorType Color { get; internal set; } = (ColorType)(-1);
public Dictionary<PlayerOutfitType, PlayerOutfit> Outfits { get; } = new()
{
[PlayerOutfitType.Default] = new PlayerOutfit(),
};
public HatType Hat { get; internal set; }
public PlayerOutfitType CurrentOutfitType { get; set; } = PlayerOutfitType.Default;
public PetType Pet { get; internal set; }
public PlayerOutfit CurrentOutfit => Outfits[CurrentOutfitType];
public SkinType Skin { get; internal set; }
public RoleTypes RoleType { get; internal set; }
public bool Disconnected { get; internal set; }
public bool IsImpostor { get; internal set; }
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter;
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Engineer;
public bool IsDead { get; internal set; }
@@ -41,6 +46,8 @@ namespace Impostor.Server.Net.Inner.Objects
public DateTimeOffset LastMurder { get; set; }
public uint PlayerLevel { get; internal set; }
public bool CanMurder(IGame game, IDateTimeProvider dateTimeProvider)
{
if (!IsImpostor)
@@ -48,7 +55,12 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown;
// In 2021.11.9 the Guardian Angel was created, which was unfortunately implemented in such a way that
// it is hard to determine accurately whether a kill was prevented or not. When a kill was prevented,
// the impostor has a cooldown of half the usual duration. As a workaround we always assume the kill was
// prevented and the impostor only has half of its cooldown.
// FIXME when the base game improved their implementation.
return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown / 2;
}
public void Serialize(IMessageWriter writer)
@@ -58,22 +70,29 @@ namespace Impostor.Server.Net.Inner.Objects
public void Deserialize(IMessageReader reader)
{
PlayerName = reader.ReadString();
Color = (ColorType)reader.ReadPackedInt32();
Hat = (HatType)reader.ReadPackedUInt32();
Pet = (PetType)reader.ReadPackedUInt32();
Skin = (SkinType)reader.ReadPackedUInt32();
Outfits.Clear();
var b = reader.ReadByte();
for (var i = 0; i < b; i++)
{
var key = (PlayerOutfitType)reader.ReadByte();
Outfits[key] = new PlayerOutfit();
Outfits[key].Deserialize(reader);
}
PlayerLevel = reader.ReadPackedUInt32();
var flag = reader.ReadByte();
Disconnected = (flag & 1) > 0;
IsImpostor = (flag & 2) > 0;
IsDead = (flag & 4) > 0;
Disconnected = (flag & 1) != 0;
IsDead = (flag & 4) != 0;
var roleType = (RoleTypes)reader.ReadUInt16();
RoleType = roleType;
var taskCount = reader.ReadByte();
if (Tasks.Count != taskCount)
if (Tasks.Count < taskCount)
{
Tasks = new List<InnerGameData.TaskInfo>(taskCount);
taskCount = (byte)Tasks.Count;
}
for (var i = 0; i < taskCount; i++)
@@ -8,7 +8,6 @@ using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.S2C;
using Impostor.Hazel;
using Impostor.Server.Config;
using Impostor.Server.Events.Client;
@@ -22,7 +21,10 @@ namespace Impostor.Server.Net.Manager
// NOTE: when updating this array, keep the versions ordered from old to new, otherwise the version compare logic doesn't work properly
private static readonly int[] SupportedVersions =
{
GameVersion.GetVersion(2021, 6, 30), // 2021.6.30
GameVersion.GetVersion(2021, 11, 9), // 2021.11.9
GameVersion.GetVersion(2021, 12, 14), // 2021.12.14
GameVersion.GetVersion(2022, 1, 10), // 2021.2.23
GameVersion.GetVersion(2022, 2, 2), // 2022.3.29
};
private readonly ILogger<ClientManager> _logger;
@@ -65,10 +67,10 @@ namespace Impostor.Server.Net.Manager
return clientId;
}
public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode)
public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion, Language language, QuickChatModes chatMode, PlatformSpecificData? platformSpecificData)
{
var versionCompare = CompareVersion(clientVersion);
if (versionCompare != VersionCompareResult.Compatible)
if (versionCompare != VersionCompareResult.Compatible || platformSpecificData == null)
{
GameVersion.ParseVersion(clientVersion, out var year, out var month, out var day, out var revision);
_logger.LogTrace("Client connected using unsupported version: {clientVersion} ({version})", clientVersion, $"{year}.{month}.{day}{(revision == 0 ? string.Empty : "." + revision)}");
@@ -83,29 +85,23 @@ namespace Impostor.Server.Net.Manager
_ => throw new ArgumentOutOfRangeException(),
};
Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, message);
await connection.SendAsync(packet);
await connection.CustomDisconnectAsync(DisconnectReason.Custom, message);
return;
}
if (name.Length > 10)
{
using var packet = MessageWriter.Get(MessageType.Reliable);
Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.UsernameLength);
await connection.SendAsync(packet);
await connection.CustomDisconnectAsync(DisconnectReason.Custom, DisconnectMessages.UsernameLength);
return;
}
if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed))
if (string.IsNullOrWhiteSpace(name))
{
using var packet = MessageWriter.Get(MessageType.Reliable);
Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.UsernameIllegalCharacters);
await connection.SendAsync(packet);
await connection.CustomDisconnectAsync(DisconnectReason.Custom, DisconnectMessages.UsernameIllegalCharacters);
return;
}
var client = _clientFactory.Create(connection, name, clientVersion, language, chatMode);
var client = _clientFactory.Create(connection, name, clientVersion, language, chatMode, platformSpecificData);
var id = NextId();
client.Id = id;
+2 -2
View File
@@ -62,14 +62,14 @@ namespace Impostor.Server.Net
private async ValueTask OnNewConnection(NewConnectionEventArgs e)
{
// Handshake.
HandshakeC2S.Deserialize(e.HandshakeData, out var clientVersion, out var name, out _, out var language, out var chatMode);
HandshakeC2S.Deserialize(e.HandshakeData, out var clientVersion, out var name, out var language, out var chatMode, out var platformSpecificData);
var connection = new HazelConnection(e.Connection, _connectionLogger);
await _eventManager.CallAsync(new ClientConnectionEvent(connection, e.HandshakeData));
// Register client
await _clientManager.RegisterConnectionAsync(connection, name, clientVersion, language, chatMode);
await _clientManager.RegisterConnectionAsync(connection, name, clientVersion, language, chatMode, platformSpecificData);
}
}
}
@@ -24,11 +24,12 @@ namespace Impostor.Server.Net.Redirector
int gameVersion,
Language language,
QuickChatModes chatMode,
PlatformSpecificData platformSpecificData,
HazelConnection connection,
ClientManager clientManager,
INodeProvider nodeProvider,
INodeLocator nodeLocator)
: base(name, gameVersion, language, chatMode, connection)
: base(name, gameVersion, language, chatMode, platformSpecificData, connection)
{
_clientManager = clientManager;
_nodeProvider = nodeProvider;
@@ -55,27 +56,25 @@ namespace Impostor.Server.Net.Redirector
{
Message01JoinGameC2S.Deserialize(reader, out var gameCode);
using var packet = MessageWriter.Get(MessageType.Reliable);
var endpoint = await _nodeLocator.FindAsync(gameCode);
if (endpoint == null)
{
Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.GameMissing);
await DisconnectAsync(DisconnectReason.GameMissing);
}
else
{
using var packet = MessageWriter.Get(MessageType.Reliable);
Message13RedirectS2C.Serialize(packet, false, endpoint);
await Connection.SendAsync(packet);
}
await Connection.SendAsync(packet);
break;
}
case MessageFlags.GetGameListV2:
{
// TODO: Implement.
using var packet = MessageWriter.Get(MessageType.Reliable);
Message01JoinGameS2C.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.NotImplemented);
await Connection.SendAsync(packet);
await DisconnectAsync(DisconnectReason.Custom, DisconnectMessages.NotImplemented);
break;
}
+2 -2
View File
@@ -187,7 +187,7 @@ namespace Impostor.Server.Net.State
continue;
}
_logger.LogError("Couldn't find spawnable object {0}.", objectId);
_logger.LogWarning("Couldn't find spawnable object {0}.", objectId);
break;
}
@@ -382,7 +382,7 @@ namespace Impostor.Server.Net.State
case InnerPlayerControl control:
{
if (GameState != GameStates.Started)
if (GameState != GameStates.Started && GameState != GameStates.Starting)
{
GameNet.GameData?.RemovePlayer(control);
}
@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
@@ -78,12 +78,13 @@ namespace Impostor.Server.Net.State
private void WriteJoinedGameMessage(IMessageWriter message, bool clear, IClientPlayer player)
{
var playerIds = _players
var players = _players
.Where(x => x.Value != player)
.Select(x => x.Key)
.Select(x => x.Value)
.ToArray();
Message07JoinedGameS2C.Serialize(message, clear, Code, player.Client.Id, HostId, playerIds);
// TODO: clean up post20220202 when versions before it are no longer supported.
Message07JoinedGameS2C.Serialize(message, clear, Code, player.Client.Id, HostId, players, player.Client.GameVersion >= GameVersion.GetVersion(2022, 2, 2));
}
private void WriteAlterGameMessage(IMessageWriter message, bool clear, bool isPublic)
+9 -4
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -103,7 +103,11 @@ namespace Impostor.Server.Net.State
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);
if (GameNet.ShipStatus != null)
{
await player.Character!.NetworkTransform.SetPositionAsync(player, GameNet.ShipStatus.GetSpawnLocation(player.Character, PlayerCount, true), Vector2.Zero);
}
}
GameState = GameStates.Started;
@@ -114,7 +118,7 @@ namespace Impostor.Server.Net.State
private ValueTask BroadcastJoinMessage(IMessageWriter message, bool clear, ClientPlayer player)
{
Message01JoinGameS2C.SerializeJoin(message, clear, Code, player.Client.Id, HostId);
Message01JoinGameS2C.SerializeJoin(message, clear, Code, player, HostId);
return SendToAllExceptAsync(message, player.Client.Id);
}
@@ -123,7 +127,8 @@ namespace Impostor.Server.Net.State
{
return Players
.Where(filter)
.Select(p => p.Client.Connection)!;
.Select(p => p.Client.Connection)
.Where(c => c != null && c.IsConnected)!;
}
}
}
+53 -44
View File
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Impostor.Api.Events.Managers;
using Impostor.Api.Games;
using Impostor.Api.Games.Managers;
@@ -26,6 +28,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.ObjectPool;
using Serilog;
using Serilog.Events;
using Serilog.Settings.Configuration;
namespace Impostor.Server
{
@@ -33,31 +36,9 @@ namespace Impostor.Server
{
private static int Main(string[] args)
{
#if DEBUG
var logLevel = LogEventLevel.Debug;
#else
var logLevel = LogEventLevel.Information;
#endif
if (args.Contains("--verbose"))
{
logLevel = LogEventLevel.Verbose;
}
else if (args.Contains("--errors-only"))
{
logLevel = LogEventLevel.Error;
}
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(logLevel)
#if DEBUG
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
#else
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
#endif
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
.CreateBootstrapLogger();
try
{
@@ -116,14 +97,6 @@ namespace Impostor.Server
.GetSection(ServerRedirectorConfig.Section)
.Get<ServerRedirectorConfig>() ?? new ServerRedirectorConfig();
var announcementsServer = host.Configuration
.GetSection(AnnouncementsServerConfig.Section)
.Get<AnnouncementsServerConfig>() ?? new AnnouncementsServerConfig();
var authServer = host.Configuration
.GetSection(AuthServerConfig.Section)
.Get<AuthServerConfig>() ?? new AuthServerConfig();
services.AddSingleton<ServerEnvironment>();
services.AddSingleton<IServerEnvironment>(p => p.GetRequiredService<ServerEnvironment>());
services.AddSingleton<IDateTimeProvider, RealDateTimeProvider>();
@@ -131,8 +104,6 @@ namespace Impostor.Server
services.Configure<DebugConfig>(host.Configuration.GetSection(DebugConfig.Section));
services.Configure<AntiCheatConfig>(host.Configuration.GetSection(AntiCheatConfig.Section));
services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
services.Configure<AnnouncementsServerConfig>(host.Configuration.GetSection(AnnouncementsServerConfig.Section));
services.Configure<AuthServerConfig>(host.Configuration.GetSection(AuthServerConfig.Section));
services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
if (redirector.Enabled)
@@ -218,18 +189,56 @@ namespace Impostor.Server
services.AddSingleton<IEventManager, EventManager>();
services.AddSingleton<Matchmaker>();
services.AddHostedService<MatchmakerService>();
if (announcementsServer.Enabled)
{
services.AddHostedService<AnnouncementsService>();
}
if (authServer.Enabled)
{
services.AddHostedService<AuthService>();
}
})
.UseSerilog()
.UseSerilog((context, loggerConfiguration) =>
{
#if DEBUG
var logLevel = LogEventLevel.Debug;
#else
var logLevel = LogEventLevel.Information;
#endif
if (args.Contains("--verbose"))
{
logLevel = LogEventLevel.Verbose;
}
else if (args.Contains("--errors-only"))
{
logLevel = LogEventLevel.Error;
}
static Assembly? LoadSerilogAssembly(AssemblyLoadContext loadContext, AssemblyName name)
{
var paths = new[] { AppDomain.CurrentDomain.BaseDirectory, Directory.GetCurrentDirectory() };
foreach (var path in paths)
{
try
{
return loadContext.LoadFromAssemblyPath(Path.Combine(path, name.Name + ".dll"));
}
catch (FileNotFoundException)
{
}
}
return null;
}
AssemblyLoadContext.Default.Resolving += LoadSerilogAssembly;
loggerConfiguration
.MinimumLevel.Is(logLevel)
#if DEBUG
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
#else
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
#endif
.Enrich.FromLogContext()
.WriteTo.Console()
.ReadFrom.Configuration(context.Configuration, ConfigurationAssemblySource.AlwaysScanDllFiles);
AssemblyLoadContext.Default.Resolving -= LoadSerilogAssembly;
})
.UseConsoleLifetime()
.UsePluginLoader(pluginConfig);
}
@@ -18,8 +18,8 @@ namespace Impostor.Server.Recorder
private bool _createdGame;
private bool _recordAfter;
public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, GameManager gameManager, string name, int gameVersion, Language language, QuickChatModes chatMode, HazelConnection connection, PacketRecorder recorder)
: base(logger, antiCheatOptions, clientManager, gameManager, customMessageManager, name, gameVersion, language, chatMode, connection)
public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, ICustomMessageManager<ICustomRootMessage> customMessageManager, GameManager gameManager, string name, int gameVersion, Language language, QuickChatModes chatMode, PlatformSpecificData platformSpecificData, HazelConnection connection, PacketRecorder recorder)
: base(logger, antiCheatOptions, clientManager, gameManager, customMessageManager, name, gameVersion, language, chatMode, platformSpecificData, connection)
{
_recorder = recorder;
_isFirst = true;
-5
View File
@@ -5,11 +5,6 @@
"ListenIp": "0.0.0.0",
"ListenPort": 22023
},
"AnnouncementsServer": {
"Enabled": true,
"ListenIp": "0.0.0.0",
"ListenPort": 22024
},
"AntiCheat": {
"Enabled": true,
"BanIpFromGame": true
+6 -4
View File
@@ -1,16 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.reporters" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3">
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -2,11 +2,10 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Pcap.Net.x64" Version="1.0.4.1">
<NoWarn>NU1701</NoWarn>
</PackageReference>
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more