View Javadoc

1   /*
2    * The ReWinder Project
3    * 
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU General Public License
6    * as published by the Free Software Foundation; either version 2
7    * of the License, or (at your option) any later version.
8   
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13  
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   */
18   
19  package ch.twiddlefinger.inet.rewinder.model.parser;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.util.zip.DataFormatException;
26  import java.util.zip.Inflater;
27  
28  import ch.twiddlefinger.inet.rewinder.model.entities.GameType;
29  import ch.twiddlefinger.inet.rewinder.model.entities.Player;
30  import ch.twiddlefinger.inet.rewinder.model.entities.Team;
31  import ch.twiddlefinger.inet.rewinder.model.parser.conversion.ColorMapper;
32  import ch.twiddlefinger.inet.rewinder.model.replay.WarcraftReplay;
33  import ch.twiddlefinger.inet.rewinder.model.replay.structs.DataBlockHeader;
34  import ch.twiddlefinger.inet.rewinder.model.replay.structs.GameTypeRecord;
35  import ch.twiddlefinger.inet.rewinder.model.replay.structs.Header;
36  import ch.twiddlefinger.inet.rewinder.model.replay.structs.PlayerInfoRecord;
37  import ch.twiddlefinger.inet.rewinder.model.replay.structs.PlayerRecord;
38  import ch.twiddlefinger.inet.rewinder.model.replay.structs.SubHeader;
39  
40  
41  public class ReplayParser {
42      public ReplayParser() {
43          //TODO Constructor
44      }
45  
46      public WarcraftReplay parse(InputStream in) {
47          WarcraftReplay replay = new WarcraftReplay();
48  
49          try {
50              byte[] b = new byte[0x30];
51              in.mark(0);
52              in.read(b);
53  
54              Header h = new Header(b);
55              
56              if(!h.name.get().startsWith("Warcraft")) {
57              	throw new IllegalArgumentException("Sorry, this is not a replay");
58              }
59  
60              //System.out.println("Version: " + h.version.get()); e.g. 1.xx
61              //System.out.println("Tag: " + h.name.get()); e.g. Warcraft III recorded game...
62              //System.out.println("File Size: " + h.filesize.get()); the files's size compressed
63              replay.setFileSize(h.filesize.get());
64  
65              //System.out.println("File Offset: " + h.fileoffset.get()); offset of data header = 0x44
66              b = new byte[0x44];
67              in.read(b);
68  
69              SubHeader sh = new SubHeader(b);
70  
71              //System.out.println("--- SubHeader ---");
72              //System.out.println("Version: " + sh.version.get()); e.g. 1.xx
73              replay.setVersion(sh.version.get());
74  
75              //System.out.println("Build number is: " + sh.build.get());
76              //System.out.println(new StringBuffer(sh.warcraftType.get()).reverse()); e.g. TFT or ROC
77              replay.setWarcraftVersion(new StringBuffer(sh.warcraftType.get()).reverse()
78                                                                               .toString());
79  
80              //System.out.println("Replay length is: " + sh.length.get() + "msec");
81              replay.setLengthMsec(sh.length.get());
82  
83              ///////////////////
84              // Decompression //
85              ///////////////////
86              ByteArrayOutputStream finalData = new ByteArrayOutputStream();
87              seek(in, 0x44);
88  
89              for (int i = 0; i < h.dataBlockCount.get(); i++) {
90                  byte[] dbheader = new byte[0x8];
91                  in.read(dbheader);
92  
93                  DataBlockHeader dbh = new DataBlockHeader(dbheader);
94                  byte[] compressedData = new byte[dbh.blockSize.get()];
95                  byte[] decompressedData = new byte[8000];
96                  in.read(compressedData);
97  
98                  Inflater inflater = new Inflater();
99                  inflater.setInput(compressedData);
100 
101                 try {
102                     inflater.inflate(decompressedData);
103                 } catch (DataFormatException e1) {
104                     e1.printStackTrace();
105                 }
106 
107                 finalData.write(decompressedData);
108             }
109 
110             //Resource.saveData(finalData.toByteArray(), "info.dat");
111 
112             // invalidate input stream cause its no longer needed
113             in = null;
114 
115             ////////////////////////////
116             // Replay Info Decryption //
117             ////////////////////////////
118             InputStream info = new ByteArrayInputStream(finalData.toByteArray());
119             b = new byte[4]; //irrelevant header info
120             info.read(b);
121 
122             ///////////////////////
123             // 1st Player record //
124             ///////////////////////
125             // RecordID and PlayerID
126             ByteArrayOutputStream temp = new ByteArrayOutputStream();
127             b = new byte[2];
128             info.read(b);
129             temp.write(b);
130 
131             // Player name
132             int i = -1;
133 
134             while (i != 0) {
135                 i = info.read();
136                 temp.write(i);
137             }
138 
139             temp.write(info.read());
140 
141             PlayerRecord player = new PlayerRecord(temp.toByteArray());
142 
143             //System.out.println("Player Name: " + player.name.get());
144             //System.out.println("Player ID: " + player.playerID.get());
145             //System.out.println("Record ID: " + (player.recordID.get() == 0x00 ? "Host" : "Additionl Player"));
146             //System.out.println("Game Type: " + (player.gameType.get() == 0x01 ? "Custom" : "BNet"));
147             // Player Race
148             if (player.gameType.get() == 0x01) {
149                 //Custom Game
150                 info.read();
151 
152                 // Break parsing at this point cause custom games aren't supported yet.
153                 throw new IllegalArgumentException(
154                     "Custom games not yet supported");
155             } else {
156                 //Ladder Game
157                 b = new byte[8];
158                 info.read(b);
159 
160                 PlayerInfoRecord pir = new PlayerInfoRecord(b);
161 
162                 //System.out.println("Player Race is: " + pir.getRace());
163                 Player p = new Player(player.playerID.get(), player.name.get(),
164                         pir.getRace());
165                 replay.addPlayer(p);
166             }
167 
168             ///////////////
169             // Game Name //
170             ///////////////
171             ByteArrayOutputStream gnbuffer = new ByteArrayOutputStream();
172             i = -1;
173 
174             while (true) {
175                 i = info.read();
176 
177                 if (i == 0) {
178                     break;
179                 }
180 
181                 gnbuffer.write(i);
182             }
183 
184             replay.setGameName(new String(gnbuffer.toByteArray()));
185 
186             ///////////////////////////////////////////////
187             // Game Infos (speed, flags, observers etc.) //
188             ///////////////////////////////////////////////
189             // TODO Decode information here
190             b = new byte[6];
191             info.read(b);
192 
193             //////////////////////////////////////////////////////////////////////////
194             // Maps Specific information, not yet decoded see warcraft.kliegman.com //
195             //////////////////////////////////////////////////////////////////////////
196             b = new byte[10];
197             info.read(b);
198 
199             ///////////////////////////////////////////////////////
200             // Some more unintresting stuff (Map + Creator name) //
201             ///////////////////////////////////////////////////////
202             // read the null terminated string (should be decoded)
203             ByteArrayOutputStream mcnbuffer = new ByteArrayOutputStream();
204             i = -1;
205 
206             while (true) {
207                 i = info.read();
208 
209                 if (i == 0) {
210                     break;
211                 }
212 
213                 gnbuffer.write(i);
214             }
215 
216             ///////////////////////
217             // Number of Players //
218             ///////////////////////
219             // takes up 4 bytes
220             b = new byte[4];
221             info.read(b);
222             // Doesn't have to be set because after we read all
223             // the infos about all players we actually now how 
224             // many players there are.
225             // replay.setPlayerCount(b[0]);
226 
227             ///////////////////////////
228             // Game type definitions //
229             ///////////////////////////
230             b = new byte[4];
231             info.read(b);
232 
233             GameTypeRecord gtr = new GameTypeRecord(b);
234             replay.setGameType(new GameType(gtr.getGameType()));
235 
236             //////////////////////////////////
237             // LanguageID (not of interest) //
238             //////////////////////////////////
239             b = new byte[4];
240             info.read(b);
241 
242             //////////////////////////////////
243             // Read the rest of the players //
244             //////////////////////////////////
245             while (true) {
246                 ByteArrayOutputStream pnbuffer = new ByteArrayOutputStream();
247 
248                 if (info.read() != 0x16) {
249                     break; // no additional players
250                 }
251 
252                 int playerID = info.read(); // the second byte is playerID
253 
254                 // now comes the player name, read until zero terminator
255                 int k = -1;
256 
257                 while (true) {
258                     k = info.read();
259 
260                     if (k == 0x00) {
261                         break; // we reached the end
262                     }
263 
264                     pnbuffer.write(k);
265                 }
266 
267                 info.skip(1); //read a byte which is usuall 0x08 for ladder games
268                 info.skip(4); // time of how long the players wc3.exe was running (msec)
269 
270                 int playerRace = info.read(); // there are 4 bytes, but we are only interested in the first one
271                 info.skip(3);
272 
273                 info.skip(4); // these bytes are always 00 00 00 
274 
275                 Player p = new Player(playerID,
276                         new String(pnbuffer.toByteArray()),
277                         PlayerInfoRecord.getRace(playerRace));
278                 replay.addPlayer(p);
279             }
280 
281             //////////////////////////////////////////////
282             // Additional information about the players //
283             //////////////////////////////////////////////
284             b = new byte[3];
285             info.read(b);
286 
287             int numSlots = b[2];
288 
289             for (int k = 0; k < numSlots; k++) {
290                 b = new byte[9];
291                 info.read(b);
292 
293                 if (b[0] == 0x00) {
294                     continue; // irrelevant player slots (don't actually belong to a player)
295                 }
296 
297                 //System.out.println(PlayerInfoRecord.getRace(b[0x06]));
298 
299                 Player p = replay.getPlayerByID(b[0x00]);
300                 Team t = replay.getTeamByID(b[0x04]);
301                 
302                 // Set player color
303                 p.setColor(ColorMapper.getColor(b[0x05]));
304                 // assign the player to a team
305                 t.addPlayer(p);
306                 
307             }
308         } catch (IOException e) {
309             e.printStackTrace();
310         }
311 
312         return replay;
313     }
314 
315     private void seek(InputStream in, int pos) throws IOException {
316         in.reset();
317         in.read(new byte[pos]);
318     }
319 }