diff --git a/docs/containers.md b/docs/containers.md
index 303c966..b15a862 100644
--- a/docs/containers.md
+++ b/docs/containers.md
@@ -129,3 +129,24 @@ make to the application.
Alternatively, if you use the hosted versions you can request an API key that allows for higher rate limits
by sponsoring this project! (if you self-host you can generate your own keys too!).
+
+# Disabling the client validation
+If you run the container and navigate to it in your browser
+you might come across the following message:
+```json
+{"message":"The X-Super-Client and X-Super-Contact headers are required"}
+```
+
+This is because by default the API will validate all requests send `X-Super-Client` and `X-Super-Contact`
+headers along. You can disable this behaviour by setting the following flag:
+```json
+{
+ "Helldivers": {
+ "API": {
+ "ValidateClients": false
+ }
+ }
+}
+```
+Or through environment flags with `-e Helldivers__API__ValidateClients`
+
diff --git a/src/Helldivers-2-API/Configuration/ApiConfiguration.cs b/src/Helldivers-2-API/Configuration/ApiConfiguration.cs
index 1dadb48..69a338f 100644
--- a/src/Helldivers-2-API/Configuration/ApiConfiguration.cs
+++ b/src/Helldivers-2-API/Configuration/ApiConfiguration.cs
@@ -20,6 +20,11 @@ public sealed class ApiConfiguration
///
public string Blacklist { get; set; } = string.Empty;
+ ///
+ /// Whether X-Super-Client and X-Super-Contact headers are validated.
+ ///
+ public bool ValidateClients { get; set; } = true;
+
///
/// Contains the for the API.
///
diff --git a/src/Helldivers-2-API/Helldivers-2-API.csproj b/src/Helldivers-2-API/Helldivers-2-API.csproj
index 98c8a3c..8316a3a 100644
--- a/src/Helldivers-2-API/Helldivers-2-API.csproj
+++ b/src/Helldivers-2-API/Helldivers-2-API.csproj
@@ -23,8 +23,8 @@
-
-
+
+
@@ -36,12 +36,12 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/Helldivers-2-API/Middlewares/RateLimitMiddleware.cs b/src/Helldivers-2-API/Middlewares/RateLimitMiddleware.cs
index 6c2db5c..158570c 100644
--- a/src/Helldivers-2-API/Middlewares/RateLimitMiddleware.cs
+++ b/src/Helldivers-2-API/Middlewares/RateLimitMiddleware.cs
@@ -2,8 +2,11 @@
using Helldivers.API.Extensions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
using System.Net;
using System.Security.Claims;
+using System.Text;
+using System.Text.Json;
using System.Threading.RateLimiting;
namespace Helldivers.API.Middlewares;
@@ -26,6 +29,12 @@ IMemoryCache cache
///
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
+ if (IsValidRequest(context) is false)
+ {
+ await RejectRequest(context);
+ return;
+ }
+
var limiter = GetRateLimiter(context);
using var lease = await limiter.AcquireAsync(permitCount: 1, context.RequestAborted);
if (limiter.GetStatistics() is { } statistics)
@@ -45,6 +54,18 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
await next(context);
}
+ ///
+ /// Checks if the request is "valid" (contains the correct X-Super-* headers).
+ ///
+ private bool IsValidRequest(HttpContext context)
+ {
+ if (options.Value.ValidateClients is false)
+ return true;
+
+ return context.Request.Headers.ContainsKey(Constants.CLIENT_HEADER_NAME)
+ && context.Request.Headers.ContainsKey(Constants.CONTACT_HEADER_NAME);
+ }
+
private RateLimiter GetRateLimiter(HttpContext http)
{
if (http.User.Identity?.IsAuthenticated ?? false)
@@ -86,4 +107,19 @@ private RateLimiter GetRateLimiterForUser(ClaimsPrincipal user)
});
}) ?? throw new InvalidOperationException($"Creating rate limiter failed for {name}");
}
+
+ private async Task RejectRequest(HttpContext context)
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ context.Response.Headers.WWWAuthenticate = "X-Super-Client";
+ context.Response.ContentType = "application/json";
+
+ var writer = new Utf8JsonWriter(context.Response.Body);
+ writer.WriteStartObject();
+ writer.WritePropertyName("message");
+ writer.WriteStringValue("The X-Super-Client and X-Super-Contact headers are required");
+ writer.WriteEndObject();
+
+ await writer.FlushAsync(context.RequestAborted);
+ }
}
diff --git a/src/Helldivers-2-API/appsettings.Development.json b/src/Helldivers-2-API/appsettings.Development.json
index 3bc86a6..ebc03f0 100644
--- a/src/Helldivers-2-API/appsettings.Development.json
+++ b/src/Helldivers-2-API/appsettings.Development.json
@@ -10,6 +10,7 @@
},
"Helldivers": {
"API": {
+ "ValidateClients": false,
"Authentication": {
"SigningKey": "I4eGmsXbDXfxlRo5N+w0ToRGN8aWSIaYWbZ2zMFqqnI="
}
diff --git a/src/Helldivers-2-Core/Helldivers-2-Core.csproj b/src/Helldivers-2-Core/Helldivers-2-Core.csproj
index 35ca7b9..50b582d 100644
--- a/src/Helldivers-2-Core/Helldivers-2-Core.csproj
+++ b/src/Helldivers-2-Core/Helldivers-2-Core.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Helldivers-2-Core/Mapping/V1/AssignmentMapper.cs b/src/Helldivers-2-Core/Mapping/V1/AssignmentMapper.cs
index 3eaeeda..e17e042 100644
--- a/src/Helldivers-2-Core/Mapping/V1/AssignmentMapper.cs
+++ b/src/Helldivers-2-Core/Mapping/V1/AssignmentMapper.cs
@@ -54,6 +54,7 @@ private Assignment MapToV1(Dictionary trans
Description: LocalizedMessage.FromStrings(descriptions),
Tasks: invariant.Setting.Tasks.Select(MapToV1).ToList(),
Reward: MapToV1(invariant.Setting.Reward),
+ Rewards: invariant.Setting.Rewards.Select(MapToV1).ToList(),
Expiration: expiration
);
}
diff --git a/src/Helldivers-2-Models/ArrowHead/Assignments/Setting.cs b/src/Helldivers-2-Models/ArrowHead/Assignments/Setting.cs
index 882bd76..56f1b38 100644
--- a/src/Helldivers-2-Models/ArrowHead/Assignments/Setting.cs
+++ b/src/Helldivers-2-Models/ArrowHead/Assignments/Setting.cs
@@ -8,7 +8,8 @@
/// The briefing (description) of this assignment.
/// A description of what is expected of Helldivers to complete the assignment.
/// A list of s describing the assignment requirements.
-/// Contains information on the rewards players willr eceive upon completion.
+/// Contains information on the reward players will receive upon completion.
+/// Contains information on the rewards players will receive upon completion.
/// Flags, suspected to be a binary OR'd value, purpose unknown.
public sealed record Setting(
int Type,
@@ -17,5 +18,6 @@ public sealed record Setting(
string TaskDescription,
List Tasks,
Reward Reward,
+ List Rewards,
int Flags
);
diff --git a/src/Helldivers-2-Models/V1/Assignment.cs b/src/Helldivers-2-Models/V1/Assignment.cs
index 88d9bd7..f60a7c1 100644
--- a/src/Helldivers-2-Models/V1/Assignment.cs
+++ b/src/Helldivers-2-Models/V1/Assignment.cs
@@ -15,6 +15,7 @@ namespace Helldivers.Models.V1;
/// A very short summary of the description.
/// A list of tasks that need to be completed for this assignment.
/// The reward for completing the assignment.
+/// A list of rewards for completing the assignment.
/// The date when the assignment will expire.
public sealed record Assignment(
long Id,
@@ -24,5 +25,6 @@ public sealed record Assignment(
LocalizedMessage Description,
List Tasks,
Reward Reward,
+ List Rewards,
DateTime Expiration
);
diff --git a/src/Helldivers-2-Models/json b/src/Helldivers-2-Models/json
index 2522dfc..6885068 160000
--- a/src/Helldivers-2-Models/json
+++ b/src/Helldivers-2-Models/json
@@ -1 +1 @@
-Subproject commit 2522dfcd6ce55ac2c670520affa869a16a46e78c
+Subproject commit 688506848075317ea0037a508321b40f96b054d8
diff --git a/src/Helldivers-2-SourceGen/Contracts/IJsonParser.cs b/src/Helldivers-2-SourceGen/Contracts/IJsonParser.cs
new file mode 100644
index 0000000..6286548
--- /dev/null
+++ b/src/Helldivers-2-SourceGen/Contracts/IJsonParser.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Helldivers.SourceGen.Contracts;
+
+///
+/// Interface for parsing JSON strings and generating corresponding C# source code.
+///
+public interface IJsonParser
+{
+ ///
+ /// Parses the given source text and generates corresponding C# source code.
+ ///
+ /// The source file to be parsed.
+ /// A for cancelling the parse process.
+ /// A object representing the generated C# source code.
+ SourceText Parse(AdditionalText file, CancellationToken cancellationToken = default);
+}
diff --git a/src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj b/src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj
index 121b4f2..43ddd4a 100644
--- a/src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj
+++ b/src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj
@@ -14,16 +14,15 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
-
+
diff --git a/src/Helldivers-2-SourceGen/Parsers/BaseJsonParser.cs b/src/Helldivers-2-SourceGen/Parsers/BaseJsonParser.cs
new file mode 100644
index 0000000..e1e422e
--- /dev/null
+++ b/src/Helldivers-2-SourceGen/Parsers/BaseJsonParser.cs
@@ -0,0 +1,50 @@
+using Helldivers.SourceGen.Contracts;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using System.Text;
+
+namespace Helldivers.SourceGen.Parsers;
+
+///
+/// An abstract base class for parsing JSON data and generating corresponding C# source code.
+///
+public abstract class BaseJsonParser : IJsonParser
+{
+ private const string TEMPLATE = @"//
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+using global::System.Collections.Generic;
+using global::Helldivers.Models.Domain.Localization;
+
+namespace Helldivers.Models;
+
+public static partial class Static
+{{
+ /// Public list of {0} entries from {1}
+ public static {2} {0} = {3};
+}}";
+
+ ///
+ public SourceText Parse(AdditionalText file, CancellationToken cancellationToken = default)
+ {
+ var filename = Path.GetFileName(file.Path);
+ var json = file.GetText(cancellationToken)?.ToString();
+
+ var name = Path.GetFileNameWithoutExtension(file.Path);
+ name = $"{char.ToUpper(name[0])}{name.Substring(1)}";
+
+ if (string.IsNullOrWhiteSpace(json) is false)
+ {
+ var (type, csharp) = Parse(json!);
+
+ var output = string.Format(TEMPLATE, name, filename, type, csharp);
+ return SourceText.From(output, Encoding.UTF8);
+ }
+
+ return SourceText.From("// Could not read JSON file");
+ }
+
+ ///
+ /// Convert the JSON string into C# code that can be injected.
+ ///
+ protected abstract (string Type, string Source) Parse(string json);
+}
diff --git a/src/Helldivers-2-SourceGen/Parsers/BiomesParser.cs b/src/Helldivers-2-SourceGen/Parsers/BiomesParser.cs
new file mode 100644
index 0000000..3a1df0a
--- /dev/null
+++ b/src/Helldivers-2-SourceGen/Parsers/BiomesParser.cs
@@ -0,0 +1,22 @@
+using System.Text;
+using System.Text.Json;
+
+namespace Helldivers.SourceGen.Parsers;
+
+///
+/// BiomesParser is responsible for parsing a JSON string representation of biomes.
+///
+public class BiomesParser : BaseJsonParser
+{
+ ///
+ protected override (string Type, string Source) Parse(string json)
+ {
+ var builder = new StringBuilder("new Dictionary()\n\t{\n");
+ var entries = JsonSerializer.Deserialize>>(json)!;
+ foreach (var pair in entries)
+ builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Biome(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");
+
+ builder.Append("\t}");
+ return ("IReadOnlyDictionary", builder.ToString());
+ }
+}
diff --git a/src/Helldivers-2-SourceGen/Parsers/EnvironmentalsParser.cs b/src/Helldivers-2-SourceGen/Parsers/EnvironmentalsParser.cs
new file mode 100644
index 0000000..b00298c
--- /dev/null
+++ b/src/Helldivers-2-SourceGen/Parsers/EnvironmentalsParser.cs
@@ -0,0 +1,23 @@
+using System.Text;
+using System.Text.Json;
+
+namespace Helldivers.SourceGen.Parsers;
+
+///
+/// The EnvironmentalsParser class is responsible for parsing JSON strings
+/// representing environmental hazards and converting them into C# source code.
+///
+public class EnvironmentalsParser : BaseJsonParser
+{
+ ///
+ protected override (string Type, string Source) Parse(string json)
+ {
+ var builder = new StringBuilder("new Dictionary()\n\t{\n");
+ var entries = JsonSerializer.Deserialize>>(json)!;
+ foreach (var pair in entries)
+ builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Hazard(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");
+
+ builder.Append("\t}");
+ return ("IReadOnlyDictionary", builder.ToString());
+ }
+}
diff --git a/src/Helldivers-2-SourceGen/Parsers/FactionsParser.cs b/src/Helldivers-2-SourceGen/Parsers/FactionsParser.cs
new file mode 100644
index 0000000..9b4cc0a
--- /dev/null
+++ b/src/Helldivers-2-SourceGen/Parsers/FactionsParser.cs
@@ -0,0 +1,22 @@
+using System.Text;
+using System.Text.Json;
+
+namespace Helldivers.SourceGen.Parsers;
+
+///
+/// Parses JSON data of factions and generates the corresponding C# source code representation.
+///
+public class FactionsParser : BaseJsonParser
+{
+ ///
+ protected override (string Type, string Source) Parse(string json)
+ {
+ var builder = new StringBuilder("new Dictionary()\n\t{\n");
+ var entries = JsonSerializer.Deserialize>(json)!;
+ foreach (var pair in entries)
+ builder.AppendLine($@"{'\t'}{'\t'}{{ {pair.Key}, ""{pair.Value}"" }},");
+
+ builder.Append("\t}");
+ return ("IReadOnlyDictionary", builder.ToString());
+ }
+}
diff --git a/src/Helldivers-2-SourceGen/Parsers/PlanetsParser.cs b/src/Helldivers-2-SourceGen/Parsers/PlanetsParser.cs
new file mode 100644
index 0000000..c3248e3
--- /dev/null
+++ b/src/Helldivers-2-SourceGen/Parsers/PlanetsParser.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using System.Text.Json;
+
+namespace Helldivers.SourceGen.Parsers;
+
+///
+/// Handles parsing the planets.json and generating the resulting planet data.
+///
+public class PlanetsParser : BaseJsonParser
+{
+ ///
+ protected override (string Type, string Source) Parse(string json)
+ {
+ var builder = new StringBuilder("new Dictionary Environmentals)>()\n\t{\n");
+ var document = JsonDocument.Parse(json);
+ foreach (var property in document.RootElement.EnumerateObject())
+ {
+ var index = property.Name;
+ var name = property.Value.GetProperty("name").GetString();
+ var names = property
+ .Value
+ .GetProperty("names")
+ .EnumerateObject()
+ .ToDictionary(prop => prop.Name, prop => prop.Value.GetString()!);
+ var sector = property.Value.GetProperty("sector").GetString();
+ var biome = property.Value.GetProperty("biome").GetString();
+ var environmentals = property
+ .Value
+ .GetProperty("environmentals")
+ .EnumerateArray()
+ .Select(prop => $@"""{prop.GetString()!}""")
+ .ToList();
+
+ builder.AppendLine($@"{'\t'}{'\t'}{{ {index}, (LocalizedMessage.FromStrings([{string.Join(", ", names.Select(pair => $@"new KeyValuePair(""{pair.Key}"", ""{pair.Value}"")"))}]), ""{sector}"", ""{biome}"", [{string.Join(", ", environmentals)}]) }},");
+ }
+
+ builder.Append("\t}");
+ return ("IReadOnlyDictionary Environmentals)>", builder.ToString());
+ }
+}
diff --git a/src/Helldivers-2-SourceGen/StaticJsonSourceGenerator.cs b/src/Helldivers-2-SourceGen/StaticJsonSourceGenerator.cs
index feca41f..da5d73d 100644
--- a/src/Helldivers-2-SourceGen/StaticJsonSourceGenerator.cs
+++ b/src/Helldivers-2-SourceGen/StaticJsonSourceGenerator.cs
@@ -1,6 +1,6 @@
+using Helldivers.SourceGen.Contracts;
+using Helldivers.SourceGen.Parsers;
using Microsoft.CodeAnalysis;
-using System.Text;
-using System.Text.Json;
namespace Helldivers.SourceGen;
@@ -9,14 +9,74 @@ namespace Helldivers.SourceGen;
/// When using a simple text file as a baseline, we can create a non-incremental source generator.
///
[Generator]
-public class StaticJsonSourceGenerator : ISourceGenerator
+public class StaticJsonSourceGenerator : IIncrementalGenerator
{
+ private static readonly IJsonParser PlanetParser = new PlanetsParser();
+ private static readonly IJsonParser BiomesParser = new BiomesParser();
+ private static readonly IJsonParser EnvironmentalsParser = new EnvironmentalsParser();
+ private static readonly IJsonParser FactionsParser = new FactionsParser();
+
///
- public void Initialize(GeneratorInitializationContext context)
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var generated = context
+ .AdditionalTextsProvider
+ .Where(static file => file.Path.EndsWith(".json"))
+ .Select(static (file, cancellationToken) =>
+ {
+ var parser = GetParserForFile(file);
+
+ var name = Path.GetFileNameWithoutExtension(file.Path);
+
+ return (parser.Parse(file, cancellationToken), name);
+ });
+
+ context.RegisterSourceOutput(generated, static (context, pair) =>
+ {
+ var (source, name) = pair;
+
+ try
+ {
+ context.AddSource(name, source);
+ }
+ catch (Exception exception)
+ {
+ context.AddSource($"{name}.g.cs", $"// An exception was thrown processing {name}.json\n{exception.ToString()}");
+ context.ReportDiagnostic(
+ Diagnostic.Create(
+ new DiagnosticDescriptor(
+ id: "HDJSON", // Unique ID for your error
+ title: "JSON source generator failed", // Title of the error
+ messageFormat: $"An error occured generating C# code from JSON files: {exception}", // Message format
+ category: "HD2", // Category of the error
+ DiagnosticSeverity.Error, // Severity of the error
+ isEnabledByDefault: true // Whether the error is enabled by default
+ ),
+ Location.None // No specific location provided for simplicity
+ )
+ );
+ }
+ });
+ }
+
+ private static IJsonParser GetParserForFile(AdditionalText file)
{
- // No initialization required for this generator.
+ var name = Path.GetFileNameWithoutExtension(file.Path);
+ name = $"{char.ToUpper(name[0])}{name.Substring(1)}";
+
+ return name.ToLowerInvariant() switch
+ {
+ "planets" => PlanetParser,
+ "biomes" => BiomesParser,
+ "environmentals" => EnvironmentalsParser,
+ "factions" => FactionsParser,
+ _ => throw new Exception($"Generator does not know how to parse {name}")
+ };
}
+
+
+#if false
///
public void Execute(GeneratorExecutionContext context)
{
@@ -76,69 +136,5 @@ public static partial class Static
}
}
}
-
- ///
- /// Parses a JSON file that's an object where keys are numerical and values are names (strings).
- ///
- private (string Type, string Source) ParseFactionsDictionary(string json)
- {
- var builder = new StringBuilder("new Dictionary()\n\t{\n");
- var entries = JsonSerializer.Deserialize>(json)!;
- foreach (var pair in entries)
- builder.AppendLine($@"{'\t'}{'\t'}{{ {pair.Key}, ""{pair.Value}"" }},");
-
- builder.Append("\t}");
- return ("IReadOnlyDictionary", builder.ToString());
- }
-
- private (string Type, string Source) ParsePlanetsDictionary(string json)
- {
- var builder = new StringBuilder("new Dictionary Environmentals)>()\n\t{\n");
- var document = JsonDocument.Parse(json);
- foreach (var property in document.RootElement.EnumerateObject())
- {
- var index = property.Name;
- var name = property.Value.GetProperty("name").GetString();
- var names = property
- .Value
- .GetProperty("names")
- .EnumerateObject()
- .ToDictionary(prop => prop.Name, prop => prop.Value.GetString()!);
- var sector = property.Value.GetProperty("sector").GetString();
- var biome = property.Value.GetProperty("biome").GetString();
- var environmentals = property
- .Value
- .GetProperty("environmentals")
- .EnumerateArray()
- .Select(prop => $@"""{prop.GetString()!}""")
- .ToList();
-
- builder.AppendLine($@"{'\t'}{'\t'}{{ {index}, (LocalizedMessage.FromStrings([{string.Join(", ", names.Select(pair => $@"new KeyValuePair(""{pair.Key}"", ""{pair.Value}"")"))}]), ""{sector}"", ""{biome}"", [{string.Join(", ", environmentals)}]) }},");
- }
-
- builder.Append("\t}");
- return ("IReadOnlyDictionary Environmentals)>", builder.ToString());
- }
-
- private (string Type, string Source) ParseBiomesDictionary(string json)
- {
- var builder = new StringBuilder("new Dictionary()\n\t{\n");
- var entries = JsonSerializer.Deserialize>>(json)!;
- foreach (var pair in entries)
- builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Biome(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");
-
- builder.Append("\t}");
- return ("IReadOnlyDictionary", builder.ToString());
- }
-
- private (string Type, string Source) ParseEnvironmentalsDictionary(string json)
- {
- var builder = new StringBuilder("new Dictionary()\n\t{\n");
- var entries = JsonSerializer.Deserialize>>(json)!;
- foreach (var pair in entries)
- builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Hazard(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");
-
- builder.Append("\t}");
- return ("IReadOnlyDictionary", builder.ToString());
- }
+#endif
}
diff --git a/src/Helldivers-2-Sync/Helldivers-2-Sync.csproj b/src/Helldivers-2-Sync/Helldivers-2-Sync.csproj
index 67c2d7c..2016b56 100644
--- a/src/Helldivers-2-Sync/Helldivers-2-Sync.csproj
+++ b/src/Helldivers-2-Sync/Helldivers-2-Sync.csproj
@@ -6,10 +6,10 @@
-
-
-
-
+
+
+
+