]> git.deb.at Git - rhonda/impostor.git/commitdiff
Merge Impostor.Http into core (#533)
authorjs6pak <me@6pak.dev>
Sun, 17 Dec 2023 19:19:27 +0000 (20:19 +0100)
committerGitHub <noreply@github.com>
Sun, 17 Dec 2023 19:19:27 +0000 (20:19 +0100)
26 files changed:
Dockerfile
src/Impostor.Api/Config/DisconnectMessages.cs
src/Impostor.Api/Config/HttpServerConfig.cs [new file with mode: 0644]
src/Impostor.Api/Http/IListingFilter.cs [new file with mode: 0644]
src/Impostor.Api/Impostor.Api.csproj
src/Impostor.Api/Plugins/IPluginHttpStartup.cs [new file with mode: 0644]
src/Impostor.Api/packages.lock.json
src/Impostor.Benchmarks/packages.lock.json
src/Impostor.Client.App/packages.lock.json
src/Impostor.Client/packages.lock.json
src/Impostor.Plugins.Debugger/packages.lock.json
src/Impostor.Plugins.Example/packages.lock.json
src/Impostor.Server/Http/GamesController.cs [new file with mode: 0644]
src/Impostor.Server/Http/ListingManager.cs [new file with mode: 0644]
src/Impostor.Server/Http/TokenController.cs [new file with mode: 0644]
src/Impostor.Server/Impostor.Server.csproj
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/Manager/GameManager.cs
src/Impostor.Server/Plugins/PluginLoader.cs
src/Impostor.Server/Plugins/PluginLoaderService.cs
src/Impostor.Server/Program.cs
src/Impostor.Server/config-full.json
src/Impostor.Server/config.json
src/Impostor.Server/packages.lock.json
src/Impostor.Tests/packages.lock.json
src/Impostor.Tools.ServerReplay/packages.lock.json

index 7490b1c85cbb5436407cb0d3ea578e9f846a8247..9792a1ac9d1bc0091a89f39f1414fe0d25d3677e 100644 (file)
@@ -40,14 +40,10 @@ FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:7.0
 WORKDIR /app
 COPY --from=build /app ./
 
-# Add Impostor.Http as a default built-in plugin
-ADD https://github.com/Impostor/Impostor.Http/releases/download/v0.5.0/Impostor.Http.dll /app/builtin-plugins/
-# Make it listen to 0.0.0.0 to expose it to the outside world.
-ENV IMPOSTOR_HTTP_HttpServer__ListenIp=0.0.0.0
+# Make the HttpServer listen to 0.0.0.0 to expose it to the outside world.
+ENV IMPOSTOR_HttpServer__ListenIp=0.0.0.0
 # Override ASPNETCORE_URLS to stop warning.
 ENV ASPNETCORE_URLS=
-# Enable the built-in plugin folder. Use a high number to prevent conflicts with existing configurations
-ENV IMPOSTOR_PluginLoader__Paths__76=/app/builtin-plugins
 
 EXPOSE 22023/tcp 22023/udp
 ENTRYPOINT ["./Impostor.Server"]
index 5a4055983fb8a82add85f978d952ad0f1a9833c5..e722f32143f151145c142ed4876da081b9d6a4cf 100644 (file)
         public const string VersionServerTooOld = "Your client is too new, please update your Impostor server to play.";
 
         public const string VersionUnsupported = "Your client version is unsupported, please update your Game and/or Impostor server.";
+
+        private const string UpgradingDocsLink = "https://github.com/Impostor/Impostor/blob/master/docs/Upgrading.md";
+
+        public const string UdpMatchmakingUnsupported = $"""
+                                                        Sorry, UDP Matchmaking is no longer supported.
+                                                        See <link={UpgradingDocsLink}#impostor-190>Impostor documentation</link> on how to migrate to HTTP Matchmaking
+                                                        """;
     }
 }
