diff --git a/SVSim.BattleNode/Sessions/BattleSession.cs b/SVSim.BattleNode/Sessions/BattleSession.cs index dad9c37..1c3d086 100644 --- a/SVSim.BattleNode/Sessions/BattleSession.cs +++ b/SVSim.BattleNode/Sessions/BattleSession.cs @@ -72,7 +72,15 @@ public sealed class BattleSession else { // Binary frame — an attachment for a pending binary event. - pendingAttachments.Add(msg.Value.Bytes); + // Engine.IO v3 prefixes binary WS frames with the packet-type byte + // (0x04 = Message), analogous to the leading digit on text frames. + // Strip it before treating the rest as the Socket.IO attachment payload. + var bin = msg.Value.Bytes; + if (bin.Length > 0 && bin[0] == (byte)EngineIoPacketType.Message) + { + bin = bin.AsSpan(1).ToArray(); + } + pendingAttachments.Add(bin); if (pendingFrame is not null && pendingAttachments.Count == pendingFrame.AttachmentCount) { var assembled = pendingFrame.WithAttachments(pendingAttachments.ToArray()); @@ -248,7 +256,14 @@ public sealed class BattleSession var eioText = $"{(int)EngineIoPacketType.Message}{text}"; await SendTextAsync(eioText, CancellationToken.None); foreach (var bin in bins) - await _ws.SendAsync(bin, WebSocketMessageType.Binary, endOfMessage: true, CancellationToken.None); + { + // Engine.IO v3 binary frames are prefixed with the packet-type byte + // (0x04 = Message), the binary analog of the leading digit on text frames. + var prefixed = new byte[bin.Length + 1]; + prefixed[0] = (byte)EngineIoPacketType.Message; + Buffer.BlockCopy(bin, 0, prefixed, 1, bin.Length); + await _ws.SendAsync(prefixed, WebSocketMessageType.Binary, endOfMessage: true, CancellationToken.None); + } } private async Task SendSioAckAsync(int ackId, long arg) diff --git a/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs b/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs index 961c554..2dd416a 100644 --- a/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs +++ b/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs @@ -66,7 +66,13 @@ internal sealed class RawSocketIoTestClient : IAsyncDisposable } await SendTextAsync($"{(int)EngineIoPacketType.Message}{text}", ct); foreach (var b in bins) - await _ws.SendAsync(b, WebSocketMessageType.Binary, true, ct); + { + // EIO v3 binary frames are prefixed with the packet-type byte (0x04 = Message). + var prefixed = new byte[b.Length + 1]; + prefixed[0] = (byte)EngineIoPacketType.Message; + Buffer.BlockCopy(b, 0, prefixed, 1, b.Length); + await _ws.SendAsync(prefixed, WebSocketMessageType.Binary, true, ct); + } } private async Task ReceiveTextAsync(CancellationToken ct) @@ -92,7 +98,13 @@ internal sealed class RawSocketIoTestClient : IAsyncDisposable result = await _ws.ReceiveAsync(buffer, ct); ms.Write(buffer, 0, result.Count); } while (!result.EndOfMessage); - return ms.ToArray(); + var raw = ms.ToArray(); + // EIO v3 binary frames are prefixed with the packet-type byte (0x04 = Message). Strip it. + if (raw.Length > 0 && raw[0] == (byte)EngineIoPacketType.Message) + { + return raw.AsSpan(1).ToArray(); + } + return raw; } private Task SendTextAsync(string text, CancellationToken ct)