private DataBlock CreateParsedDataBlock(byte[] replayBytes, int currIndex, out int movedIndex) { DataBlock dataBlock = new DataBlock(); if (currIndex >= replayBytes.Length) { movedIndex = currIndex; return null; } ushort compressedDataBlockSize = ByteUtility.ReadWord(replayBytes, currIndex); currIndex += 2; ushort decompressedDataBlockSize = ByteUtility.ReadWord(replayBytes, currIndex); currIndex += 2; uint checkSum = ByteUtility.ReadDoubleWord(replayBytes, currIndex); currIndex += 4; byte[] compressedDataBlockBytes = replayBytes.SubArray(currIndex, compressedDataBlockSize); byte[] decompressedDataBlockBytes = Ionic.Zlib.ZlibStream.UncompressBuffer(compressedDataBlockBytes); dataBlock.CompressedDataBlockSize = compressedDataBlockSize; dataBlock.DecompressedDataBlockSize = decompressedDataBlockSize; dataBlock.CheckSum = checkSum; dataBlock.CompressedDataBlockBytes = compressedDataBlockBytes; dataBlock.DecompressedDataBlockBytes = decompressedDataBlockBytes; currIndex += compressedDataBlockSize; movedIndex = currIndex; return dataBlock; }
private string GetChatMessage(ReplayData replayData, ref int currIndex, byte[] gameData) { int playerId = gameData[currIndex]; currIndex++; PlayerInfo playerInfo = replayData.GetPlayerInfoByPlayerReplayId(playerId); if (playerInfo == null) throw new InvalidDataException( String.Format("Player Id Not found in method ParseGameReplayDataFromBlock. Input : {0}", playerId)); ushort numberOfBytes = ByteUtility.ReadWord(gameData, currIndex); currIndex += 2; byte checkFlag = gameData[currIndex]; currIndex++; uint chatMode = ByteUtility.ReadDoubleWord(gameData, currIndex); currIndex += 4; string chatMessage = String.Empty; //Mix Teams messes up team orientation //For now, not appending all or allied chat until better solution is found //if (chatMode == 0x00) // chatMessage += "[A]"; //else if (chatMode == 0x01) // chatMessage += "[T" + (playerInfo.Team + 1) + "]"; chatMessage += "[" + playerInfo.PlayerName + "]"; List<byte> encodedChatMessage = new List<byte>(); while (gameData[currIndex] != 0x00) { encodedChatMessage.Add(gameData[currIndex]); currIndex++; } chatMessage += Encoding.UTF8.GetString(encodedChatMessage.ToArray()); currIndex++; return chatMessage; }
public ReplayHeader ParseReplayHeader(byte[] totalReplayBytes, out int currIndex) { currIndex = 0; byte[] replayHeaderBytes = totalReplayBytes.SubArray(0, HEADER_SIZE); ReplayHeader parsedReplayHeader = new ReplayHeader(replayHeaderBytes); //Check if the replay file starts with the expected header string of "Warcraft III recorded game" byte[] replayStartHeader = replayHeaderBytes.SubArray(0, REPLAY_HEADER_ST_BYTES.Length); if (!replayStartHeader.SequenceEqual(REPLAY_HEADER_ST_BYTES)) { throw new InvalidDataException(String.Format(ERROR_INVALID_HEADER_START_STRING, Encoding.UTF8.GetString(replayStartHeader))); } currIndex += REPLAY_HEADER_ST_BYTES.Length; //Move 26 characters forward //Get file offset uint replayFileOffset = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); if (replayFileOffset != FILE_OFFSET_FIRST_COMPRESSED_DATA_BLOCK) { throw new InvalidDataException(String.Format(ERROR_INVALID_REPLAY_FILE_OFFSET, BitConverter.ToString(BitConverter.GetBytes(replayFileOffset)))); } currIndex += 4; //Get overall size of compressed file uint compressedFileSize = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); parsedReplayHeader.CompressedFileSize = compressedFileSize; currIndex += 4; //Get replay version uint replayHeaderVersion = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); if (replayHeaderVersion != SUPPORTED_REPLAY_HEADER_VERSION) { throw new InvalidDataException(String.Format(ERROR_INVALID_REPLAY_HEADER_VERSION, BitConverter.ToString(BitConverter.GetBytes(replayHeaderVersion)))); } currIndex += 4; //Get overall size of decompressed file uint decompressedFileSize = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); parsedReplayHeader.DecompressedFileSize = decompressedFileSize; currIndex += 4; //Get total number of compressed data blocks in file uint compressedDataBlockCount = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); parsedReplayHeader.CompressedDataBlockCount = (int)compressedDataBlockCount; currIndex += 4; //Start SubHeaderParsing (Version 1) //Get version identifier (Classic, TFT) string clientType = ByteUtility.ReadDoubleWordString(replayHeaderBytes, currIndex); if (clientType != SUPPORTED_WC3_CLIENT_TYPE) { throw new InvalidDataException(String.Format(ERROR_INVALID_REPLAY_CLIENT_TYPE, clientType)); } currIndex += 4; //Get Client Version Number string replayVersion = ByteUtility.ReadDoubleWordString(replayHeaderBytes, currIndex); double replayVersionValue = 0; Double.TryParse(replayVersion, out replayVersionValue); //For some reason, replays generated by GHost doesn't follow the standard replay format //This part is commented out until I figure out what's going on //if (replayVersionValue < 1.0 && replayVersionValue > 2.0) //{ // throw new InvalidDataException(String.Format(ERROR_INVALID_REPLAY_VERSION, replayVersion)); //} parsedReplayHeader.ReplayVersion = replayVersion; currIndex += 4; ushort buildNumber = ByteUtility.ReadWord(replayHeaderBytes, currIndex); parsedReplayHeader.BuildNumber = buildNumber; currIndex += 2; ushort flag = ByteUtility.ReadWord(replayHeaderBytes, currIndex); if (flag != FLAG_MULTIPLAYER) { //throw new InvalidDataException(String.Format(ERROR_INVALID_GAME_TYPE, BitConverter.ToString(BitConverter.GetBytes(flag)))); } currIndex += 2; uint replayLength = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); parsedReplayHeader.ReplayLength = replayLength; currIndex += 4; uint checksum = ByteUtility.ReadDoubleWord(replayHeaderBytes, currIndex); parsedReplayHeader.CRC32 = checksum; currIndex += 4; return(parsedReplayHeader); }
//Precondition: datablock cannot be null //Parses Gamename, playerNames and the team indices of the player. private void ParseGameHeaderBlock(DataBlock dataBlock, ReplayData replayData, out int endIndex) { //Skip 4 (According to specification, first 4 bytes is unknown) int currIndex = 4; byte[] gameHeaderData = dataBlock.DecompressedDataBlockBytes; byte recordId = gameHeaderData[currIndex]; currIndex++; byte playerId = gameHeaderData[currIndex]; currIndex++; string playerName = ByteUtility.GetNullTerminatedString(gameHeaderData, currIndex, out currIndex); replayData.AddPlayerInfo(new PlayerInfo(playerName,playerId,recordId)); //Custom data byte. We can safely ignore this. Debug.Assert(gameHeaderData[currIndex] == 0x01); currIndex++; //Null byte. Ignore this as well Debug.Assert(gameHeaderData[currIndex] == 0x00); currIndex++; replayData.GameName = ByteUtility.GetNullTerminatedString(gameHeaderData, currIndex, out currIndex); //Null byte. Debug.Assert(gameHeaderData[currIndex] == 0x00); currIndex++; //Refers to Section 4.4, 4.5 (Game Settings, Map&Creator Name) //We don't actually need this information, but it's kept just in case we need it in the future string encodedString = ByteUtility.GetReplayEncodedString(gameHeaderData, currIndex, out currIndex); //According to spec, this is player count but in reality, it's mapslotcount uint mapSlotCount = ByteUtility.ReadDoubleWord(gameHeaderData, currIndex); currIndex += 4; //Game Type byte. Safely skip (Section 4.7) currIndex++; //Private Flag. Safely Skip (Section 4.7) currIndex++; //Unknown Word. Safely Skip (Section 4.7) currIndex += 2; //Language ID. Safely Skip (Section 4.8) currIndex += 4; //Loop until we find all players while (gameHeaderData[currIndex] == 0x16) { recordId = gameHeaderData[currIndex]; currIndex++; playerId = gameHeaderData[currIndex]; currIndex++; playerName = ByteUtility.GetNullTerminatedString(gameHeaderData, currIndex, out currIndex); replayData.AddPlayerInfo(new PlayerInfo(playerName, playerId, recordId)); //Custom data byte. We can safely ignore this. Debug.Assert(gameHeaderData[currIndex] == 0x01); currIndex++; //Skip 4 unknown bytes (Section 4.9) currIndex += 4; //Skip null byte while (gameHeaderData[currIndex] == 0x00) { currIndex++; } } //Skip Record Id (Section 4.10, always 0x19) currIndex++; //Number of data bytes following ushort dataByteCount = ByteUtility.ReadWord(gameHeaderData, currIndex); currIndex += 2; //number of available slots (For fate, always 12) int slotCount = gameHeaderData[currIndex]; currIndex++; //int slotRecordIndex = 0; for (int i = 0; i < slotCount; i++ ) { playerId = gameHeaderData[currIndex]; currIndex++; if (playerId == 0x00) //Computer player. Skip to next one { currIndex += 8; continue; } //Skip map download percent currIndex++; byte slotStatus = gameHeaderData[currIndex]; if (slotStatus == 0x00) //Empty slot. Skip to next one. { currIndex += 7; continue; } currIndex++; //Skip computer player flag currIndex++; byte teamNumber = gameHeaderData[currIndex]; PlayerInfo player = replayData.GetPlayerInfoByPlayerReplayId(playerId); if (player == null) throw new InvalidDataException("Player Id not found! ID: " + playerId); player.Team = teamNumber; replayData.PlayerCount++; currIndex++; //Skip rest of bytes (color, raceflags, AI strength, handicap) currIndex += 4; } //Skip randomseed (Section 4.12) currIndex += 4; //Skip selectMode byte selectMode = gameHeaderData[currIndex]; //For fate, Team & Race is not selectable. currIndex++; //Skip StartSpotCount currIndex++; endIndex = currIndex; }