Files
OpenRA/OpenRA.Game/Network/OrderIO.cs

186 lines
4.9 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
namespace OpenRA.Network
{
public class OrderPacket
{
readonly Order[] orders;
readonly MemoryStream data;
public OrderPacket(Order[] orders)
{
this.orders = orders;
data = null;
}
public OrderPacket(MemoryStream data)
{
orders = null;
this.data = data;
}
public IEnumerable<Order> GetOrders(World world)
{
return orders ?? ParseData(world);
}
IEnumerable<Order> ParseData(World world)
{
if (data == null)
yield break;
// Order deserialization depends on the current world state,
// so must be deferred until we are ready to consume them.
var reader = new BinaryReader(data);
while (data.Position < data.Length)
{
var o = Order.Deserialize(world, reader);
if (o != null)
yield return o;
}
}
public byte[] Serialize(int frame)
{
if (data != null)
return data.ToArray();
var ms = new MemoryStream();
ms.WriteArray(BitConverter.GetBytes(frame));
foreach (var o in orders)
ms.WriteArray(o.Serialize());
return ms.ToArray();
}
public static OrderPacket Combine(IEnumerable<OrderPacket> packets)
{
var orders = new List<Order>();
foreach (var packet in packets)
{
if (packet.orders == null)
throw new InvalidOperationException("OrderPacket.Combine can only be used with locally generated OrderPackets.");
orders.AddRange(packet.orders);
}
return new OrderPacket(orders.ToArray());
}
}
public static class OrderIO
{
static readonly OrderPacket NoOrders = new OrderPacket(Array.Empty<Order>());
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
{
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
ms.WriteArray(BitConverter.GetBytes(data.Frame));
ms.WriteByte((byte)OrderType.SyncHash);
ms.WriteArray(BitConverter.GetBytes(data.SyncHash));
ms.WriteArray(BitConverter.GetBytes(data.DefeatState));
return ms.GetBuffer();
}
public static bool TryParseDisconnect((int FromClient, byte[] Data) packet, out (int Frame, int ClientId) disconnect)
{
// Valid Disconnect packets are only ever generated by the server
if (packet.FromClient != 0 || packet.Data.Length != Order.DisconnectOrderLength + 4 || packet.Data[4] != (byte)OrderType.Disconnect)
{
disconnect = (0, 0);
return false;
}
var frame = BitConverter.ToInt32(packet.Data, 0);
var clientId = BitConverter.ToInt32(packet.Data, 5);
disconnect = (frame, clientId);
return true;
}
public static bool TryParseSync(byte[] packet, out (int Frame, int SyncHash, ulong DefeatState) data)
{
if (packet.Length != 4 + Order.SyncHashOrderLength || packet[4] != (byte)OrderType.SyncHash)
{
data = (0, 0, 0);
return false;
}
var frame = BitConverter.ToInt32(packet, 0);
var syncHash = BitConverter.ToInt32(packet, 5);
var defeatState = BitConverter.ToUInt64(packet, 9);
data = (frame, syncHash, defeatState);
return true;
}
public static bool TryParsePing(int fromClient, byte[] packet, out byte[] ping)
{
// Valid Ping packets are only ever generated by the server
if (fromClient != 0 || packet.Length != 13 || packet[4] != (byte)OrderType.Ping)
{
ping = null;
return false;
}
// Valid Ping packets always have frame 0
var frame = BitConverter.ToInt32(packet, 0);
if (frame != 0)
{
ping = null;
return false;
}
ping = packet;
return true;
}
public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame, out byte count)
{
// Ack packets are only accepted from the server
if (packet.FromClient != 0 || packet.Data.Length != 6 || packet.Data[4] != (byte)OrderType.Ack)
{
frame = count = 0;
return false;
}
frame = BitConverter.ToInt32(packet.Data, 0);
count = packet.Data[5];
return true;
}
public static bool TryParseOrderPacket(byte[] packet, out (int Frame, OrderPacket Orders) data)
{
// Not a valid packet
if (packet.Length < 4)
{
data = (0, null);
return false;
}
// Wrong packet type
if (packet.Length >= 5 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
{
data = (0, null);
return false;
}
var frame = BitConverter.ToInt32(packet, 0);
// PERF: Skip empty order frames, often per client each frame
var orders = packet.Length > 4 ? new OrderPacket(new MemoryStream(packet, 4, packet.Length - 4)) : NoOrders;
data = (frame, orders);
return true;
}
}
}