//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; }
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)); } } }