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 }