Files
SVSimServer/SVSim.EmulatedEntrypoint/Models/Dtos/Internal/DataWrapper.cs
gamer147 f4f2ec380c feat(envelope): push required_res_ver from ResourceConfig on game_start
A wiped/fresh client (NukeIdentityOnStartup, new install, or any path
that clears PlayerPrefs) defaults its stored RES_VER to "00000000"
per Cute/SavedataManager.GetResourceVersion. The client builds the
Akamai manifest URL as dl/Manifest/<RES_VER>/<lang>/<Platform>/, and
Akamai 404s the "00000000" path -> Toolbox.AssetManager.InitializeManifest
fails -> the title screen shows "Connection Error / Reconnect"
before any tutorial UI loads.

Fix:

- New ResourceConfig [ConfigSection] in SVSim.Database — single
  field RequiredResVer defaulting to "4670rPsPMVlRTd2" (the value
  prod returned in data_dumps/traffic_prod_tutorial.ndjson and was
  still returning at 2026-05-28 21:00 UTC). Lives in GameConfigs so
  it can be tuned via DB / appsettings without code edits.

- ShadowverseTranslationMiddleware injects IGameConfigService and
  emits required_res_ver in data_headers ONLY on /check/game_start
  responses. NetworkTask.Parse opens a "new data is available" popup
  whenever required_res_ver is present and the URL is anything other
  than GameStartCheck (NetworkTask.cs:128-138); the suppression on
  game_start is what lets us silently bump PlayerPrefs["RES_VER"]
  before ResourceDownloader runs.

- DataHeaders gains a nullable RequiredResVer field. DataWrapper.DataHeaders
  is now Dictionary<string, object?> instead of the typed DataHeaders POCO
  directly — the construction site stays type-safe (the middleware builds
  the typed POCO, then projects through the same STJ +
  ConvertJsonTreeToPlainObject pipeline that DataWrapper.Data uses) so
  null-valued optional fields are absent from the wire instead of being
  written as "key":null. Without this, MessagePack's ContractlessStandardResolver
  walked the typed properties and wrote required_res_ver=null on every
  non-game_start response, tripping the popup on every boot.

- GameConfigurationJsonbTests updated to expect the 9th config section.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 18:03:15 -04:00

34 lines
1.5 KiB
C#

using MessagePack;
using System.Text.Json.Serialization;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Internal;
/// <summary>
/// Wraps responses in the format the official game client expects, with a header section for additional data. Not for manual endpoint use, this wrapping is done automatically in a middleware.
/// </summary>
[MessagePackObject]
public class DataWrapper
{
/// <summary>
/// Wire-shape projection of the response envelope headers. The middleware builds a
/// strongly-typed <see cref="DataHeaders"/> POCO and runs it through the same STJ +
/// <c>ConvertJsonTreeToPlainObject</c> pipeline that the controller's response goes
/// through, yielding this dict with absent keys for null-valued optional fields.
/// Typed as <see cref="Dictionary{TKey,TValue}"/> (not <see cref="object"/>) because
/// the projected shape is fully known — only the per-key value type varies. Direct
/// assignment of the typed POCO would let MessagePack's contractless resolver emit
/// <c>"key":null</c> for nullables, which the client treats as "key present" via
/// <c>Keys.Contains</c> (see <c>NetworkTask.isResourceVersionUp</c> for the
/// load-bearing case).
/// </summary>
[JsonPropertyName("data_headers")]
[Key("data_headers")]
public Dictionary<string, object?> DataHeaders { get; set; } = new();
/// <summary>
/// The response data from the endpoint.
/// </summary>
[JsonPropertyName("data")]
[Key("data")]
public object Data { get; set; } = new();
}