Skip to content

Commit

Permalink
Merge pull request #91 from Zemill/master
Browse files Browse the repository at this point in the history
feat:  Adds in ParseOption classes
  • Loading branch information
barrett777 authored Jan 21, 2020
2 parents 16218b0 + 78b77c1 commit 9dda6ee
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 168 deletions.
2 changes: 1 addition & 1 deletion Heroes.ReplayParser.ConsoleApplication/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ static void Main(string[] args)

// Attempt to parse the replay
// Ignore errors can be set to true if you want to attempt to parse currently unsupported replays, such as 'VS AI' or 'PTR Region' replays
var (replayParseResult, replay) = DataParser.ParseReplay(randomReplayFileName, ignoreErrors: false, deleteFile: false);
var (replayParseResult, replay) = DataParser.ParseReplay(randomReplayFileName, deleteFile: false, ParseOptions.DefaultParsing);

// If successful, the Replay object now has all currently available information
if (replayParseResult == DataParser.ReplayParseResult.Success)
Expand Down
70 changes: 40 additions & 30 deletions Heroes.ReplayParser/BitReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,20 @@ public class BitReader
public BitReader(Stream stream)
{
this.stream = stream;
this.Cursor = 0;
Cursor = 0;
}

/// <summary>
/// Gets the current cursor position.
/// </summary>
public int Cursor { get; private set; }


/// <summary>
/// Gets a value indicating whether the end of stream has been reached.
/// </summary>
public bool EndOfStream => (this.Cursor >> 3) == this.stream.Length;
public bool EndOfStream => (Cursor >> 3) == stream.Length;


/// <summary>
/// Reads up to 32 bits from the stream, returning them as a uint.
Expand All @@ -56,31 +58,54 @@ public uint Read(int numBits)

while (numBits > 0)
{
var bytePos = this.Cursor & 7;
var bytePos = Cursor & 7;
int bitsLeftInByte = 8 - bytePos;
if (bytePos == 0)
{
this.currentByte = this.stream.ReadByte();
currentByte = stream.ReadByte();
}

var bitsToRead = (bitsLeftInByte > numBits) ? numBits : bitsLeftInByte;

value = (value << bitsToRead) | ((uint)this.currentByte >> bytePos) & ((1u << bitsToRead) - 1u);
this.Cursor += bitsToRead;
value = (value << bitsToRead) | ((uint)currentByte >> bytePos) & ((1u << bitsToRead) - 1u);
Cursor += bitsToRead;
numBits -= bitsToRead;
}

return value;
}

/// <summary>
/// Skip specified number of bits in stream.
/// </summary>
/// <param name="numBits">The number of bits to skip.</param>
public void Skip(int numBits)
{
// todo: calculade number of bytes to skip and just increment this.stream position
while (numBits > 0)
{
var bytePos = Cursor & 7;
int bitsLeftInByte = 8 - bytePos;
if (bytePos == 0)
{
currentByte = stream.ReadByte();
}

var bitsToRead = (bitsLeftInByte > numBits) ? numBits : bitsLeftInByte;

Cursor += bitsToRead;
numBits -= bitsToRead;
}
}

/// <summary>
/// If in the middle of a byte, moves to the start of the next byte.
/// </summary>
public void AlignToByte()
{
if ((this.Cursor & 7) > 0)
if ((Cursor & 7) > 0)
{
this.Cursor = (this.Cursor & 0x7ffffff8) + 8;
Cursor = (Cursor & 0x7ffffff8) + 8;
}
}

Expand All @@ -89,10 +114,7 @@ public void AlignToByte()
/// </summary>
/// <param name="numBits">Number of bits to read, up to 32.</param>
/// <returns>Returns a uint containing the number of bits read.</returns>
public uint Read(uint numBits)
{
return this.Read((int)numBits);
}
public uint Read(uint numBits) => Read((int)numBits);

public bool[] ReadBitArray(uint numBits)
{
Expand All @@ -109,43 +131,31 @@ public bool[] ReadBitArray(uint numBits)
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
public byte ReadByte()
{
return (byte)this.Read(8);
}
public byte ReadByte() => (byte)Read(8);

/// <summary>
/// Reads a single bit from the stream as a boolean.
/// </summary>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
public bool ReadBoolean()
{
return this.Read(1) == 1;
}
public bool ReadBoolean() => Read(1) == 1;

/// <summary>
/// Reads 2 bytes from the stream as a short.
/// </summary>
/// <returns>
/// The <see cref="short"/>.
/// </returns>
public short ReadInt16()
{
return (short)this.Read(16);
}
public short ReadInt16() => (short)Read(16);

/// <summary>
/// Reads 4 bytes from the stream as an int.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public int ReadInt32()
{
return (int)this.Read(32);
}
public int ReadInt32() => (int)Read(32);

/// <summary>
/// Reads an array of bytes from the stream.
Expand All @@ -161,7 +171,7 @@ public byte[] ReadBytes(int bytes)
var buffer = new byte[bytes];
for (int i = 0; i < bytes; i++)
{
buffer[i] = this.ReadByte();
buffer[i] = ReadByte();
}

return buffer;
Expand All @@ -178,7 +188,7 @@ public byte[] ReadBytes(int bytes)
/// </returns>
public string ReadString(int length)
{
var buffer = this.ReadBytes(length);
var buffer = ReadBytes(length);
return Encoding.UTF8.GetString(buffer);
}

Expand Down
126 changes: 67 additions & 59 deletions Heroes.ReplayParser/DataParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ public enum ReplayParseResult
{ "Towers of Doom", new Tuple<double, double, double, double>(4.0, 42.0, 1.03, 0.925) },
{ "Battlefield of Eternity", new Tuple<double, double, double, double>(-5.0, 33.0, 1.09, 0.96) }
};

