Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
213
OpenRA.Game/Network/OrderIO.cs
Normal file
213
OpenRA.Game/Network/OrderIO.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 MemoryStream data;
|
||||
public OrderPacket(IEnumerable<Order> orders)
|
||||
{
|
||||
// Orders may refer to actors that no longer exist by the time
|
||||
// that the order is resolved. In order to ensure consistent
|
||||
// behaviour between local and remote clients, it is simplest
|
||||
// to always serialize / deserialize orders, instead of storing
|
||||
// the Order objects directly on the local client.
|
||||
data = new MemoryStream();
|
||||
foreach (var o in orders)
|
||||
data.Write(o.Serialize());
|
||||
}
|
||||
|
||||
public OrderPacket(MemoryStream data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public IEnumerable<Order> GetOrders(World world)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
yield break;
|
||||
|
||||
// Order deserialization depends on the current world state,
|
||||
// so must be deferred until we are ready to consume them.
|
||||
data.Position = 0;
|
||||
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)
|
||||
{
|
||||
var ms = new MemoryStream((int)data.Length + 4);
|
||||
ms.Write(frame);
|
||||
|
||||
data.Position = 0;
|
||||
data.CopyTo(ms);
|
||||
|
||||
return ms.GetBuffer();
|
||||
}
|
||||
|
||||
public static OrderPacket Combine(IEnumerable<OrderPacket> packets)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
packet.data.Position = 0;
|
||||
packet.data.CopyTo(ms);
|
||||
}
|
||||
|
||||
return new OrderPacket(ms);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OrderIO
|
||||
{
|
||||
static readonly OrderPacket NoOrders = new([]);
|
||||
|
||||
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
||||
{
|
||||
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
||||
ms.Write(data.Frame);
|
||||
ms.WriteByte((byte)OrderType.SyncHash);
|
||||
ms.Write(data.SyncHash);
|
||||
ms.Write(data.DefeatState);
|
||||
return ms.GetBuffer();
|
||||
}
|
||||
|
||||
public static byte[] SerializePingResponse(long timestamp, byte queueLength)
|
||||
{
|
||||
var ms = new MemoryStream(14);
|
||||
ms.Write(0);
|
||||
ms.WriteByte((byte)OrderType.Ping);
|
||||
ms.Write(timestamp);
|
||||
ms.WriteByte(queueLength);
|
||||
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 TryParseTickScale((int FromClient, byte[] Data) packet, out float scale)
|
||||
{
|
||||
// Valid tick scale commands are only ever generated by the server
|
||||
if (packet.FromClient != 0 || packet.Data.Length != 9 || packet.Data[4] != (byte)OrderType.TickScale)
|
||||
{
|
||||
scale = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Valid tick scale packets always have frame 0
|
||||
var frame = BitConverter.ToInt32(packet.Data, 0);
|
||||
if (frame != 0)
|
||||
{
|
||||
scale = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
scale = BitConverter.ToSingle(packet.Data, 5);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParsePingRequest((int FromClient, byte[] Data) packet, out long timestamp)
|
||||
{
|
||||
// Valid Ping requests are only ever generated by the server
|
||||
if (packet.FromClient != 0 || packet.Data.Length != 13 || packet.Data[4] != (byte)OrderType.Ping)
|
||||
{
|
||||
timestamp = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Valid Ping packets always have frame 0
|
||||
var frame = BitConverter.ToInt32(packet.Data, 0);
|
||||
if (frame != 0)
|
||||
{
|
||||
timestamp = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp = BitConverter.ToInt64(packet.Data, 5);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user