Files
OpenRA/OpenRA.Game/FileFormats/ReplayMetadata.cs
Pavlos Touboulidis a80c4f086a Add filters to the replay browser dialog
This closes issue #2152. The filters added are:

* Game type (singleplayer / multiplayer)
* Date
* Duration
* Outcome
* Player name

Other changes:

* Added a 'CollapseHiddenChildren' option to the ScrollPanelWidget to
make hidden children take up no space.
* Removed the extension (.rep) from the replay filenames in the
replay browser.
2014-05-22 21:54:14 +03:00

217 lines
5.9 KiB
C#

#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.
*/
using System.Text;
#endregion
using System;
using System.IO;
using OpenRA.Network;
namespace OpenRA.FileFormats
{
public class ReplayMetadata
{
public const int MetaStartMarker = -1; // Must be an invalid replay 'client' value
public const int MetaEndMarker = -2;
public const int MetaVersion = 0x00000001;
public string FilePath { get; private set; }
public DateTime EndTimestampUtc { get; private set; }
public TimeSpan Duration { get { return EndTimestampUtc.Subtract(StartTimestampUtc); } }
public WinState Outcome { get; private set; }
public readonly Lazy<Session> Session;
public readonly DateTime StartTimestampUtc;
readonly string sessionData;
ReplayMetadata()
{
Outcome = WinState.Undefined;
}
public ReplayMetadata(DateTime startGameTimestampUtc, Session session)
: this()
{
if (startGameTimestampUtc.Kind == DateTimeKind.Unspecified)
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "startGameTimestamp");
StartTimestampUtc = startGameTimestampUtc.ToUniversalTime();
sessionData = session.Serialize();
Session = new Lazy<OpenRA.Network.Session>(() => OpenRA.Network.Session.Deserialize(this.sessionData));
}
public void FinalizeReplayMetadata(DateTime endGameTimestampUtc, WinState outcome)
{
if (endGameTimestampUtc.Kind == DateTimeKind.Unspecified)
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "endGameTimestampUtc");
EndTimestampUtc = endGameTimestampUtc.ToUniversalTime();
Outcome = outcome;
}
ReplayMetadata(BinaryReader reader)
: this()
{
// Read start marker
if (reader.ReadInt32() != MetaStartMarker)
throw new InvalidOperationException("Expected MetaStartMarker but found an invalid value.");
// Read version
var version = reader.ReadInt32();
if (version > MetaVersion)
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
// Read start game timestamp
StartTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
// Read end game timestamp
EndTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
// Read game outcome
WinState outcome;
if (Enum.TryParse(ReadUtf8String(reader), true, out outcome))
Outcome = outcome;
// Read session
sessionData = ReadUtf8String(reader);
Session = new Lazy<OpenRA.Network.Session>(() => OpenRA.Network.Session.Deserialize(this.sessionData));
}
public void Write(BinaryWriter writer)
{
// Write start marker & version
writer.Write(MetaStartMarker);
writer.Write(MetaVersion);
// Write data
int dataLength = 0;
{
// Write start game timestamp
writer.Write(StartTimestampUtc.Ticks);
dataLength += sizeof(long);
// Write end game timestamp
writer.Write(EndTimestampUtc.Ticks);
dataLength += sizeof(long);
// Write game outcome
dataLength += WriteUtf8String(writer, Outcome.ToString());
// Write session data
dataLength += WriteUtf8String(writer, sessionData);
}
// Write total length & end marker
writer.Write(dataLength);
writer.Write(MetaEndMarker);
}
public static ReplayMetadata Read(string path, bool enableFallbackMethod = true)
{
Func<DateTime> timestampProvider = () => {
try
{
return File.GetCreationTimeUtc(path);
}
catch
{
return DateTime.MinValue;
}
};
using (var fs = new FileStream(path, FileMode.Open))
{
var o = Read(fs, enableFallbackMethod, timestampProvider);
if (o != null)
o.FilePath = path;
return o;
}
}
static ReplayMetadata Read(FileStream fs, bool enableFallbackMethod, Func<DateTime> fallbackTimestampProvider)
{
using (var reader = new BinaryReader(fs))
{
// Disposing the BinaryReader will dispose the underlying stream
// and we don't want that because ReplayConnection may use the
// stream as well.
//
// Fixed in .NET 4.5.
// See: http://msdn.microsoft.com/en-us/library/gg712804%28v=vs.110%29.aspx
if (fs.CanSeek)
{
fs.Seek(-(4 + 4), SeekOrigin.End);
var dataLength = reader.ReadInt32();
if (reader.ReadInt32() == MetaEndMarker)
{
// go back end marker + length storage + data + version + start marker
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
try
{
return new ReplayMetadata(reader);
}
catch (InvalidOperationException)
{
}
catch (NotSupportedException)
{
}
}
// Reset the stream position or the ReplayConnection will fail later
fs.Seek(0, SeekOrigin.Begin);
}
if (enableFallbackMethod)
{
using (var conn = new ReplayConnection(fs))
{
var replay = new ReplayMetadata(fallbackTimestampProvider(), conn.LobbyInfo);
if (conn.TickCount == 0)
return null;
var seconds = (int)Math.Ceiling((conn.TickCount * Game.NetTickScale) / 25f);
replay.EndTimestampUtc = replay.StartTimestampUtc.AddSeconds(seconds);
return replay;
}
}
}
return null;
}
static int WriteUtf8String(BinaryWriter writer, string text)
{
byte[] bytes;
if (!string.IsNullOrEmpty(text))
bytes = Encoding.UTF8.GetBytes(text);
else
bytes = new byte[0];
writer.Write(bytes.Length);
writer.Write(bytes);
return 4 + bytes.Length;
}
static string ReadUtf8String(BinaryReader reader)
{
return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32()));
}
public MapPreview MapPreview
{
get { return Game.modData.MapCache[Session.Value.GlobalSettings.Map]; }
}
}
}