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.
This commit is contained in:
@@ -35,51 +35,63 @@ namespace OpenRA.Network
|
||||
|
||||
public ReplayConnection(string replayFilename)
|
||||
{
|
||||
// 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.
|
||||
using (var rs = File.OpenRead(replayFilename))
|
||||
{
|
||||
var chunk = new Chunk();
|
||||
|
||||
while (rs.Position < rs.Length)
|
||||
{
|
||||
var client = rs.ReadInt32();
|
||||
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);
|
||||
}
|
||||
}
|
||||
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) { }
|
||||
|
||||
@@ -12,12 +12,16 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
class ReplayRecorderConnection : IConnection
|
||||
{
|
||||
public ReplayMetadata Metadata;
|
||||
public WinState LocalGameState = WinState.Undefined;
|
||||
|
||||
IConnection inner;
|
||||
BinaryWriter writer;
|
||||
Func<string> chooseFilename;
|
||||
@@ -101,6 +105,12 @@ namespace OpenRA.Network
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (Metadata != null)
|
||||
{
|
||||
Metadata.FinalizeReplayMetadata(DateTime.UtcNow, LocalGameState);
|
||||
Metadata.Write(writer);
|
||||
}
|
||||
|
||||
writer.Close();
|
||||
inner.Dispose();
|
||||
disposed = true;
|
||||
|
||||
Reference in New Issue
Block a user