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