Files
OpenRA/OpenRA.Game/Network/ReplayRecorderConnection.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

124 lines
3.0 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2013 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 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;
MemoryStream preStartBuffer = new MemoryStream();
public ReplayRecorderConnection(IConnection inner, Func<string> chooseFilename)
{
this.chooseFilename = chooseFilename;
this.inner = inner;
writer = new BinaryWriter(preStartBuffer);
}
void StartSavingReplay(byte[] initialContent)
{
var filename = chooseFilename();
var mod = Game.modData.Manifest.Mod;
var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
FileStream file = null;
var id = -1;
while (file == null)
{
var fullFilename = Path.Combine(dir, id < 0 ? "{0}.rep".F(filename) : "{0}-{1}.rep".F(filename, id));
id++;
try
{
file = File.Create(fullFilename);
}
catch (IOException) { }
}
file.Write(initialContent);
this.writer = new BinaryWriter(file);
}
public int LocalClientId { get { return inner.LocalClientId; } }
public ConnectionState ConnectionState { get { return inner.ConnectionState; } }
public void Send(int frame, List<byte[]> orders) { inner.Send(frame, orders); }
public void SendImmediate(List<byte[]> orders) { inner.SendImmediate(orders); }
public void SendSync(int frame, byte[] syncData) { inner.SendSync(frame, syncData); }
public void Receive(Action<int, byte[]> packetFn)
{
inner.Receive((client, data) =>
{
if (preStartBuffer != null && IsGameStart(data))
{
writer.Flush();
var preStartData = preStartBuffer.ToArray();
preStartBuffer = null;
StartSavingReplay(preStartData);
}
writer.Write(client);
writer.Write(data.Length);
writer.Write(data);
packetFn(client, data);
});
}
bool IsGameStart(byte[] data)
{
if (data.Length == 5 && data[4] == 0xbf)
return false;
if (data.Length >= 5 && data[4] == 0x65)
return false;
var frame = BitConverter.ToInt32(data, 0);
return frame == 0 && data.ToOrderList(null).Any(o => o.OrderString == "StartGame");
}
bool disposed;
public void Dispose()
{
if (disposed)
return;
if (Metadata != null)
{
Metadata.FinalizeReplayMetadata(DateTime.UtcNow, LocalGameState);
Metadata.Write(writer);
}
writer.Close();
inner.Dispose();
disposed = true;
}
~ReplayRecorderConnection()
{
Dispose();
}
}
}