public static Tuple<ReplayParseResult, Replay> ParseReplay(byte[] bytes, bool ignoreErrors = false, bool allowPTRRegion = false)
public static Tuple<ReplayParseResult, Replay> ParseReplay(byte[] bytes, ParseOptions parseOptions)
{
try
{
Expand All @@ -50,22 +49,20 @@ public static Tuple<ReplayParseResult, Replay> ParseReplay(byte[] bytes, bool ig
// File in the version numbers for later use.
MpqHeader.ParseHeader(replay, bytes);

if (!ignoreErrors && replay.ReplayBuild < 32455)
if (!parseOptions.IgnoreErrors && replay.ReplayBuild < 32455)
return new Tuple<ReplayParseResult, Replay>(ReplayParseResult.PreAlphaWipe, null);

using (var memoryStream = new MemoryStream(bytes))
using (var archive = new MpqArchive(memoryStream))
ParseReplayArchive(replay, archive, ignoreErrors);
ParseReplayArchive(replay, archive, parseOptions);

return ParseReplayResults(replay, ignoreErrors, allowPTRRegion);
return ParseReplayResults(replay, parseOptions.IgnoreErrors, parseOptions.AllowPTR);
}
catch
{
return new Tuple<ReplayParseResult, Replay>(ReplayParseResult.Exception, null);
}
}

public static Tuple<ReplayParseResult, Replay> ParseReplay(string fileName, bool ignoreErrors, bool deleteFile, bool allowPTRRegion = false, bool detailedBattleLobbyParsing = false)
public static Tuple<ReplayParseResult, Replay> ParseReplay(string fileName, bool deleteFile, ParseOptions parseOptions)
{
try
{
Expand All @@ -74,16 +71,16 @@ public static Tuple<ReplayParseResult, Replay> ParseReplay(string fileName, bool
// File in the version numbers for later use.
MpqHeader.ParseHeader(replay, fileName);

if (!ignoreErrors && replay.ReplayBuild < 32455)
if (!parseOptions.IgnoreErrors && replay.ReplayBuild < 32455)
return new Tuple<ReplayParseResult, Replay>(ReplayParseResult.PreAlphaWipe, null);

using (var archive = new MpqArchive(fileName))
ParseReplayArchive(replay, archive, ignoreErrors, detailedBattleLobbyParsing);
ParseReplayArchive(replay, archive, parseOptions);

if (deleteFile)
File.Delete(fileName);

return ParseReplayResults(replay, ignoreErrors, allowPTRRegion);
return ParseReplayResults(replay, parseOptions.IgnoreErrors, parseOptions.AllowPTR);
}
catch
{
Expand Down Expand Up @@ -117,14 +114,14 @@ private static Tuple<ReplayParseResult, Replay> ParseReplayResults(Replay replay
return new Tuple<ReplayParseResult, Replay>(ReplayParseResult.Success, replay);
}

private static void ParseReplayArchive(Replay replay, MpqArchive archive, bool ignoreErrors, bool detailedBattleLobbyParsing = false)
private static void ParseReplayArchive(Replay replay, MpqArchive archive, ParseOptions parseOptions)
{
archive.AddListfileFilenames();

// Replay Details
ReplayDetails.Parse(replay, GetMpqFile(archive, ReplayDetails.FileName), ignoreErrors);
ReplayDetails.Parse(replay, GetMpqFile(archive, ReplayDetails.FileName), parseOptions.IgnoreErrors);

if (!ignoreErrors)
if (!parseOptions.IgnoreErrors)
{
if (replay.Players.Length != 10 || replay.Players.Count(i => i.IsWinner) != 5)
// Filter out 'Try Me' games, any games without 10 players, and incomplete games
Expand All @@ -142,61 +139,72 @@ private static void ParseReplayArchive(Replay replay, MpqArchive archive, bool i

ReplayAttributeEvents.Parse(replay, GetMpqFile(archive, ReplayAttributeEvents.FileName));

replay.TrackerEvents = ReplayTrackerEvents.Parse(GetMpqFile(archive, ReplayTrackerEvents.FileName));

try
{
replay.GameEvents = ReplayGameEvents.Parse(GetMpqFile(archive, ReplayGameEvents.FileName), replay.ClientListByUserID, replay.ReplayBuild, replay.ReplayVersionMajor);
replay.IsGameEventsParsedSuccessfully = true;
}
catch
if (parseOptions.ShouldParseEvents)
{
replay.GameEvents = new List<GameEvent>();
}

{
// Gather talent selections
var talentGameEventsDictionary = replay.GameEvents
.Where(i => i.eventType == GameEventType.CHeroTalentSelectedEvent)
.GroupBy(i => i.player)
.ToDictionary(
i => i.Key,
i => i.Select(j => new Talent { TalentID = (int)j.data.unsignedInt.Value, TimeSpanSelected = j.TimeSpan }).OrderBy(j => j.TimeSpanSelected).ToArray());

foreach (var player in talentGameEventsDictionary.Keys)
player.Talents = talentGameEventsDictionary[player];
}
replay.TrackerEvents = ReplayTrackerEvents.Parse(GetMpqFile(archive, ReplayTrackerEvents.FileName));
try
{
replay.GameEvents = ReplayGameEvents.Parse(GetMpqFile(archive, ReplayGameEvents.FileName), replay.ClientListByUserID, replay.ReplayBuild, replay.ReplayVersionMajor, parseOptions.ShouldParseMouseEvents);
replay.IsGameEventsParsedSuccessfully = true;
}
catch
{
replay.GameEvents = new List<GameEvent>();

// Replay Server Battlelobby
if (!ignoreErrors && archive.Any(i => i.Filename == ReplayServerBattlelobby.FileName))
{
if (detailedBattleLobbyParsing)
ReplayServerBattlelobby.Parse(replay, GetMpqFile(archive, ReplayServerBattlelobby.FileName));
else
ReplayServerBattlelobby.GetBattleTags(replay, GetMpqFile(archive, ReplayServerBattlelobby.FileName));
}
}

// Parse Unit Data using Tracker events
Unit.ParseUnitData(replay);
{
// Gather talent selections
var talentGameEventsDictionary = replay.GameEvents
.Where(i => i.eventType == GameEventType.CHeroTalentSelectedEvent)
.GroupBy(i => i.player)
.ToDictionary(
i => i.Key,
i => i.Select(j => new Talent { TalentID = (int)j.data.unsignedInt.Value, TimeSpanSelected = j.TimeSpan }).OrderBy(j => j.TimeSpanSelected).ToArray());

foreach (var player in talentGameEventsDictionary.Keys)
player.Talents = talentGameEventsDictionary[player];
}
// Replay Server Battlelobby
if (!parseOptions.IgnoreErrors && archive.Any(i => i.Filename == ReplayServerBattlelobby.FileName))
{
if (parseOptions.ShouldParseDetailedBattleLobby)
ReplayServerBattlelobby.Parse(replay, GetMpqFile(archive, ReplayServerBattlelobby.FileName));
else
ReplayServerBattlelobby.GetBattleTags(replay, GetMpqFile(archive, ReplayServerBattlelobby.FileName));
}

// Parse Statistics
if (replay.ReplayBuild >= 40431)
try
// Parse Unit Data using Tracker events
if (parseOptions.ShouldParseUnits)
{
Statistics.Parse(replay);
replay.IsStatisticsParsedSuccessfully = true;
Unit.ParseUnitData(replay);
}
catch

// Parse Statistics
if (parseOptions.ShouldParseStatistics)
{
replay.IsGameEventsParsedSuccessfully = false;
if (replay.ReplayBuild >= 40431)
try
{
Statistics.Parse(replay);
replay.IsStatisticsParsedSuccessfully = true;
}
catch
{
replay.IsGameEventsParsedSuccessfully = false;
}
}

// Replay Message Events
// ReplayMessageEvents.Parse(replay, GetMpqFile(archive, ReplayMessageEvents.FileName));
// Replay Message Events
if (parseOptions.ShouldParseMessageEvents)
{
ReplayMessageEvents.Parse(replay, GetMpqFile(archive, ReplayMessageEvents.FileName));
}

// Replay Resumable Events
// So far it doesn't look like this file has anything we would be interested in
// ReplayResumableEvents.Parse(replay, GetMpqFile(archive, "replay.resumable.events"));
// Replay Resumable Events
// So far it doesn't look like this file has anything we would be interested in
// ReplayResumableEvents.Parse(replay, GetMpqFile(archive, "replay.resumable.events"));
}
}

public static byte[] GetMpqFile(MpqArchive archive, string fileName)
Expand Down
Loading

0 comments on commit 9dda6ee

Please sign in to comment.