Exemple #1
0
        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;
        }
Exemple #2
0
        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;
        }
Exemple #3
0
        private void ParseTimeSlotBlock(ReplayData replayData, ref int currIndex, ref uint accumulatedReplayTime, byte[] gameData)
        {
            ushort commandByteCount = ByteUtility.ReadWord(gameData, currIndex);
            currIndex += 2;

            if (commandByteCount < 2)
                throw new InvalidDataException(String.Format("Unexpected Command Byte Count (Min. 2). Input: {0}", commandByteCount));

            accumulatedReplayTime += ByteUtility.ReadWord(gameData, currIndex);
            currIndex += 2;

            //Command Data not present if n = 2
            int commandByteEndIndex = currIndex + commandByteCount - 2;

            while (currIndex < commandByteEndIndex) //A single command block may contain multiple actions
            {
                byte playerId = gameData[currIndex];
                currIndex++;
                ushort actionBlockLength = ByteUtility.ReadWord(gameData, currIndex);
                currIndex += 2;

                ParseActionBlock(actionBlockLength, ref currIndex, gameData,replayData);
            }
        }
Exemple #4
0
        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);
        }
Exemple #5
0
        //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;
        }
Exemple #6
0
        private void ParseActionBlock(int actionBlockLength, ref int currIndex, byte[] gameData, ReplayData replayData)
        {
            actionBlockLength += currIndex;
            while(currIndex < actionBlockLength)
            {
                byte actionId = gameData[currIndex];
                currIndex++;

                switch (actionId)
                {
                    case 0x01: //Pause Game
                        break;
                    case 0x02: //Resume Game
                        break;
                    case 0x03: //Set game speed (Single Player)
                        currIndex++;
                        break;
                    case 0x04: //Increase game speed (Single Player)
                        break;
                    case 0x05: //Decrease game speed (Single Player)
                        break;
                    case 0x06: //Save game
                        string saveName = ByteUtility.GetNullTerminatedString(gameData, currIndex, out currIndex);
                        break;
                    case 0x07: //save game finished
                        currIndex += 4;
                        break;
                    case 0x10: //Unit/Building Ability
                        currIndex += 14;
                        break;
                    case 0x11: //Unit/Building Ability with target position
                        currIndex += 22;
                        break;
                    case 0x12: //Unit/Building ability (With target position and target object id)
                        currIndex += 30;
                        break;
                    case 0x13: //Give item to Unit / Drop item on ground
                        currIndex += 38;
                        break;
                    case 0x14: //Unit building ability (With two target positions and two item ids)
                        currIndex += 43;
                        break;
                    case 0x16:
                    case 0x17: //Change selection, assign group hotkey
                        currIndex++; //Select Mode
                        ushort selectionCount = ByteUtility.ReadWord(gameData, currIndex);
                        currIndex += 2;
                        currIndex += selectionCount*8; //n * 8 bytes (Two DWORD repeated);
                        break;
                    case 0x18: //Select Group Hotkey
                        currIndex += 2;
                        break;
                    case 0x19: //Select Subgroup
                        currIndex += 12;
                        break;
                    case 0x1A: //Pre Subselection

                        break;
                    case 0x1B: //Unknown
                        currIndex += 9;
                        break;
                    case 0x1C: //Select Ground Item
                        currIndex += 9;
                        break;
                    case 0x1D: //Cancel Hero Revival
                        currIndex += 8;
                        break;
                    case 0x1E: //Remove unit from building queue
                        currIndex += 5;
                        break;
                    case 0x21: //unknown
                        currIndex += 8;
                        break;
                    case 0x50: //Change ally options
                        currIndex += 5;
                        break;
                    case 0x51: //Transfer Resources
                        currIndex += 9;
                        break;
                    case 0x60: //Map Trigger Chat Command
                        currIndex += 8; //Two Unknown Double Words
                        List<byte> encodedChatBytes = new List<byte>();
                        while (gameData[currIndex] != 0x00)
                        {
                            encodedChatBytes.Add(gameData[currIndex]);
                            currIndex++;
                        }
                        currIndex++;
                        string chatString = Encoding.UTF8.GetString(encodedChatBytes.ToArray());
                        break;
                    case 0x61: //ESC Pressed
                        break;
                    case 0x62: //Scenario Trigger
                        currIndex += 12;
                        break;
                    case 0x66: //Choose hero skill submenu
                        break;
                    case 0x67: //Choose building submenu
                        break;
                    case 0x68: //Minimap Ping
                        currIndex += 12;
                        break;
                    case 0x69: //Continue Game (Block B)
                        currIndex += 16;
                        break;
                    case 0x6A: //Continue Game (Block A)
                        currIndex += 16;
                        break;
                    case 0x75: //Unknown
                        currIndex++;
                        break;
                    case 0x20: //Cheats
                    case 0x22:
                    case 0x23:
                    case 0x24:
                    case 0x25:
                    case 0x26:
                    case 0x29:
                    case 0x2A:
                    case 0x2B:
                    case 0x2C:
                    case 0x2F:
                    case 0x30:
                    case 0x31:
                    case 0x32:
                        break;
                    case 0x70: //SyncStoredInteger. The most important part.
                        string gameCacheName = ByteUtility.GetNullTerminatedString(gameData, currIndex, out currIndex);
                        string eventCategory = ByteUtility.GetNullTerminatedString(gameData, currIndex, out currIndex);
                        string[] eventDetailId = ByteUtility.GetNullTerminatedString(gameData, currIndex, out currIndex).Split(new[] { "/" }, StringSplitOptions.None);
                        string eventId = eventDetailId[0];
                        string eventDetail = string.Join("/",eventDetailId.Skip(1).Take(4));
                        _frsEventCallList.Add(new FRSEvent(eventId, gameCacheName,eventCategory,eventDetail));


                        break;
                    default:
                        throw new Exception(String.Format("Unexpected Action ID found at currIndex: {0} Input: {1}",
                                                          currIndex-1, actionId));
                }
            }
        }