diff --git a/src/Impostor.Api/Config/HttpServerConfig.cs b/src/Impostor.Api/Config/HttpServerConfig.cs
new file mode 100644 (file)
index 0000000..fd3cda2
--- /dev/null
@@ -0,0 +1,27 @@
+namespace Impostor.Api.Config;
+
+/// <summary>
+/// Configuration for HttpServer.
+/// </summary>
+public class HttpServerConfig
+{
+    /// <summary>
+    /// Gets the name of this config section.
+    /// </summary>
+    public const string Section = "HttpServer";
+
+    public bool Enabled { get; set; } = true;
+
+    /// <summary>
+    /// Gets or sets the IP address the HTTP Matchmaking server will listen on.
+    /// </summary>
+    /// Use "127.0.0.1" if you are running behind a reverse proxy or just testing locally.
+    /// Use "0.0.0.0" if you are directly exposing this server to the internet (not recommended).
+    public string ListenIp { get; set; } = "127.0.0.1";
+
+    /// <summary>
+    /// Gets or sets the port the HTTP Matchmaking server will listen on.
+    /// </summary>
+    /// For port forwarding purposes, this is a TCP port.
+    public ushort ListenPort { get; set; } = 22023;
+}
diff --git a/src/Impostor.Api/Http/IListingFilter.cs b/src/Impostor.Api/Http/IListingFilter.cs
new file mode 100644 (file)
index 0000000..ce3768a
--- /dev/null
@@ -0,0 +1,18 @@
+using System;
+using Impostor.Api.Games;
+using Microsoft.AspNetCore.Http;
+
+namespace Impostor.Api.Http;
+
+/// <summary>
+/// Register a method to filter listings on.
+/// </summary>
+public interface IListingFilter
+{
+    /// <summary>
+    /// Return a filter to filter listings on.
+    /// </summary>
+    /// <param name="context">HTTP Context of this request.</param>
+    /// <returns>A function that looks at a game and returns true iff the connecting player is compatible with this game.</returns>
+    Func<IGame, bool> GetFilter(HttpContext context);
+}
index 0b3ab694f6d948e92831f2d0bc5f948bf636ffc3..29025dc941ab5dcc54489f6331f8e13324cb852e 100644 (file)
@@ -25,6 +25,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
diff --git a/src/Impostor.Api/Plugins/IPluginHttpStartup.cs b/src/Impostor.Api/Plugins/IPluginHttpStartup.cs
new file mode 100644 (file)
index 0000000..bdb7dd6
--- /dev/null
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace Impostor.Api.Plugins;
+
+public interface IPluginHttpStartup : IPluginStartup
+{
+    void ConfigureWebApplication(IApplicationBuilder builder);
+}
index d7113bf5a196eb7e9b0c13f5329954c159bae8b9..0cdaf8737d8462da5f511493fe0a759ff8a9828c 100644 (file)
@@ -8,6 +8,16 @@
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Direct",
+        "requested": "[2.2.0, )",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
       "Microsoft.Extensions.Hosting.Abstractions": {
         "type": "Direct",
         "requested": "[7.0.0, )",
           "StyleCop.Analyzers.Unstable": "1.2.0.435"
         }
       },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration.Abstractions": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "type": "Transitive",
         "resolved": "1.2.0.435",
         "contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
+      },
+      "System.Text.Encodings.Web": {
+        "type": "Transitive",
+        "resolved": "4.5.0",
+        "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
       }
     }
   }
