using DorkNet.Server.Compat;
using Microsoft.AspNetCore.Mvc;
namespace DorkNet.Server.Controllers.Auth;
/// The boot-time client-version gate the 2023.03.21 client hits
/// at startup as GET api/versioncheck/v4?v={build}&p={platform}.
///
/// Verified against the watch's KMDHPCHFADM.VerifyGameVersion
/// (decompiled): the response is parsed into JFKEIPDCCHH which
/// exposes a single VersionStatus property of enum type
/// PCBCGDHBAEL:
///
/// - 0 = ValidForPlay — proceed normally
/// - 1 = ValidForMenu — let user reach menus but block matchmaking
/// - 2 = UpdateRequired — watch shows its built-in
/// "Update Required" dialog and refuses to advance past the boot screen
///
/// This is the CLEAN way to reject a wrong-build client at the
/// front door — the watch has dedicated UX for it, no custom client
/// mod work needed.
///
/// The v query param is the raw build identifier supplied
/// by the client. We map raw build IDs to our internal
/// s via the
/// DorkNet:BuildIdToVersionKey config map; any build ID we
/// don't recognise OR that maps to a key not in
/// gets
/// VersionStatus = 2 and the watch refuses to proceed.
///
/// This endpoint is EXEMPT from
/// VersionDetectionMiddleware's gate (otherwise the gate would
/// 426 the rejection request itself before this endpoint could reply
/// with the polite UpdateRequired payload). The exemption is set in
/// VersionDetectionMiddleware.IsExempt.
///
[ApiController]
public class VersionCheckController(
IConfiguration config,
VersionRegistry registry,
ILogger logger) : ControllerBase
{
// Mirrors the PCBCGDHBAEL enum the watch decodes the response into.
private const int VersionStatus_ValidForPlay = 0;
private const int VersionStatus_ValidForMenu = 1;
private const int VersionStatus_UpdateRequired = 2;
private static readonly IReadOnlyDictionary BuiltInBuildIdToVersionKey =
new Dictionary(StringComparer.OrdinalIgnoreCase)
{
["20230317"] = "march_2023_03_21",
["20230321"] = "march_2023_03_21",
};
[HttpGet("/api/versioncheck/v4")]
[HttpGet("/api/versioncheck/v3")]
[HttpGet("/api/versioncheck/v2")]
[HttpGet("/api/versioncheck/v1")]
[HttpGet("/api/versioncheck")]
[HttpGet("/api/version/v1")]
[HttpGet("/api/version")]
public IActionResult Check(
[FromQuery(Name = "v")] string? buildId,
[FromQuery(Name = "p")] string? platform)
{
// Map raw build ID → ClientVersion key via config. Format:
// "DorkNet": {
// "BuildIdToVersionKey": {
// "20230321": "march_2023_03_21"
// }
// }
// Mapping ABSENT → unknown build → UpdateRequired.
var mapSection = config.GetSection("DorkNet:BuildIdToVersionKey");
string? versionKey = null;
if (!string.IsNullOrWhiteSpace(buildId))
{
var normalizedBuildId = buildId.Trim();
versionKey = mapSection[normalizedBuildId];
if (string.IsNullOrWhiteSpace(versionKey)
&& BuiltInBuildIdToVersionKey.TryGetValue(normalizedBuildId, out var builtInVersionKey))
{
versionKey = builtInVersionKey;
}
}
bool allowed = versionKey is not null && registry.IsSupported(versionKey);
var status = allowed ? VersionStatus_ValidForPlay : VersionStatus_UpdateRequired;
logger.LogInformation(
"[versioncheck] buildId={Build} platform={Platform} → versionKey={Key} status={Status} ({Label})",
buildId, platform, versionKey ?? "(unmapped)", status,
status == VersionStatus_ValidForPlay ? "ValidForPlay" : "UpdateRequired");
// Wire shape verified against KMDHPCHFADM_NestedType_JFKEIPDCCHH.txt:
// single key "VersionStatus" (PascalCase, case-sensitive — LitJson).
return Ok(new { VersionStatus = status });
}
}