Files
OpenRA/OpenRA.Game/Network/ReplayConnection.cs
Pavlos Touboulidis 98a05b61b3 Add metadata block to replays
The replay files are just streams all network communication so to
get any info out of them it is necessary to play back the stream
until the wanted information is reached.

This introduces a new metadata block placed at the end of the
replay files and logic to read the new block, or fall back to
playing back the stream for older files.

The replay browser is also updated to use the metadata information
instead of reading the replay stream directly.
2014-05-22 21:54:14 +03:00

126 lines
3.4 KiB
C#
Executable File

#region Copyright & License Information
/*
* Copyright 2007-2014 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using OpenRA.Primitives;
namespace OpenRA.Network
{
public class ReplayConnection : IConnection
{
class Chunk
{
public int Frame;
public List<Pair<int, byte[]>> Packets = new List<Pair<int, byte[]>>();
}
Queue<Chunk> chunks = new Queue<Chunk>();
List<byte[]> sync = new List<byte[]>();
int ordersFrame;
public int LocalClientId { get { return 0; } }
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
public readonly int TickCount;
public readonly bool IsValid;
public readonly Session LobbyInfo;
public ReplayConnection(string replayFilename)
{
using (var rs = File.OpenRead(replayFilename))
{
Read(rs, ref TickCount, ref IsValid, ref LobbyInfo);
}
ordersFrame = LobbyInfo.GlobalSettings.OrderLatency;
}
public ReplayConnection(FileStream rs)
{
Read(rs, ref TickCount, ref IsValid, ref LobbyInfo);
}
void Read(FileStream rs, ref int TickCount, ref bool IsValid, ref Session LobbyInfo)
{
// Parse replay data into a struct that can be fed to the game in chunks
// to avoid issues with all immediate orders being resolved on the first tick.
var chunk = new Chunk();
while (rs.Position < rs.Length)
{
var client = rs.ReadInt32();
if (client == FileFormats.ReplayMetadata.MetaStartMarker)
break;
var packetLen = rs.ReadInt32();
var packet = rs.ReadBytes(packetLen);
var frame = BitConverter.ToInt32(packet, 0);
chunk.Packets.Add(Pair.New(client, packet));
if (packet.Length == 5 && packet[4] == 0xBF)
continue; // disconnect
else if (packet.Length >= 5 && packet[4] == 0x65)
continue; // sync
else if (frame == 0)
{
// Parse replay metadata from orders stream
var orders = packet.ToOrderList(null);
foreach (var o in orders)
{
if (o.OrderString == "StartGame")
IsValid = true;
else if (o.OrderString == "SyncInfo" && !IsValid)
LobbyInfo = Session.Deserialize(o.TargetString);
}
}
else
{
// Regular order - finalize the chunk
chunk.Frame = frame;
chunks.Enqueue(chunk);
chunk = new Chunk();
TickCount = Math.Max(TickCount, frame);
}
}
}
// Do nothing: ignore locally generated orders
public void Send(int frame, List<byte[]> orders) { }
public void SendImmediate(List<byte[]> orders) { }
public void SendSync(int frame, byte[] syncData)
{
var ms = new MemoryStream();
ms.Write(BitConverter.GetBytes(frame));
ms.Write(syncData);
sync.Add(ms.ToArray());
// Store the current frame so Receive() can return the next chunk of orders.
ordersFrame = frame + LobbyInfo.GlobalSettings.OrderLatency;
}
public void Receive(Action<int, byte[]> packetFn)
{
while (sync.Count != 0)
{
packetFn(LocalClientId, sync[0]);
sync.RemoveAt(0);
}
while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame)
foreach (var o in chunks.Dequeue().Packets)
packetFn(o.First, o.Second);
}
public void Dispose() { }
}
}