index 2a0b8292e3c940f6188819ffb9626dcadcc2edd6..bd54502e6b8fae66b0d98b7f24236731e99c0e5d 100644 (file)
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Bcl.AsyncInterfaces": {
         "type": "Transitive",
         "resolved": "1.1.0",
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
       "impostor.server": {
         "type": "Project",
         "dependencies": {
-          "Impostor.Api": "[1.7.3-dev, )",
+          "Impostor.Api": "[1.8.4-dev, )",
           "Impostor.Hazel": "[1.0.0, )",
           "Microsoft.Extensions.FileSystemGlobbing": "[7.0.0, )",
           "Microsoft.Extensions.Hosting": "[7.0.0, )",
index 98ba9b6bdeddaac9e2cdcde7d194e53cba0ebdce..8b24be1900b6423eeda0daccab95972c255903a9 100644 (file)
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration.Abstractions": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "resolved": "2.12.0",
         "contentHash": "xaiJLIdu6rYMKfQMYUZgTy8YK7SMZjB4Yk50C/u//Z4OsvxkUfSPJy4nknfvwAC34yr13q7kcyh4grbwhSxyZg=="
       },
+      "System.Text.Encodings.Web": {
+        "type": "Transitive",
+        "resolved": "4.5.0",
+        "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
+      },
       "impostor.api": {
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
       "impostor.client": {
         "type": "Project",
         "dependencies": {
-          "Impostor.Api": "[1.7.3-dev, )",
+          "Impostor.Api": "[1.8.4-dev, )",
           "Impostor.Hazel": "[1.0.0, )"
         }
       }
index 166d277db84ec19378142dae65043d15b59e504d..0e4e032b8bc41e18f744e4a1768b6b9d44c8360b 100644 (file)
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration.Abstractions": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "resolved": "2.12.0",
         "contentHash": "xaiJLIdu6rYMKfQMYUZgTy8YK7SMZjB4Yk50C/u//Z4OsvxkUfSPJy4nknfvwAC34yr13q7kcyh4grbwhSxyZg=="
       },
+      "System.Text.Encodings.Web": {
+        "type": "Transitive",
+        "resolved": "4.5.0",
+        "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
+      },
       "impostor.api": {
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
index c18e493c828539214e084ca22076a12c5fbe1033..a88d4eaef8ebe0e7e8fda1da2a25e1601f0b4465 100644 (file)
@@ -7,6 +7,23 @@
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration.Abstractions": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "resolved": "7.0.0",
         "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q=="
       },
+      "System.Text.Encodings.Web": {
+        "type": "Transitive",
+        "resolved": "4.5.0",
+        "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
+      },
       "impostor.api": {
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
index c18e493c828539214e084ca22076a12c5fbe1033..a88d4eaef8ebe0e7e8fda1da2a25e1601f0b4465 100644 (file)
@@ -7,6 +7,23 @@
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration.Abstractions": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "resolved": "7.0.0",
         "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q=="
       },
+      "System.Text.Encodings.Web": {
+        "type": "Transitive",
+        "resolved": "4.5.0",
+        "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
+      },
       "impostor.api": {
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
diff --git a/src/Impostor.Server/Http/GamesController.cs b/src/Impostor.Server/Http/GamesController.cs
new file mode 100644 (file)
index 0000000..e64a498
--- /dev/null
@@ -0,0 +1,213 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Impostor.Api.Config;
+using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Innersloth;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Http;
+
+/// <summary>
+/// This controller has method to get a list of public games, join by game and create new games.
+/// </summary>
+[Route("/api/games")]
+[ApiController]
+public sealed class GamesController : ControllerBase
+{
+    private readonly IGameManager _gameManager;
+    private readonly ListingManager _listingManager;
+    private readonly HostServer _hostServer;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="GamesController"/> class.
+    /// </summary>
+    /// <param name="gameManager">GameManager containing a list of games.</param>
+    /// <param name="listingManager">ListingManager responsible for filtering.</param>
+    /// <param name="serverConfig">Impostor configuration section containing the public ip address of this server.</param>
+    public GamesController(IGameManager gameManager, ListingManager listingManager, IOptions<ServerConfig> serverConfig)
+    {
+        _gameManager = gameManager;
+        _listingManager = listingManager;
+        var config = serverConfig.Value;
+        _hostServer = HostServer.From(IPAddress.Parse(config.ResolvePublicIp()), config.PublicPort);
+    }
+
+    /// <summary>
+    /// Get a list of active games.
+    /// </summary>
+    /// <param name="mapId">Maps that are requested.</param>
+    /// <param name="lang">Preferred chat language.</param>
+    /// <param name="numImpostors">Amount of impostors. 0 is any.</param>
+    /// <param name="authorization">Authorization header containing the matchmaking token.</param>
+    /// <returns>An array of game listings.</returns>
+    [HttpGet]
+    public IActionResult Index(int mapId, GameKeywords lang, int numImpostors, [FromHeader] AuthenticationHeaderValue authorization)
+    {
+        if (authorization.Scheme != "Bearer" || authorization.Parameter == null)
+        {
+            return BadRequest();
+        }
+
+        var token = JsonSerializer.Deserialize<TokenController.Token>(Convert.FromBase64String(authorization.Parameter));
+        if (token == null)
+        {
+            return BadRequest();
+        }
+
+        var clientVersion = new GameVersion(token.Content.ClientVersion);
+
+        var listings = _listingManager.FindListings(HttpContext, mapId, numImpostors, lang, clientVersion);
+        return Ok(listings.Select(GameListing.From));
+    }
+
+    /// <summary>
+    /// Get the address a certain game is hosted at.
+    /// </summary>
+    /// <param name="gameId">The id of the game that should be retrieved.</param>
+    /// <returns>The server this game is hosted on.</returns>
+    [HttpPost]
+    public IActionResult Post(int gameId)
+    {
+        var code = new GameCode(gameId);
+        var game = _gameManager.Find(code);
+
+        // If the game was not found, print an error message.
+        if (game == null)
+        {
+            return NotFound(new MatchmakerResponse(new MatchmakerError(DisconnectReason.GameNotFound)));
+        }
+
+        return Ok(HostServer.From(game.PublicIp));
+    }
+
+    /// <summary>
+    /// Get the address to host a new game on.
+    /// </summary>
+    /// <returns>The address of this server.</returns>
+    [HttpPut]
+    public IActionResult Put()
+    {
+        return Ok(_hostServer);
+    }
+
+    private static uint ConvertAddressToNumber(IPAddress address)
+    {
+#pragma warning disable CS0618 // Among Us only supports IPv4
+        return (uint)address.Address;
+#pragma warning restore CS0618
+    }
+
+    private class HostServer
+    {
+        [JsonPropertyName("Ip")]
+        public required long Ip { get; init; }
+
+        [JsonPropertyName("Port")]
+        public required ushort Port { get; init; }
+
+        public static HostServer From(IPAddress ipAddress, ushort port)
+        {
+            return new HostServer
+            {
+                Ip = ConvertAddressToNumber(ipAddress),
+                Port = port,
+            };
+        }
+
+        public static HostServer From(IPEndPoint endPoint)
+        {
+            return From(endPoint.Address, (ushort)endPoint.Port);
+        }
+    }
+
+    private class MatchmakerResponse
+    {
+        [SetsRequiredMembers]
+        public MatchmakerResponse(MatchmakerError error)
+        {
+            Errors = new[] { error };
+        }
+
+        [JsonPropertyName("Errors")]
+        public required MatchmakerError[] Errors { get; init; }
+    }
+
+    private class MatchmakerError
+    {
+        [SetsRequiredMembers]
+        public MatchmakerError(DisconnectReason reason)
+        {
+            Reason = reason;
+        }
+
+        [JsonPropertyName("Reason")]
+        public required DisconnectReason Reason { get; init; }
+    }
+
+    private class GameListing
+    {
+        [JsonPropertyName("IP")]
+        public required uint Ip { get; init; }
+
+        [JsonPropertyName("Port")]
+        public required ushort Port { get; init; }
+
+        [JsonPropertyName("GameId")]
+        public required int GameId { get; init; }
+
+        [JsonPropertyName("PlayerCount")]
+        public required int PlayerCount { get; init; }
+
+        [JsonPropertyName("HostName")]
+        public required string HostName { get; init; }
+
+        [JsonPropertyName("HostPlatformName")]
+        public required string HostPlatformName { get; init; }
+
+        [JsonPropertyName("Platform")]
+        public required Platforms Platform { get; init; }
+
+        [JsonPropertyName("Age")]
+        public required int Age { get; init; }
+
+        [JsonPropertyName("MaxPlayers")]
+        public required int MaxPlayers { get; init; }
+
+        [JsonPropertyName("NumImpostors")]
+        public required int NumImpostors { get; init; }
+
+        [JsonPropertyName("MapId")]
+        public required MapTypes MapId { get; init; }
+
+        [JsonPropertyName("Language")]
+        public required GameKeywords Language { get; init; }
+
+        public static GameListing From(IGame game)
+        {
+            var platform = game.Host?.Client.PlatformSpecificData;
+
+            return new GameListing
+            {
+                Ip = ConvertAddressToNumber(game.PublicIp.Address),
+                Port = (ushort)game.PublicIp.Port,
+                GameId = game.Code,
+                PlayerCount = game.PlayerCount,
+                HostName = game.DisplayName ?? game.Host?.Client.Name ?? "Unknown host",
+                HostPlatformName = platform?.PlatformName ?? string.Empty,
+                Platform = platform?.Platform ?? Platforms.Unknown,
+                Age = 0,
+                MaxPlayers = game.Options.MaxPlayers,
+                NumImpostors = game.Options.NumImpostors,
+                MapId = game.Options.Map,
+                Language = game.Options.Keywords,
+            };
+        }
+    }
+}
diff --git a/src/Impostor.Server/Http/ListingManager.cs b/src/Impostor.Server/Http/ListingManager.cs
new file mode 100644 (file)
index 0000000..27867d7
--- /dev/null
@@ -0,0 +1,125 @@
+using System.Collections.Generic;
+using System.Linq;
+using Impostor.Api.Config;
+using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Http;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Manager;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Http;
+
+/// <summary>
+/// Perform game listing filtering.
+/// </summary>
+public sealed class ListingManager
+{
+    private readonly IGameManager _gameManager;
+    private readonly IEnumerable<IListingFilter> _listingFilters;
+    private readonly ICompatibilityManager _compatibilityManager;
+    private readonly CompatibilityConfig _compatibilityConfig;
+
+    public ListingManager(IGameManager gameManager, IEnumerable<IListingFilter> listingFilters, ICompatibilityManager compatibilityManager, IOptions<CompatibilityConfig> compatibilityConfig)
+    {
+        _gameManager = gameManager;
+        _listingFilters = listingFilters;
+        _compatibilityManager = compatibilityManager;
+        _compatibilityConfig = compatibilityConfig.Value;
+    }
+
+    /// <summary>
+    /// Find listings that match the requested settings.
+    /// </summary>
+    /// <param name="ctx">The context of this http request.</param>
+    /// <param name="map">The selected maps.</param>
+    /// <param name="impostorCount">The amount of impostors. 0 is any.</param>
+    /// <param name="language">Chat language of the game.</param>
+    /// <param name="gameVersion">Game version of the client.</param>
+    /// <param name="maxListings">Maximum amount of games to return.</param>
+    /// <returns>Listings that match the required criteria.</returns>
+    public IEnumerable<IGame> FindListings(HttpContext ctx, int map, int impostorCount, GameKeywords language, GameVersion gameVersion, int maxListings = 10)
+    {
+        var resultCount = 0;
+
+        var filters = _listingFilters.Select(f => f.GetFilter(ctx)).ToArray();
+
+        var compatibleGames = new List<IGame>();
+
+        // We want to add 2 types of games
+        // 1. Desireable games that the player wants to play (right language, right map, desired impostor amount)
+        // 2. Failing that, display compatible games the player could join (public games with spots available)
+
+        // .Where filters out games that can't be joined.
+        foreach (var game in this._gameManager.Games)
+        {
+            if (!game.IsPublic || game.GameState != GameStates.NotStarted || game.PlayerCount >= game.Options.MaxPlayers)
+            {
+                continue;
+            }
+
+            if (!_compatibilityConfig.AllowVersionMixing &&
+                game.Host != null &&
+                _compatibilityManager.CanJoinGame(game.Host.Client.GameVersion, gameVersion) != GameJoinError.None)
+            {
+                continue;
+            }
+
+            if (!filters.All(filter => filter(game)))
+            {
+                continue;
+            }
+
+            if (IsGameDesired(game, map, impostorCount, language))
+            {
+                // Add to result immediately.
+                yield return game;
+
+                // Break out if we have enough.
+                if (++resultCount == maxListings)
+                {
+                    yield break;
+                }
+            }
+            else
+            {
+                // Add to result to add afterwards. Adding is pointless if we already have enough compatible games to fill the list
+                if (compatibleGames.Count < (maxListings - resultCount))
+                {
+                    compatibleGames.Add(game);
+                }
+            }
+        }
+
+        foreach (var game in compatibleGames)
+        {
+            yield return game;
+
+            if (++resultCount == maxListings)
+            {
+                yield break;
+            }
+        }
+    }
+
+    private static bool IsGameDesired(IGame game, int map, int impostorCount, GameKeywords language)
+    {
+        if ((map & (1 << (int)game.Options.Map)) == 0)
+        {
+            return false;
+        }
+
+        if (language != game.Options.Keywords)
+        {
+            return false;
+        }
+
+        if (impostorCount != 0 && game.Options.NumImpostors != impostorCount)
+        {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/Impostor.Server/Http/TokenController.cs b/src/Impostor.Server/Http/TokenController.cs
new file mode 100644 (file)
index 0000000..4345cb8
--- /dev/null
@@ -0,0 +1,85 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Impostor.Api.Innersloth;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Impostor.Server.Http;
+
+/// <summary>
+/// This controller has a method to get an auth token.
+/// </summary>
+[Route("/api/user")]
+[ApiController]
+public sealed class TokenController : ControllerBase
+{
+    /// <summary>
+    /// Get an authentication token.
+    /// </summary>
+    /// <param name="request">Token parameters that need to be put into the token.</param>
+    /// <returns>A bare minimum authentication token that the client will accept.</returns>
+    [HttpPost]
+    public IActionResult GetToken([FromBody] TokenRequest request)
+    {
+        var token = new Token
+        {
+            Content = new TokenPayload
+            {
+                ProductUserId = request.ProductUserId,
+                ClientVersion = request.ClientVersion,
+            },
+            Hash = "impostor_was_here",
+        };
+
+        // Wrap into a Base64 sandwich
+        var serialized = JsonSerializer.SerializeToUtf8Bytes(token);
+        return this.Ok(Convert.ToBase64String(serialized));
+    }
+
+    /// <summary>
+    /// Body of the token request endpoint.
+    /// </summary>
+    public class TokenRequest
+    {
+        [JsonPropertyName("Puid")]
+        public required string ProductUserId { get; init; }
+
+        [JsonPropertyName("Username")]
+        public required string Username { get; init; }
+
+        [JsonPropertyName("ClientVersion")]
+        public required int ClientVersion { get; init; }
+
+        [JsonPropertyName("Language")]
+        public required Language Language { get; init; }
+    }
+
+    /// <summary>
+    /// Token that is returned to the user with a "signature".
+    /// </summary>
+    public sealed class Token
+    {
+        [JsonPropertyName("Content")]
+        public required TokenPayload Content { get; init; }
+
+        [JsonPropertyName("Hash")]
+        public required string Hash { get; init; }
+    }
+
+    /// <summary>
+    /// Actual token contents.
+    /// </summary>
+    public sealed class TokenPayload
+    {
+        private static readonly DateTime DefaultExpiryDate = new(2012, 12, 21);
+
+        [JsonPropertyName("Puid")]
+        public required string ProductUserId { get; init; }
+
+        [JsonPropertyName("ClientVersion")]
+        public required int ClientVersion { get; init; }
+
+        [JsonPropertyName("ExpiresAt")]
+        public DateTime ExpiresAt { get; init; } = DefaultExpiryDate;
+    }
+}
index 6e708b0c4ee77bf81034c95aaeb823c54d6050c1..b16ea5320afc75d80ce5fab1c38b383d84b83b04 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
     <PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
     <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
-    <PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
+    <PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.507">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
   </ItemGroup>
 
-  <ItemGroup>
-    <Content Include="config.json">
-      <CopyToPublishDirectory>Always</CopyToPublishDirectory>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
-    </Content>
-    <Content Include="config.*.json">
-      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
-    </Content>
-    <Content Include="config-full.json">
-      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
-      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
-      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
-    </Content>
-  </ItemGroup>
-
 </Project>
index b2a928d9ca21d135de8a940a63923961be068ceb..0debdd58fb08cef7616387ba24c7d3640e81b731 100644 (file)
@@ -5,7 +5,6 @@ using Impostor.Api;
 using Impostor.Api.Config;
 using Impostor.Api.Games;
 using Impostor.Api.Innersloth;
-using Impostor.Api.Innersloth.GameOptions;
 using Impostor.Api.Net;
 using Impostor.Api.Net.Custom;
 using Impostor.Api.Net.Messages;
@@ -260,9 +259,8 @@ namespace Impostor.Server.Net
 
                 case MessageFlags.GetGameListV2:
                 {
-                    Message16GetGameListC2S.Deserialize(reader, out var options, out _, out _, out var filterOptions);
-                    await OnRequestGameListAsync(options, filterOptions);
-                    break;
+                    await DisconnectAsync(DisconnectReason.Custom, DisconnectMessages.UdpMatchmakingUnsupported);
+                    return;
                 }
 
                 case MessageFlags.SetActivePodType:
@@ -352,22 +350,6 @@ namespace Impostor.Server.Net
             return true;
         }
 
-        /// <summary>
-        ///     Triggered when the connected client requests the game listing.
-        /// </summary>
-        /// <param name="options">Options specific to the game mode. At this moment, the client can only specify the map, impostor count and chat language.</param>
-        /// <param name="filterOptions">Filter options not specific to the game mode.</param>
-        private ValueTask OnRequestGameListAsync(IGameOptions options, GameFilterOptions filterOptions)
-        {
-            using var message = MessageWriter.Get(MessageType.Reliable);
-
-            var games = _gameManager.FindListings((MapFlags)options.Map, options.NumImpostors, options.Keywords, this.GameVersion, filterOptions.FilterTags);
-
-            Message16GetGameListS2C.Serialize(message, games);
-
-            return Connection.SendAsync(message);
-        }
-
         /// <summary>
         ///     Triggered when the connected client requests the PlatformSpecificData.
         /// </summary>
index 40c8610ccf26d739021f9470804dab5dbad2da71..c8371d55512481454092039733ae439d95d5bd93 100644 (file)
@@ -61,56 +61,6 @@ namespace Impostor.Server.Net.Manager
             return game;
         }
 
-        public IEnumerable<Game> FindListings(
-            MapFlags map,
-            int impostorCount,
-            GameKeywords language,
-            GameVersion gameVersion,
-            HashSet<string> filterTags,
-            int count = 10)
-        {
-            var results = 0;
-
-            // Find games that have not started yet.
-            foreach (var (_, game) in _games.Where(x =>
-                x.Value.IsPublic &&
-                x.Value.GameState == GameStates.NotStarted &&
-                x.Value.PlayerCount < x.Value.Options.MaxPlayers &&
-                (_compatibilityConfig.AllowVersionMixing || x.Value.Host == null ||
-                 this._compatibilityManager.CanJoinGame(x.Value.Host.Client.GameVersion, gameVersion) == GameJoinError.None)))
-            {
-                // Check for options.
-                if (!map.HasFlag((MapFlags)(1 << (byte)game.Options.Map)))
-                {
-                    continue;
-                }
-
-                if (!language.HasFlag(game.Options.Keywords))
-                {
-                    continue;
-                }
-
-                if (impostorCount != 0 && game.Options.NumImpostors != impostorCount)
-                {
-                    continue;
-                }
-
-                if (!game.FilterOptions.FilterTags.SetEquals(filterTags))
-                {
-                    continue;
-                }
-
-                // Add to result.
-                yield return game;
-
-                // Break out if we have enough.
-                if (++results == count)
-                {
-                    yield break;
-                }
-            }
-        }
-
         public async ValueTask RemoveAsync(GameCode gameCode)
         {
             if (_games.TryGetValue(gameCode, out var game) && game.PlayerCount > 0)
index 76c10e65dad22e531b3005a4a85ed695d7a1b17c..4148c6d6da37a3ef9d7b25562e76ab2295e1d05b 100644 (file)
@@ -27,19 +27,6 @@ namespace Impostor.Server.Plugins
             CheckPaths(pluginPaths);
             CheckPaths(libraryPaths);
 
-            // Add library path for ASP.NET Core. This is useful for plugins that depend on ASP.NET Core,
-            // like Impostor.Http. The path contains the .NET version number, so it changes regularly.
-            var aspNetPath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()
-                .Replace("Microsoft.NETCore.App", "Microsoft.AspNetCore.App");
-            if (Directory.Exists(aspNetPath))
-            {
-                libraryPaths.Add(aspNetPath);
-            }
-            else
-            {
-                Logger.Information("ASP.NET Core not installed, this may cause issues if you have plugins that depend on it");
-            }
-
             var rootFolder = Directory.GetCurrentDirectory();
 
             pluginPaths.Add(Path.Combine(rootFolder, "plugins"));
@@ -126,7 +113,8 @@ namespace Impostor.Server.Plugins
 
             builder.ConfigureServices(services =>
             {
-                services.AddHostedService(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(provider, orderedPlugins));
+                services.AddSingleton<PluginLoaderService>(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(provider, orderedPlugins));
+                services.AddSingleton<IHostedService>(p => p.GetRequiredService<PluginLoaderService>());
 
                 foreach (var plugin in orderedPlugins)
                 {
index 40e13b5b8bc91a790c2c44738ed212596b03500f..9ec5d002fdcbd09a2c518d9f40ff8888542433d3 100644 (file)
@@ -22,6 +22,8 @@ namespace Impostor.Server.Plugins
             _plugins = plugins;
         }
 
+        public IReadOnlyList<PluginInformation> Plugins => _plugins;
+
         public async Task StartAsync(CancellationToken cancellationToken)
         {
             _logger.LogInformation("Loading plugins.");
index 584629f27b4ba1be3dc37b0443eedcc2067186ab..fdcd44d9a562dbe8ec839fa6d8bfd9fe7fccdbd8 100644 (file)
@@ -1,6 +1,7 @@
 using System;
 using System.IO;
 using System.Linq;
+using System.Net;
 using System.Reflection;
 using System.Runtime.Loader;
 using Impostor.Api.Config;
@@ -9,9 +10,11 @@ using Impostor.Api.Games;
 using Impostor.Api.Games.Managers;
 using Impostor.Api.Net.Custom;
 using Impostor.Api.Net.Manager;
+using Impostor.Api.Plugins;
 using Impostor.Api.Utils;
 using Impostor.Hazel.Extensions;
 using Impostor.Server.Events;
+using Impostor.Server.Http;
 using Impostor.Server.Net;
 using Impostor.Server.Net.Custom;
 using Impostor.Server.Net.Factories;
@@ -20,6 +23,9 @@ using Impostor.Server.Net.Messages;
 using Impostor.Server.Plugins;
 using Impostor.Server.Recorder;
 using Impostor.Server.Utils;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
@@ -73,8 +79,10 @@ namespace Impostor.Server
             var configuration = CreateConfiguration(args);
             var pluginConfig = configuration.GetSection("PluginLoader")
                 .Get<PluginConfig>() ?? new PluginConfig();
+            var httpConfig = configuration.GetSection(HttpServerConfig.Section)
+                .Get<HttpServerConfig>() ?? new HttpServerConfig();
 
-            return Host.CreateDefaultBuilder(args)
+            var hostBuilder = Host.CreateDefaultBuilder(args)
                 .UseContentRoot(Directory.GetCurrentDirectory())
 #if DEBUG
                 .UseEnvironment(Environment.GetEnvironmentVariable("IMPOSTOR_ENV") ?? "Development")
@@ -100,6 +108,7 @@ namespace Impostor.Server
                     services.Configure<CompatibilityConfig>(host.Configuration.GetSection(CompatibilityConfig.Section));
                     services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
                     services.Configure<TimeoutConfig>(host.Configuration.GetSection(TimeoutConfig.Section));
+                    services.Configure<HttpServerConfig>(host.Configuration.GetSection(HttpServerConfig.Section));
 
                     services.AddSingleton<ICompatibilityManager, CompatibilityManager>();
                     services.AddSingleton<ClientManager>();
@@ -126,6 +135,7 @@ namespace Impostor.Server
 
                     services.AddSingleton<GameManager>();
                     services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
+                    services.AddSingleton<ListingManager>();
 
                     services.AddEventPools();
                     services.AddHazel();
@@ -180,6 +190,7 @@ namespace Impostor.Server
 #else
                         .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
 #endif
+                        .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
                         .Enrich.FromLogContext()
                         .WriteTo.Console()
                         .ReadFrom.Configuration(context.Configuration, ConfigurationAssemblySource.AlwaysScanDllFiles);
@@ -188,6 +199,46 @@ namespace Impostor.Server
                 })
                 .UseConsoleLifetime()
                 .UsePluginLoader(pluginConfig);
+
+            if (httpConfig.Enabled)
+            {
+                hostBuilder.ConfigureWebHostDefaults(builder =>
+                {
+                    builder.ConfigureServices(services =>
+                    {
+                        services.AddControllers();
+                    });
+
+                    builder.Configure(app =>
+                    {
+                        var pluginLoaderService = app.ApplicationServices.GetRequiredService<PluginLoaderService>();
+                        foreach (var pluginInformation in pluginLoaderService.Plugins)
+                        {
+                            if (pluginInformation.Startup is IPluginHttpStartup httpStartup)
+                            {
+                                httpStartup.ConfigureWebApplication(app);
+                            }
+                        }
+
+                        app.UseRouting();
+
+                        app.UseEndpoints(endpoints =>
+                        {
+                            endpoints.MapControllers();
+                        });
+                    });
+
+                    builder.ConfigureKestrel(serverOptions =>
+                    {
+                        serverOptions.Listen(IPAddress.Parse(httpConfig.ListenIp), httpConfig.ListenPort, listenOptions =>
+                        {
+                            listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+                        });
+                    });
+                });
+            }
+
+            return hostBuilder;
         }
     }
 }
index 562e290f128a7e129078573c160233283f09ed79..9ffb3531d134e91ec5dfc44e4b9fbbbfaf5dd4c2 100644 (file)
@@ -5,6 +5,11 @@
     "ListenIp": "0.0.0.0",
     "ListenPort": 22023
   },
+  "HttpServer": {
+    "Enabled": true,
+    "ListenIp": "127.0.0.1",
+    "ListenPort": 22023
+  },
   "AntiCheat": {
     "Enabled": true,
     "BanIpFromGame": true
index 1f68f968f97d3e391c18c946395d010accf379a4..fbad991c2173bd89724e7041d29343f4c4176732 100644 (file)
@@ -5,6 +5,11 @@
     "ListenIp": "0.0.0.0",
     "ListenPort": 22023
   },
+  "HttpServer": {
+    "Enabled": true,
+    "ListenIp": "127.0.0.1",
+    "ListenPort": 22023
+  },
   "AntiCheat": {
     "Enabled": true,
     "BanIpFromGame": true
index b28bd0fea54c619c6edfa6a0b009ae8b73fe2797..2182e5909dbfd86765bb5e7e4f313fc0108c6e69 100644 (file)
       },
       "StyleCop.Analyzers.Unstable": {
         "type": "Direct",
-        "requested": "[1.2.0.435, )",
-        "resolved": "1.2.0.435",
-        "contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
+        "requested": "[1.2.0.507, )",
+        "resolved": "1.2.0.507",
+        "contentHash": "gTY3IQdRqDJ4hbhSA3e/R48oE8b/OiKfvwkt1QdNVfrJK2gMHBV8ldaHJ885jxWZfllK66soa/sdcjh9bX49Tw=="
       },
       "Impostor.Hazel.Abstractions": {
         "type": "Transitive",
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
index fcf2b2f0cc96fc0877582f64b6cc3784bc0289c4..536a439cc0857df89b67e79aa7af1cf589171280 100644 (file)
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.CodeCoverage": {
         "type": "Transitive",
         "resolved": "17.4.1",
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
       "impostor.server": {
         "type": "Project",
         "dependencies": {
-          "Impostor.Api": "[1.7.3-dev, )",
+          "Impostor.Api": "[1.8.4-dev, )",
           "Impostor.Hazel": "[1.0.0, )",
           "Microsoft.Extensions.FileSystemGlobbing": "[7.0.0, )",
           "Microsoft.Extensions.Hosting": "[7.0.0, )",
index 7e0296904bd2cff9d682d8069294264f8a6b3dee..5f0e27ac70cf0de2f23b2482caf3a5209b337c35 100644 (file)
         "resolved": "1.0.0",
         "contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
       },
+      "Microsoft.AspNetCore.Http.Abstractions": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==",
+        "dependencies": {
+          "Microsoft.AspNetCore.Http.Features": "2.2.0",
+          "System.Text.Encodings.Web": "4.5.0"
+        }
+      },
+      "Microsoft.AspNetCore.Http.Features": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "2.2.0"
+        }
+      },
       "Microsoft.Extensions.Configuration": {
         "type": "Transitive",
         "resolved": "7.0.0",
         "type": "Project",
         "dependencies": {
           "Impostor.Hazel.Abstractions": "[1.0.0, )",
+          "Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
           "Microsoft.Extensions.Hosting.Abstractions": "[7.0.0, )",
           "Microsoft.Extensions.Logging.Abstractions": "[7.0.0, )"
         }
       "impostor.server": {
         "type": "Project",
         "dependencies": {
-          "Impostor.Api": "[1.7.3-dev, )",
+          "Impostor.Api": "[1.8.4-dev, )",
           "Impostor.Hazel": "[1.0.0, )",
           "Microsoft.Extensions.FileSystemGlobbing": "[7.0.0, )",
           "Microsoft.Extensions.Hosting": "[7.0.0, )",