1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include <string>
4 #include <iostream>
5 #include <boost/program_options.hpp>
6 #include <iomanip> //hex
7
8 #include "StringSerializer.h"
9
10 #include "Net/Protocol/BaseNetProtocol.h"
11 #include "System/LoadSave/DemoReader.h"
12 #include "System/Net/RawPacket.h"
13 #include "Sim/Units/CommandAI/Command.h"
14
15 namespace po = boost::program_options;
16
17 /*
18 Usage:
19 Start with the full! path to the demofile as the only argument
20
21 Please note that not all NETMSG's are implemented, expand if needed.
22
23 When compiling for windows with MinGW, make sure to use the
24 -Wl,-subsystem,console flag when linking, as otherwise there will be
25 no console output (you still could use this.exe > z.tzt though).
26 */
27
28 void TrafficDump(CDemoReader& reader, bool trafficStats);
29 void WriteTeamstatHistory(CDemoReader& reader, unsigned team, const std::string& file);
30
main(int argc,char * argv[])31 int main (int argc, char* argv[])
32 {
33 std::string filename;
34 po::variables_map vm;
35
36 po::options_description all;
37 all.add_options()("demofile,f", po::value<std::string>(), "Path to demo file");
38 po::positional_options_description p;
39 p.add("demofile", 1);
40 all.add_options()("help,h", "This one");
41 all.add_options()("dump,d", "Only dump networc traffic saved in demo");
42 all.add_options()("stats,s", "Print all game, player and team stats");
43 all.add_options()("header,H", "Print demoheader content");
44 all.add_options()("playerstats,p", "Print playerstats");
45 all.add_options()("teamstats,t", "Print teamstats");
46 all.add_options()("team", po::value<unsigned>(), "Select team");
47 all.add_options()("teamsstatcsv", po::value<std::string>(), "Write teamstats in a csv file");
48
49 po::store(po::command_line_parser(argc, argv).options(all).positional(p).run(), vm);
50 po::notify(vm);
51
52 if (vm.count("help"))
53 {
54 std::cout << "demotool Usage: " << std::endl;
55 all.print(std::cout);
56 std::cout << "example: demotool myReplay.sdf -d > myReplay_sdf_demotool.txt" << std::endl;
57 return 0;
58 }
59 if (vm.count("demofile"))
60 {
61 filename = vm["demofile"].as<std::string>();
62 }
63 else
64 {
65 std::cout << "No demofile given" << std::endl;
66 all.print(std::cout);
67 return 1;
68 }
69
70 const bool printStats = vm.count("stats");
71 CDemoReader reader(filename, 0.0f);
72 reader.LoadStats();
73 if (vm.count("dump"))
74 {
75 TrafficDump(reader, true);
76 return 0;
77 }
78 if (vm.count("teamsstatcsv"))
79 {
80 const std::string outfile = vm["teamsstatcsv"].as<std::string>();
81 if (!vm.count("team"))
82 {
83 std::cout << "teamsstatcsv requires a team to select" << std::endl;
84 exit(1);
85 }
86 unsigned team = vm["team"].as<unsigned>();
87 WriteTeamstatHistory(reader, team, outfile);
88 }
89
90 if (vm.count("header") || printStats)
91 {
92 wstringstream buf;
93 buf << reader.GetFileHeader();
94 std::wcout << buf.str();
95 }
96 if (vm.count("playerstats") || printStats)
97 {
98 const std::vector<PlayerStatistics> statvec = reader.GetPlayerStats();
99 for (unsigned i = 0; i < statvec.size(); ++i)
100 {
101 std::wcout << L"-- Player statistics for player " << i << L" --" << std::endl;
102 wstringstream buf;
103 buf << statvec[i];
104 std::wcout << buf.str();
105 }
106 }
107 if (vm.count("teamstats") || printStats)
108 {
109 const DemoFileHeader header = reader.GetFileHeader();
110 const std::vector< std::vector<TeamStatistics> > statvec = reader.GetTeamStats();
111 for (unsigned teamNum = 0; teamNum < statvec.size(); ++teamNum)
112 {
113 int time = 0;
114 for (unsigned i = 0; i < statvec[teamNum].size(); ++i)
115 {
116 std::wcout << L"-- Team statistics for player " << teamNum << L", game second " << time << L" --" << std::endl;
117 wstringstream buf;
118 buf << statvec[teamNum][i];
119 time += header.teamStatPeriod;
120 std::wcout << buf.str();
121 }
122 }
123 }
124 return 0;
125 }
126
127
128 static std::map<int, std::string> cmdIdToName;
129
InitCommandNames()130 void InitCommandNames()
131 {
132 #define REGISTER_CMD(cmdDefine) \
133 cmdIdToName[cmdDefine] = #cmdDefine;
134
135 REGISTER_CMD(CMD_STOP)
136 REGISTER_CMD(CMD_INSERT)
137 REGISTER_CMD(CMD_REMOVE)
138 REGISTER_CMD(CMD_WAIT)
139 REGISTER_CMD(CMD_TIMEWAIT)
140 REGISTER_CMD(CMD_DEATHWAIT)
141 REGISTER_CMD(CMD_SQUADWAIT)
142 REGISTER_CMD(CMD_GATHERWAIT)
143 REGISTER_CMD(CMD_MOVE)
144 REGISTER_CMD(CMD_PATROL)
145 REGISTER_CMD(CMD_FIGHT)
146 REGISTER_CMD(CMD_ATTACK)
147 REGISTER_CMD(CMD_AREA_ATTACK)
148 REGISTER_CMD(CMD_GUARD)
149 REGISTER_CMD(CMD_AISELECT)
150 REGISTER_CMD(CMD_GROUPSELECT)
151 REGISTER_CMD(CMD_GROUPADD)
152 REGISTER_CMD(CMD_GROUPCLEAR)
153 REGISTER_CMD(CMD_REPAIR)
154 REGISTER_CMD(CMD_FIRE_STATE)
155 REGISTER_CMD(CMD_MOVE_STATE)
156 REGISTER_CMD(CMD_SETBASE)
157 REGISTER_CMD(CMD_INTERNAL)
158 REGISTER_CMD(CMD_SELFD)
159 REGISTER_CMD(CMD_SET_WANTED_MAX_SPEED)
160 REGISTER_CMD(CMD_LOAD_UNITS)
161 REGISTER_CMD(CMD_LOAD_ONTO)
162 REGISTER_CMD(CMD_UNLOAD_UNITS)
163 REGISTER_CMD(CMD_UNLOAD_UNIT)
164 REGISTER_CMD(CMD_ONOFF)
165 REGISTER_CMD(CMD_RECLAIM)
166 REGISTER_CMD(CMD_CLOAK)
167 REGISTER_CMD(CMD_STOCKPILE)
168 REGISTER_CMD(CMD_MANUALFIRE)
169 REGISTER_CMD(CMD_RESTORE)
170 REGISTER_CMD(CMD_REPEAT)
171 REGISTER_CMD(CMD_TRAJECTORY)
172 REGISTER_CMD(CMD_RESURRECT)
173 REGISTER_CMD(CMD_CAPTURE)
174 REGISTER_CMD(CMD_AUTOREPAIRLEVEL)
175 REGISTER_CMD(CMD_IDLEMODE)
176 REGISTER_CMD(CMD_FAILED)
177
178 #undef REGISTER_CMD
179 }
180
GetCommandName(int commandId)181 const std::string& GetCommandName(int commandId)
182 {
183 static const std::string CMD_NAME_BUILD_UNIT = "<BUILD_UNIT>";
184 static const std::string CMD_NAME_UNKNOWN = "<UNKNOWN>";
185
186 if (commandId < 0) {
187 return CMD_NAME_BUILD_UNIT;
188 }
189
190 const std::map<int, std::string>::const_iterator cmd = cmdIdToName.find(commandId);
191 if (cmd != cmdIdToName.end()) {
192 return cmd->second;
193 }
194
195 return CMD_NAME_UNKNOWN;
196 }
197
PrintBinary(void * buf,int len)198 void PrintBinary(void* buf, int len)
199 {
200 for(int i=0; i<len; i++) {
201 std::cout << std::hex << (int)((char*)buf)[i];
202 }
203 std::cout << std::dec; //reset to decimal
204 }
205
TrafficDump(CDemoReader & reader,bool trafficStats)206 void TrafficDump(CDemoReader& reader, bool trafficStats)
207 {
208 InitCommandNames();
209 std::vector<unsigned> trafficCounter(NETMSG_LAST, 0);
210 int frame = 0;
211 int cmdId = 0;
212 while (!reader.ReachedEnd())
213 {
214 netcode::RawPacket* packet;
215 packet = reader.GetData(3.402823466e+38f);
216 if (packet == NULL)
217 continue;
218 assert(packet->data[0]<NETMSG_LAST);
219 trafficCounter[packet->data[0]] += packet->length;
220 const unsigned char* buffer = packet->data;
221 char buf[16]; // FIXME: cba to look up how to format numbers with iostreams
222 sprintf(buf, "%06d ", frame);
223 std::cout << buf;
224 const int cmd = (unsigned char)buffer[0];
225 switch (cmd)
226 {
227 case NETMSG_AICOMMAND:
228 std::cout << "AICOMMAND: Playernum: " << (unsigned)buffer[3];
229 std::cout << " Length: " << (unsigned)packet->length;
230 std::cout << " AI id: " << (unsigned)buffer[4];
231 std::cout << " UnitId: " << *((short*)(buffer + 5));
232 cmdId = *((int*)(buffer + 7));
233 std::cout << " CommandId: " << GetCommandName(cmdId) << "(" << cmdId << ")";
234 std::cout << " Options: " << (unsigned)buffer[11];
235 std::cout << " Parameters:";
236 for (unsigned short i = 12; i < packet->length; i += 4) {
237 std::cout << " " << *((float*)(buffer + i));
238 }
239 std::cout << std::endl;
240 break;
241 case NETMSG_AICOMMANDS: {
242 std::cout << "AICOMMANDS: Playernum: " << (unsigned)buffer[3];
243 std::cout << " Length: " << (unsigned)packet->length;
244 std::cout << " AI id: " << (unsigned)buffer[4];
245 std::cout << " Pair: " << (unsigned)buffer[5];
246 unsigned int sameid = *((unsigned int*)(buffer + 6));
247 std::cout << " SameID: " << sameid;
248 unsigned int sameopt = (unsigned)buffer[10];
249 std::cout << " SameOpt: " << sameopt;
250 unsigned short samesize = *((unsigned short*)(buffer + 11));
251 std::cout << " SameSize: " << samesize;
252 short uidc = *((short*)(buffer + 13));
253 std::cout << " UnitIDCount: " << uidc;
254 for (unsigned int i = 0; i < uidc; ++i) {
255 std::cout << " " << *((short*)(buffer + 15 + i * 2));
256 }
257 short cidc = *((short*)(buffer + 15 + uidc * 2));
258 int startp = 15 + uidc * 2 + 2;
259 std::cout << " CmdIDCount: " << cidc;
260 for (unsigned int i = 0; i < cidc; ++i) {
261 if (sameid == 0) {
262 std::cout << " " << *((unsigned int*)(buffer + startp));
263 startp += 4;
264 }
265 if (sameopt == 0xFF) {
266 std::cout << " " << (unsigned)buffer[startp];
267 startp += 1;
268 }
269 if (sameopt == 0xFFFF) {
270 std::cout << " " << *((unsigned short*)(buffer + startp));
271 startp += 2;
272 }
273 }
274 std::cout << std::endl;
275 break;
276 }
277 case NETMSG_PLAYERNAME:
278 std::cout << "PLAYERNAME: Playernum: " << (unsigned)buffer[2] << " Name: " << buffer+3 << std::endl;
279 break;
280 case NETMSG_SETPLAYERNUM:
281 std::cout << "SETPLAYERNUM: Playernum: " << (unsigned)buffer[1] << std::endl;
282 break;
283 case NETMSG_QUIT:
284 std::cout << "QUIT" << std::endl;
285 break;
286 case NETMSG_STARTPLAYING:
287 std::cout << "STARTPLAYING" << std::endl;
288 break;
289 case NETMSG_STARTPOS:
290 std::cout << "STARTPOS: Playernum: " << (unsigned)buffer[1] << " Team: " << (unsigned)buffer[2] << " Readyness: " << (unsigned)buffer[3] << std::endl;
291 break;
292 case NETMSG_SYSTEMMSG:
293 std::cout << "SYSTEMMSG: Player: " << (unsigned)buffer[3] << " Msg: " << (char*)(buffer+4) << std::endl;
294 break;
295 case NETMSG_CHAT:
296 std::cout << "CHAT: Player: " << (unsigned)buffer[2] << " Msg: " << (char*)(buffer+4) << std::endl;
297 break;
298 case NETMSG_KEYFRAME:
299 std::cout << "KEYFRAME: " << *(int*)(buffer+1) << std::endl;
300 ++frame;
301 if (*(int*)(buffer+1) != frame) {
302 std::cout << "keyframe mismatch!" << std::endl;
303 }
304 break;
305 case NETMSG_NEWFRAME:
306 std::cout << "NEWFRAME" << std::endl;
307 ++frame;
308 break;
309 case NETMSG_PLAYERINFO:
310 std::cout << "NETMSG_PLAYERINFO: Player:" << (int)buffer[1] << " Ping: " << *(uint16_t*)&buffer[6] << std::endl;
311 break;
312 case NETMSG_LUAMSG:
313 {
314 std::cout << "LUAMSG length:" << packet->length << " Player:" << (unsigned)buffer[3] << " Script: " << *(uint16_t*)&buffer[4] << " Mode: " << (unsigned)buffer[6] << " Msg: ";
315 PrintBinary(&packet->data[7], packet->length);
316 std::cout << std::endl;
317 break;
318 }
319 case NETMSG_TEAM:
320 std::cout << "TEAM Playernum:" << (int)buffer[1] << " Action:";
321 switch (buffer[2]) {
322 case TEAMMSG_GIVEAWAY: std::cout << "GIVEAWAY"; break;
323 case TEAMMSG_RESIGN: std::cout << "RESIGN"; break;
324 case TEAMMSG_TEAM_DIED: std::cout << "TEAM_DIED"; break;
325 case TEAMMSG_JOIN_TEAM: std::cout << "JOIN_TEAM"; break;
326 default: std::cout << (int)buffer[2];
327 }
328 std::cout << " Parameter:" << (int)buffer[3] << std::endl;
329 break;
330 case NETMSG_COMMAND:
331 std::cout << "COMMAND Playernum:" << (int)buffer[3] << " Size: " << *(unsigned short*)(buffer+1) << std::endl;
332 if (*(unsigned short*)(buffer+1) != packet->length)
333 std::cout << " packet length error: expected: " << *(unsigned short*)(buffer+1) << " got: " << packet->length << std::endl;
334 break;
335 case NETMSG_SELECT:
336 std::cout << "NETMGS_SELECT: Playernum: " << (unsigned)buffer[3];
337 std::cout << " Length: " << (unsigned)packet->length;
338 std::cout << " Unit IDs:";
339 for (unsigned short i = 4; i < packet->length; i += 2) {
340 std::cout << " " << *((short*)(buffer + i));
341 }
342 std::cout << std::endl;
343 break;
344 case NETMSG_GAMEOVER:
345 std::cout << "NETMSG_GAMEOVER" << std::endl;
346 break;
347 case NETMSG_MAPDRAW:
348 std::cout << "NETMSG_MAPDRAW" << std::endl;
349 break;
350 case NETMSG_PATH_CHECKSUM:
351 std::cout << "NETMSG_PATH_CHECKSUM" << std::endl;
352 break;
353 case NETMSG_INTERNAL_SPEED:
354 std::cout << "NETMSG_INTERNAL_SPEED" << std::endl;
355 break;
356 case NETMSG_PLAYERLEFT:
357 std::cout << "NETMSG_PLAYERLEFT" << std::endl;
358 break;
359 case NETMSG_GAMEDATA:
360 std::cout << "NETMSG_GAMEDATA" << std::endl;
361 break;
362 case NETMSG_CREATE_NEWPLAYER:
363 std::cout << "NETMSG_CREATE_NEWPLAYER" << std::endl;
364 break;
365 case NETMSG_GAMEID:
366 std::cout << "NETMSG_GAMEID" << std::endl;
367 break;
368 case NETMSG_RANDSEED:
369 std::cout << "NETMSG_RANDSEED" << std::endl;
370 break;
371 case NETMSG_SHARE:
372 std::cout << "NETMSG_SHARE: Playernum: " << (unsigned)buffer[1];
373 std::cout << " Team: " << (unsigned)buffer[2];
374 std::cout << " ShareUnits: " << (unsigned)buffer[3];
375 std::cout << " Metal: " << *(float*)(buffer + 4);
376 std::cout << " Energy: " << *(float*)(buffer + 8);
377 std::cout << std::endl;
378 break;
379 case NETMSG_CCOMMAND:
380 std::cout << "NETMSG_CCOMMAND: " << std::endl;
381 break;
382 case NETMSG_PAUSE:
383 std::cout << "NETMSG_PAUSE: Player " << (unsigned)buffer[1] << " paused: " << (unsigned)buffer[2] << std::endl;
384 break;
385 case NETMSG_SYNCRESPONSE:
386 std::cout << "NETMSG_SYNCRESPONSE: " << std::endl;
387 break;
388 case NETMSG_DIRECT_CONTROL:
389 std::cout << "NETMSG_DIRECT_CONTROL: " << std::endl;
390 break;
391 case NETMSG_SETSHARE:
392 std::cout << "NETMSG_SETSHARE: " << std::endl;
393 break;
394 default:
395 std::cout << "MSG: " << cmd << std::endl;
396 }
397 delete packet;
398 }
399
400 // how many times did each message appear
401 for (unsigned i = 0; i != trafficCounter.size(); ++i)
402 {
403 if (trafficStats && trafficCounter[i] > 0)
404 std::cout << "Msg " << i << ": " << trafficCounter[i] << std::endl;
405 }
406 }
407
408 template<typename T>
PrintSep(std::ofstream & file,T value)409 void PrintSep(std::ofstream& file, T value)
410 {
411 file << value << ";";
412 }
413
WriteTeamstatHistory(CDemoReader & reader,unsigned team,const std::string & file)414 void WriteTeamstatHistory(CDemoReader& reader, unsigned team, const std::string& file)
415 {
416 const DemoFileHeader header = reader.GetFileHeader();
417 const std::vector< std::vector<TeamStatistics> >& statvec = reader.GetTeamStats();
418 if (team < statvec.size())
419 {
420 int time = 0;
421 std::ofstream out(file.c_str());
422 out << "Team Statistics for " << team << std::endl;
423 out << "Time[sec];MetalUsed;EnergyUsed;MetalProduced;EnergyProduced;MetalExcess;EnergyExcess;"
424 << "EnergyReceived;MetalSent;EnergySent;DamageDealt;DamageReceived;"
425 << "UnitsProduced;UnitsDied;UnitsReceived;UnitsSent;nitsCaptured;"
426 << "UnitsOutCaptured;UnitsKilled" << std::endl;
427 for (unsigned i = 0; i < statvec[team].size(); ++i)
428 {
429 PrintSep(out, time);
430 PrintSep(out, statvec[team][i].metalUsed);
431 PrintSep(out, statvec[team][i].energyUsed);
432 PrintSep(out, statvec[team][i].metalProduced);
433 PrintSep(out, statvec[team][i].energyProduced);
434 PrintSep(out, statvec[team][i].metalExcess);
435 PrintSep(out, statvec[team][i].energyExcess);
436 PrintSep(out, statvec[team][i].metalReceived);
437 PrintSep(out, statvec[team][i].energyReceived);
438 PrintSep(out, statvec[team][i].metalSent);
439 PrintSep(out, statvec[team][i].energySent);
440 PrintSep(out, statvec[team][i].damageDealt);
441 PrintSep(out, statvec[team][i].damageReceived);
442 PrintSep(out, statvec[team][i].unitsProduced);
443 PrintSep(out, statvec[team][i].unitsDied);
444 PrintSep(out, statvec[team][i].unitsReceived);
445 PrintSep(out, statvec[team][i].unitsSent);
446 PrintSep(out, statvec[team][i].unitsCaptured);
447 PrintSep(out, statvec[team][i].unitsOutCaptured);
448 PrintSep(out, statvec[team][i].unitsKilled);
449 out << std::endl;
450 time += header.teamStatPeriod;
451 }
452 }
453 else
454 {
455 std::wcout << L"Invalid teamnumber" << std::endl;
456 exit(1);
457 }
458 };
459