Files
SVSimServer/SVSim.BattleNode/Protocol/Bodies/MatchedBody.cs
gamer147 99129c786c fix(battle-node): harden SIO parse + narrow Matched OppoId/Seed to int
#3: SocketIoFrame.Parse now range-checks the packet type char (was
unchecked cast — any char outside 0-6 produced an undefined enum
value) and uses int.TryParse for ack-id (was int.Parse — a >10-digit
ack-id threw OverflowException, tearing down the WS mid-game). Both
now throw ArgumentException consistently. The read loop in
RealParticipant wraps both EIO and SIO parse calls with try-catch so
a malformed frame is logged and skipped instead of killing the battle.

#4: MatchedSelfInfo/MatchedOppoInfo OppoId and Seed narrowed from
long to int. The client reads both with Convert.ToInt32 inside a
swallowing try/catch — any value > int.MaxValue silently dropped the
Matched event, preventing the battle from starting. Seed was already
int-range (BattleSeeds.Stable returns int); OppoId (viewer ID) is
~847M in captures, well under int.MaxValue. The narrowing cast now
happens explicitly in ServerBattleFrames.BuildMatched at the wire
boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-04 21:57:29 -04:00

41 lines
2.1 KiB
C#

using System.Text.Json.Serialization;
namespace SVSim.BattleNode.Protocol.Bodies;
public sealed record MatchedBody(
[property: JsonPropertyName("selfInfo")] MatchedSelfInfo SelfInfo,
[property: JsonPropertyName("oppoInfo")] MatchedOppoInfo OppoInfo,
[property: JsonPropertyName("selfDeck")] IReadOnlyList<DeckCardRef> SelfDeck,
[property: JsonPropertyName("resultCode")] int ResultCode = (int)ReceiveNodeResultCode.Success) : IMsgBody;
// Note: `country_code` is deliberately snake_case among camelCase siblings — that's what prod
// sends on this frame (verified against the TK2 capture). Do NOT "normalize" it to countryCode.
public sealed record MatchedSelfInfo(
[property: JsonPropertyName("country_code")] string CountryCode,
[property: JsonPropertyName("userName")] string UserName,
[property: JsonPropertyName("sleeveId")] string SleeveId,
[property: JsonPropertyName("emblemId")] string EmblemId,
[property: JsonPropertyName("degreeId")] string DegreeId,
[property: JsonPropertyName("fieldId")] int FieldId,
[property: JsonPropertyName("isOfficial")]
[property: JsonConverter(typeof(NumericBoolJsonConverter))] bool IsOfficial,
[property: JsonPropertyName("oppoId")] int OppoId,
[property: JsonPropertyName("seed")] int Seed);
public sealed record MatchedOppoInfo(
[property: JsonPropertyName("country_code")] string CountryCode,
[property: JsonPropertyName("userName")] string UserName,
[property: JsonPropertyName("sleeveId")] string SleeveId,
[property: JsonPropertyName("emblemId")] string EmblemId,
[property: JsonPropertyName("degreeId")] string DegreeId,
[property: JsonPropertyName("fieldId")] int FieldId,
[property: JsonPropertyName("isOfficial")]
[property: JsonConverter(typeof(NumericBoolJsonConverter))] bool IsOfficial,
[property: JsonPropertyName("oppoId")] int OppoId,
[property: JsonPropertyName("seed")] int Seed,
[property: JsonPropertyName("oppoDeckCount")] int OppoDeckCount);
public sealed record DeckCardRef(
[property: JsonPropertyName("idx")] int Idx,
[property: JsonPropertyName("cardId")] long CardId);