using DorkNet.Server.Compat;
using Microsoft.AspNetCore.Mvc;
namespace DorkNet.Server.Controllers.Auth;
/// The boot-time client-version gate the 2020.12 watch 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 the watch
/// reads from KMJJGELKNAP.MCFMPCNNEBH — for the December build
/// that's the literal string "20201210". 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)
{
["20201210"] = "december_2020_12_18",
};
[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": {
// "20201210": "december_2020_12_18",
// "20200310": "march_2020_03_10"
// }
// }
// 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 });
}
}