1 /* packet-quakeworld.c
2  * Routines for QuakeWorld packet dissection
3  *
4  * Uwe Girlich <uwe@planetquake.com>
5  *	http://www.idsoftware.com/q1source/q1source.zip
6  *
7  * Wireshark - Network traffic analyzer
8  * By Gerald Combs <gerald@wireshark.org>
9  * Copyright 1998 Gerald Combs
10  *
11  * Copied from packet-quake.c
12  *
13  * SPDX-License-Identifier: GPL-2.0-or-later
14  */
15 
16 #include "config.h"
17 
18 #include <stdlib.h>
19 #include <epan/packet.h>
20 #include <epan/prefs.h>
21 #include <epan/expert.h>
22 
23 #include <wsutil/strtoi.h>
24 
25 void proto_register_quakeworld(void);
26 void proto_reg_handoff_quakeworld(void);
27 
28 static int proto_quakeworld = -1;
29 
30 static int hf_quakeworld_s2c = -1;
31 static int hf_quakeworld_c2s = -1;
32 static int hf_quakeworld_connectionless = -1;
33 static int hf_quakeworld_game = -1;
34 static int hf_quakeworld_connectionless_marker = -1;
35 static int hf_quakeworld_connectionless_text = -1;
36 static int hf_quakeworld_connectionless_command = -1;
37 static int hf_quakeworld_connectionless_arguments = -1;
38 static int hf_quakeworld_connectionless_connect_version = -1;
39 static int hf_quakeworld_connectionless_connect_qport = -1;
40 static int hf_quakeworld_connectionless_connect_challenge = -1;
41 static int hf_quakeworld_connectionless_connect_infostring = -1;
42 static int hf_quakeworld_connectionless_connect_infostring_key_value = -1;
43 static int hf_quakeworld_connectionless_connect_infostring_key = -1;
44 static int hf_quakeworld_connectionless_connect_infostring_value = -1;
45 static int hf_quakeworld_connectionless_rcon_password = -1;
46 static int hf_quakeworld_connectionless_rcon_command = -1;
47 static int hf_quakeworld_game_seq1 = -1;
48 static int hf_quakeworld_game_rel1 = -1;
49 static int hf_quakeworld_game_seq2 = -1;
50 static int hf_quakeworld_game_rel2 = -1;
51 static int hf_quakeworld_game_qport = -1;
52 
53 static gint ett_quakeworld = -1;
54 static gint ett_quakeworld_connectionless = -1;
55 static gint ett_quakeworld_connectionless_text = -1;
56 static gint ett_quakeworld_connectionless_arguments = -1;
57 static gint ett_quakeworld_connectionless_connect_infostring = -1;
58 static gint ett_quakeworld_connectionless_connect_infostring_key_value = -1;
59 static gint ett_quakeworld_game = -1;
60 static gint ett_quakeworld_game_seq1 = -1;
61 static gint ett_quakeworld_game_seq2 = -1;
62 static gint ett_quakeworld_game_clc = -1;
63 static gint ett_quakeworld_game_svc = -1;
64 
65 static expert_field ei_quakeworld_connectionless_command_invalid = EI_INIT;
66 
67 /*
68 	helper functions, they may ave to go somewhere else
69 	they are mostly copied without change from
70 	  quakeworldsource/client/cmd.c
71 	  quakeworldsource/client/common.c
72 */
73 
74 #define MAX_TEXT_SIZE	2048
75 
76 static const char *
COM_Parse(const char * data,int data_len,int * token_start,int * token_len)77 COM_Parse (const char *data, int data_len, int* token_start, int* token_len)
78 {
79 	int c;
80 	char* com_token = (char*)wmem_alloc(wmem_packet_scope(), data_len+1);
81 
82 	com_token[0] = '\0';
83 	*token_start = 0;
84 	*token_len = 0;
85 
86 	if (data == NULL)
87 		return NULL;
88 
89 	/* skip whitespace */
90 skipwhite:
91 	while (TRUE) {
92 		c = *data;
93 		if (c == '\0')
94 			return NULL;	/* end of file; */
95 		if ((c != ' ') && (!g_ascii_iscntrl(c)))
96 		    break;
97 		data++;
98 		(*token_start)++;
99 	}
100 
101 	/* skip // comments */
102 	if ((c=='/') && (data[1]=='/')) {
103 		while (*data && *data != '\n'){
104 			data++;
105 			(*token_start)++;
106 		}
107 		goto skipwhite;
108 	}
109 
110 	/* handle quoted strings specially */
111 	if (c == '\"') {
112 		data++;
113 		(*token_start)++;
114 		while (*token_len < data_len) {
115 			c = *data++;
116 			if ((c=='\"') || (c=='\0')) {
117 				com_token[*token_len] = '\0';
118 				return data;
119 			}
120 			com_token[*token_len] = c;
121 			(*token_len)++;
122 		}
123 	}
124 
125 	if (*token_len == data_len) {
126 		com_token[*token_len] = '\0';
127 		return data;
128 	}
129 
130 	/* parse a regular word */
131 	do {
132 		com_token[*token_len] = c;
133 		data++;
134 		(*token_len)++;
135 		c = *data;
136 	} while (( c != ' ') && (!g_ascii_iscntrl(c)) && (*token_len < data_len));
137 
138 	com_token[*token_len] = '\0';
139 	return data;
140 }
141 
142 
143 #define			MAX_ARGS 80
144 static	int		cmd_argc = 0;
145 static	const char	*cmd_argv[MAX_ARGS];
146 static	const char	*cmd_null_string = "";
147 static	int		cmd_argv_start[MAX_ARGS];
148 static	int		cmd_argv_length[MAX_ARGS];
149 
150 
151 
152 static int
Cmd_Argc(void)153 Cmd_Argc(void)
154 {
155 	return cmd_argc;
156 }
157 
158 
159 static const char*
Cmd_Argv(int arg)160 Cmd_Argv(int arg)
161 {
162 	if ( arg >= cmd_argc )
163 		return cmd_null_string;
164 	return cmd_argv[arg];
165 }
166 
167 
168 static int
Cmd_Argv_start(int arg)169 Cmd_Argv_start(int arg)
170 {
171 	if ( arg >= cmd_argc )
172 		return 0;
173 	return cmd_argv_start[arg];
174 }
175 
176 
177 static int
Cmd_Argv_length(int arg)178 Cmd_Argv_length(int arg)
179 {
180 	if ( arg >= cmd_argc )
181 		return 0;
182 	return cmd_argv_length[arg];
183 }
184 
185 
186 static void
Cmd_TokenizeString(const char * text,int text_len)187 Cmd_TokenizeString(const char* text, int text_len)
188 {
189 	int start;
190 	int com_token_start;
191 	int com_token_length;
192 	cmd_argc = 0;
193 
194 	start = 0;
195 	while (start < text_len) {
196 
197 		/* skip whitespace up to a \n */
198 		while (*text && *text <= ' ' && *text != '\n' && start < text_len) {
199 			text++;
200 			start++;
201 		}
202 
203 		if (*text == '\n') {
204 			/* a newline separates commands in the buffer */
205 			text++;
206 			break;
207 		}
208 
209 		if ((!*text) || (start == text_len))
210 			return;
211 
212 		text = COM_Parse (text, text_len-start, &com_token_start, &com_token_length);
213 		if (!text)
214 			return;
215 
216 		if (cmd_argc < MAX_ARGS) {
217 			cmd_argv[cmd_argc] = (char*)text;
218 			cmd_argv_start[cmd_argc] = start + com_token_start;
219 			cmd_argv_length[cmd_argc] = com_token_length;
220 			cmd_argc++;
221 		}
222 
223 		start += com_token_start + com_token_length;
224 	}
225 }
226 
227 
228 static void
dissect_id_infostring(tvbuff_t * tvb,proto_tree * tree,int offset,char * infostring,gint ett_key_value,int hf_key_value,int hf_key,int hf_value)229 dissect_id_infostring(tvbuff_t *tvb, proto_tree* tree,
230 	int offset, char* infostring,
231 	gint ett_key_value, int hf_key_value, int hf_key, int hf_value)
232 {
233 	char     *newpos     = infostring;
234 	gboolean end_of_info = FALSE;
235 
236 	/* to look at all the key/value pairs, we destroy infostring */
237 	while(!end_of_info) {
238 		char* keypos;
239 		char* valuepos;
240 		int   keylength;
241 		char* keyvaluesep;
242 		int   valuelength;
243 		char* valueend;
244 
245 		keypos = newpos;
246 		if (*keypos == '\0') break;
247 		if (*keypos == '\\') keypos++;
248 
249 		for (keylength = 0
250 			;
251 			*(keypos + keylength) != '\\' &&
252 			*(keypos + keylength) != '\0'
253 			;
254 			keylength++)
255 		;
256 		keyvaluesep = keypos + keylength;
257 		if (*keyvaluesep == '\0') break;
258 		valuepos = keyvaluesep+1;
259 		for (valuelength = 0
260 			;
261 			*(valuepos + valuelength) != '\\' &&
262 			*(valuepos + valuelength) != '\0'
263 			;
264 			valuelength++)
265 		;
266 		valueend = valuepos + valuelength;
267 		if (*valueend == '\0') {
268 			end_of_info = TRUE;
269 		}
270 		*(keyvaluesep) = '=';
271 		*(valueend) = '\0';
272 
273 		if (tree) {
274 			proto_item* sub_item;
275 			proto_tree* sub_tree;
276 
277 			sub_item = proto_tree_add_string(tree,
278 				hf_key_value,
279 				tvb,
280 				offset + (gint)(keypos-infostring),
281 				keylength + 1 + valuelength, keypos);
282 			sub_tree = proto_item_add_subtree(
283 				sub_item,
284 				ett_key_value);
285 			*(keyvaluesep) = '\0';
286 			proto_tree_add_string(sub_tree,
287 					      hf_key,
288 					      tvb,
289 					      offset + (gint)(keypos-infostring),
290 					      keylength, keypos);
291 			proto_tree_add_string(sub_tree,
292 					      hf_value,
293 					      tvb,
294 					      offset + (gint)(valuepos-infostring),
295 					      valuelength, valuepos);
296 		}
297 		newpos = valueend + 1;
298 	}
299 }
300 
301 
302 static const value_string names_direction[] = {
303 #define DIR_C2S 0
304 	{ DIR_C2S, "Client to Server" },
305 #define DIR_S2C 1
306 	{ DIR_S2C, "Server to Client" },
307 	{ 0, NULL }
308 };
309 
310 
311 /* I took this name and value directly out of the QW source. */
312 #define PORT_MASTER 27500 /* Not IANA registered */
313 static guint gbl_quakeworldServerPort=PORT_MASTER;
314 
315 
316 /* out of band message id bytes (taken out of quakeworldsource/client/protocol.h */
317 
318 /* M = master, S = server, C = client, A = any */
319 /* the second character will allways be \n if the message isn't a single */
320 /* byte long (?? not true anymore?) */
321 
322 #define S2C_CHALLENGE		'c'
323 #define S2C_CONNECTION		'j'
324 #define A2A_PING		'k'	/* respond with an A2A_ACK */
325 #define A2A_ACK			'l'	/* general acknowledgement without info */
326 #define A2A_NACK		'm'	/* [+ comment] general failure */
327 #define A2A_ECHO		'e'	/* for echoing */
328 #define A2C_PRINT		'n'	/* print a message on client */
329 
330 #define S2M_HEARTBEAT		'a'	/* + serverinfo + userlist + fraglist */
331 #define A2C_CLIENT_COMMAND	'B'	/* + command line */
332 #define S2M_SHUTDOWN		'C'
333 
334 
335 static void
dissect_quakeworld_ConnectionlessPacket(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,int direction)336 dissect_quakeworld_ConnectionlessPacket(tvbuff_t *tvb, packet_info *pinfo,
337 	proto_tree *tree, int direction)
338 {
339 	proto_tree	*cl_tree;
340 	proto_tree	*text_tree = NULL;
341 	proto_item	*pi = NULL;
342 	guint8		*text;
343 	int		len;
344 	int		offset;
345 	guint32		marker;
346 	int		command_len;
347 	const char	*command = "";
348 	gboolean	command_finished = FALSE;
349 
350 	marker = tvb_get_ntohl(tvb, 0);
351 	cl_tree = proto_tree_add_subtree(tree, tvb, 0, -1, ett_quakeworld_connectionless, NULL, "Connectionless");
352 
353 	proto_tree_add_uint(cl_tree, hf_quakeworld_connectionless_marker,
354 				tvb, 0, 4, marker);
355 
356 	/* all the rest of the packet is just text */
357 	offset = 4;
358 
359 	text = tvb_get_stringz_enc(wmem_packet_scope(), tvb, offset, &len, ENC_ASCII|ENC_NA);
360 	/* actually, we should look for a eol char and stop already there */
361 
362 	if (cl_tree) {
363 		proto_item *text_item;
364 		text_item = proto_tree_add_string(cl_tree, hf_quakeworld_connectionless_text,
365 						  tvb, offset, len, text);
366 		text_tree = proto_item_add_subtree(text_item, ett_quakeworld_connectionless_text);
367 	}
368 
369 	if (direction == DIR_C2S) {
370 		/* client to server commands */
371 		const char *c;
372 
373 		Cmd_TokenizeString(text, len);
374 		c = Cmd_Argv(0);
375 
376 		/* client to sever commands */
377 		if (strcmp(c,"ping") == 0) {
378 			command = "Ping";
379 			command_len = 4;
380 		} else if (strcmp(c,"status") == 0) {
381 			command = "Status";
382 			command_len = 6;
383 		} else if (strcmp(c,"log") == 0) {
384 			command = "Log";
385 			command_len = 3;
386 		} else if (strcmp(c,"connect") == 0) {
387 			guint32 version = 0;
388 			guint16 qport = 0;
389 			guint32 challenge = 0;
390 			gboolean version_valid = TRUE;
391 			gboolean qport_valid = TRUE;
392 			gboolean challenge_valid = TRUE;
393 			const char *infostring;
394 			proto_tree *argument_tree = NULL;
395 			command = "Connect";
396 			command_len = Cmd_Argv_length(0);
397 			if (text_tree) {
398 				proto_item *argument_item;
399 				pi = proto_tree_add_string(text_tree, hf_quakeworld_connectionless_command,
400 					tvb, offset, command_len, command);
401 				argument_item = proto_tree_add_string(text_tree,
402 					hf_quakeworld_connectionless_arguments,
403 					tvb, offset + Cmd_Argv_start(1), len + 1 - Cmd_Argv_start(1),
404 					text + Cmd_Argv_start(1));
405 				argument_tree = proto_item_add_subtree(argument_item,
406 								       ett_quakeworld_connectionless_arguments);
407 				command_finished=TRUE;
408 			}
409 			version_valid = ws_strtou32(Cmd_Argv(1), NULL, &version);
410 			qport_valid = ws_strtou16(Cmd_Argv(2), NULL, &qport);
411 			challenge_valid = ws_strtou32(Cmd_Argv(3), NULL, &challenge);
412 			infostring = Cmd_Argv(4);
413 
414 			if (text_tree && (!version_valid || !qport_valid || !challenge_valid))
415 				expert_add_info(pinfo, pi, &ei_quakeworld_connectionless_command_invalid);
416 
417 			if (argument_tree) {
418 				proto_item *info_item;
419 				proto_tree *info_tree;
420 				proto_tree_add_uint(argument_tree,
421 					hf_quakeworld_connectionless_connect_version,
422 					tvb,
423 					offset + Cmd_Argv_start(1),
424 					Cmd_Argv_length(1), version);
425 				proto_tree_add_uint(argument_tree,
426 					hf_quakeworld_connectionless_connect_qport,
427 					tvb,
428 					offset + Cmd_Argv_start(2),
429 					Cmd_Argv_length(2), qport);
430 				proto_tree_add_int(argument_tree,
431 					hf_quakeworld_connectionless_connect_challenge,
432 					tvb,
433 					offset + Cmd_Argv_start(3),
434 					Cmd_Argv_length(3), challenge);
435 				info_item = proto_tree_add_string(argument_tree,
436 					hf_quakeworld_connectionless_connect_infostring,
437 					tvb,
438 					offset + Cmd_Argv_start(4),
439 					Cmd_Argv_length(4), infostring);
440 				info_tree = proto_item_add_subtree(
441 					info_item, ett_quakeworld_connectionless_connect_infostring);
442 				dissect_id_infostring(tvb, info_tree, offset + Cmd_Argv_start(4),
443 					wmem_strdup(wmem_packet_scope(), infostring),
444 					ett_quakeworld_connectionless_connect_infostring_key_value,
445 					hf_quakeworld_connectionless_connect_infostring_key_value,
446 					hf_quakeworld_connectionless_connect_infostring_key,
447 					hf_quakeworld_connectionless_connect_infostring_value);
448 			}
449 		} else if (strcmp(c,"getchallenge") == 0) {
450 			command = "Get Challenge";
451 			command_len = Cmd_Argv_length(0);
452 		} else if (strcmp(c,"rcon") == 0) {
453 			const char* password;
454 			int i;
455 			char remaining[MAX_TEXT_SIZE+1];
456 			proto_tree *argument_tree = NULL;
457 			command = "Remote Command";
458 			command_len = Cmd_Argv_length(0);
459 			if (text_tree) {
460 				proto_item *argument_item;
461 				proto_tree_add_string(text_tree, hf_quakeworld_connectionless_command,
462 					tvb, offset, command_len, command);
463 				argument_item = proto_tree_add_string(text_tree,
464 					hf_quakeworld_connectionless_arguments,
465 					tvb, offset + Cmd_Argv_start(1), len - Cmd_Argv_start(1),
466 					text + Cmd_Argv_start(1));
467 				argument_tree =	proto_item_add_subtree(argument_item,
468 								       ett_quakeworld_connectionless_arguments);
469 				command_finished=TRUE;
470 			}
471 			password = Cmd_Argv(1);
472 			if (argument_tree) {
473 				proto_tree_add_string(argument_tree,
474 					hf_quakeworld_connectionless_rcon_password,
475 					tvb,
476 					offset + Cmd_Argv_start(1),
477 					Cmd_Argv_length(1), password);
478 			}
479 			remaining[0] = '\0';
480 			for (i=2; i<Cmd_Argc() ; i++) {
481 				(void) g_strlcat (remaining, Cmd_Argv(i), MAX_TEXT_SIZE+1);
482 				(void) g_strlcat (remaining, " ", MAX_TEXT_SIZE+1);
483 			}
484 			if (text_tree) {
485 				proto_tree_add_string(argument_tree,
486 					hf_quakeworld_connectionless_rcon_command,
487 					tvb, offset + Cmd_Argv_start(2),
488 					Cmd_Argv_start(Cmd_Argc()-1) + Cmd_Argv_length(Cmd_Argc()-1) -
489 					Cmd_Argv_start(2),
490 					remaining);
491 			}
492 		} else if (c[0]==A2A_PING && ( c[1]=='\0' || c[1]=='\n')) {
493 			command = "Ping";
494 			command_len = 1;
495 		} else if (c[0]==A2A_ACK && ( c[1]=='\0' || c[1]=='\n')) {
496 			command = "Ack";
497 			command_len = 1;
498 		} else {
499 			command = "Unknown";
500 			command_len = len - 1;
501 		}
502 	}
503 	else {
504 		/* server to client commands */
505 		if (text[0] == S2C_CONNECTION) {
506 			command = "Connected";
507 			command_len = 1;
508 		} else if (text[0] == A2C_CLIENT_COMMAND) {
509 			command = "Client Command";
510 			command_len = 1;
511 			/* stringz (command), stringz (localid) */
512 		} else if (text[0] == A2C_PRINT) {
513 			command = "Print";
514 			command_len = 1;
515 			/* string */
516 		} else if (text[0] == A2A_PING) {
517 			command = "Ping";
518 			command_len = 1;
519 		} else if (text[0] == S2C_CHALLENGE) {
520 			command = "Challenge";
521 			command_len = 1;
522 			/* string, conversion */
523 		} else {
524 			command = "Unknown";
525 			command_len = len - 1;
526 		}
527 	}
528 
529 	col_append_fstr(pinfo->cinfo, COL_INFO, " %s", command);
530 
531 	if (!command_finished) {
532 		proto_tree_add_string(text_tree, hf_quakeworld_connectionless_command,
533 			tvb, offset, command_len, command);
534 	}
535 	/*offset += len;*/
536 }
537 
538 
539 static void
dissect_quakeworld_client_commands(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree)540 dissect_quakeworld_client_commands(tvbuff_t *tvb, packet_info *pinfo,
541 	proto_tree *tree)
542 {
543 	/* If I have too much time at hand, I'll fill it with all
544 	   the information from my QWD specs:
545 		http://www.planetquake.com/demospecs/qwd/
546 	*/
547 	call_data_dissector(tvb, pinfo, tree);
548 }
549 
550 
551 static void
dissect_quakeworld_server_commands(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree)552 dissect_quakeworld_server_commands(tvbuff_t *tvb, packet_info *pinfo,
553 	proto_tree *tree)
554 {
555 	/* If I have too much time at hand, I'll fill it with all
556 	   the information from my QWD specs:
557 		http://www.planetquake.com/demospecs/qwd/
558 	*/
559 	call_data_dissector(tvb, pinfo, tree);
560 }
561 
562 
563 static const value_string names_reliable[] = {
564 	{ 0, "Non Reliable" },
565 	{ 1, "Reliable" },
566 	{ 0, NULL }
567 };
568 
569 
570 static void
dissect_quakeworld_GamePacket(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,int direction)571 dissect_quakeworld_GamePacket(tvbuff_t *tvb, packet_info *pinfo,
572 	proto_tree *tree, int direction)
573 {
574 	proto_tree	*game_tree = NULL;
575 	guint32		seq1;
576 	guint32		seq2;
577 	int		rel1;
578 	int		rel2;
579 	int		offset;
580 	guint		rest_length;
581 
582 	direction = (pinfo->destport == gbl_quakeworldServerPort) ?
583 			DIR_C2S : DIR_S2C;
584 
585 	game_tree = proto_tree_add_subtree(tree, tvb, 0, -1, ett_quakeworld_game, NULL, "Game");
586 
587 	offset = 0;
588 
589 	seq1 = tvb_get_letohl(tvb, offset);
590 	rel1 = seq1 & 0x80000000 ? 1 : 0;
591 	seq1 &= ~0x80000000;
592 	if (game_tree) {
593 		proto_tree *seq1_tree = proto_tree_add_subtree_format(game_tree,
594 							    tvb, offset, 4, ett_quakeworld_game_seq1, NULL, "Current Sequence: %u (%s)",
595 							    seq1, val_to_str(rel1,names_reliable,"%u"));
596 		proto_tree_add_uint(seq1_tree, hf_quakeworld_game_seq1,
597 				    tvb, offset, 4, seq1);
598 		proto_tree_add_boolean(seq1_tree, hf_quakeworld_game_rel1,
599 				       tvb, offset+3, 1, rel1);
600 	}
601 	offset += 4;
602 
603 	seq2 = tvb_get_letohl(tvb, offset);
604 	rel2 = seq2 & 0x80000000 ? 1 : 0;
605 	seq2 &= ~0x80000000;
606 	if (game_tree) {
607 		proto_tree *seq2_tree = proto_tree_add_subtree_format(game_tree,
608 							    tvb, offset, 4, ett_quakeworld_game_seq2, NULL, "Acknowledge Sequence: %u (%s)",
609 							    seq2, val_to_str(rel2,names_reliable,"%u"));
610 		proto_tree_add_uint(seq2_tree, hf_quakeworld_game_seq2, tvb, offset, 4, seq2);
611 		proto_tree_add_boolean(seq2_tree, hf_quakeworld_game_rel2, tvb, offset+3, 1, rel2);
612 	}
613 	offset += 4;
614 
615 	if (direction == DIR_C2S) {
616 		/* client to server */
617 		guint16 qport = tvb_get_letohs(tvb, offset);
618 		if (game_tree) {
619 			proto_tree_add_uint(game_tree, hf_quakeworld_game_qport, tvb, offset, 2, qport);
620 		}
621 		offset +=2;
622 	}
623 
624 	/* all the rest is pure game data */
625 	rest_length = tvb_reported_length(tvb) - offset;
626 	if (rest_length) {
627 		tvbuff_t *next_tvb = tvb_new_subset_remaining(tvb, offset);
628 		proto_tree *c_tree;
629 
630 		if (direction == DIR_C2S) {
631 			c_tree = proto_tree_add_subtree(game_tree, next_tvb,
632 							     0, -1, ett_quakeworld_game_clc, NULL, "Client Commands");
633 			dissect_quakeworld_client_commands(next_tvb, pinfo, c_tree);
634 		}
635 		else {
636 			c_tree = proto_tree_add_subtree(game_tree, next_tvb,
637 							     0, -1, ett_quakeworld_game_svc, NULL, "Server Commands");
638 
639 			dissect_quakeworld_server_commands(next_tvb, pinfo, c_tree);
640 		}
641 	}
642 }
643 
644 
645 static int
dissect_quakeworld(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)646 dissect_quakeworld(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
647 {
648 	proto_tree	*quakeworld_tree = NULL;
649 	int		direction;
650 
651 	direction = (pinfo->destport == gbl_quakeworldServerPort) ?
652 			DIR_C2S : DIR_S2C;
653 
654 	col_set_str(pinfo->cinfo, COL_PROTOCOL, "QUAKEWORLD");
655 	col_add_str(pinfo->cinfo, COL_INFO, val_to_str(direction,
656 			names_direction, "%u"));
657 
658 	if (tree) {
659 		proto_item	*quakeworld_item;
660 		quakeworld_item = proto_tree_add_item(tree, proto_quakeworld,
661 				tvb, 0, -1, ENC_NA);
662 		quakeworld_tree = proto_item_add_subtree(quakeworld_item, ett_quakeworld);
663 		proto_tree_add_uint_format(quakeworld_tree,
664 					   direction == DIR_S2C ?
665 					   hf_quakeworld_s2c :
666 					   hf_quakeworld_c2s,
667 					   tvb, 0, 0, 1,
668 					   "Direction: %s", val_to_str(direction, names_direction, "%u"));
669 	}
670 
671 	if (tvb_get_ntohl(tvb, 0) == 0xffffffff) {
672 		col_append_str(pinfo->cinfo, COL_INFO, " Connectionless");
673 		proto_tree_add_uint_format(quakeworld_tree,
674 				hf_quakeworld_connectionless,
675 				tvb, 0, 0, 1,
676 				"Type: Connectionless");
677 		dissect_quakeworld_ConnectionlessPacket(
678 			tvb, pinfo, quakeworld_tree, direction);
679 	}
680 	else {
681 		col_append_str(pinfo->cinfo, COL_INFO, " Game");
682 		proto_tree_add_uint_format(quakeworld_tree,
683 				hf_quakeworld_game,
684 				tvb, 0, 0, 1,
685 				"Type: Game");
686 		dissect_quakeworld_GamePacket(
687 			tvb, pinfo, quakeworld_tree, direction);
688 	}
689 	return tvb_captured_length(tvb);
690 }
691 
692 static void
apply_quakeworld_prefs(void)693 apply_quakeworld_prefs(void)
694 {
695     /* Port preference used to determine client/server */
696     gbl_quakeworldServerPort = prefs_get_uint_value("quakeworld", "udp.port");
697 }
698 
699 void
proto_register_quakeworld(void)700 proto_register_quakeworld(void)
701 {
702 	expert_module_t* expert_quakeworld;
703 
704 	static hf_register_info hf[] = {
705 		{ &hf_quakeworld_c2s,
706 			{ "Client to Server", "quakeworld.c2s",
707 			FT_UINT32, BASE_DEC, NULL, 0x0,
708 			NULL, HFILL }},
709 		{ &hf_quakeworld_s2c,
710 			{ "Server to Client", "quakeworld.s2c",
711 			FT_UINT32, BASE_DEC, NULL, 0x0,
712 			NULL, HFILL }},
713 		{ &hf_quakeworld_connectionless,
714 			{ "Connectionless", "quakeworld.connectionless",
715 			FT_UINT32, BASE_DEC, NULL, 0x0,
716 			NULL, HFILL }},
717 		{ &hf_quakeworld_game,
718 			{ "Game", "quakeworld.game",
719 			FT_UINT32, BASE_DEC, NULL, 0x0,
720 			NULL, HFILL }},
721 		{ &hf_quakeworld_connectionless_marker,
722 			{ "Marker", "quakeworld.connectionless.marker",
723 			FT_UINT32, BASE_HEX, NULL, 0x0,
724 			NULL, HFILL }},
725 		{ &hf_quakeworld_connectionless_text,
726 			{ "Text", "quakeworld.connectionless.text",
727 			FT_STRING, BASE_NONE, NULL, 0x0,
728 			NULL, HFILL }},
729 		{ &hf_quakeworld_connectionless_command,
730 			{ "Command", "quakeworld.connectionless.command",
731 			FT_STRING, BASE_NONE, NULL, 0x0,
732 			NULL, HFILL }},
733 		{ &hf_quakeworld_connectionless_arguments,
734 			{ "Arguments", "quakeworld.connectionless.arguments",
735 			FT_STRING, BASE_NONE, NULL, 0x0,
736 			NULL, HFILL }},
737 		{ &hf_quakeworld_connectionless_connect_version,
738 			{ "Version", "quakeworld.connectionless.connect.version",
739 			FT_UINT32, BASE_DEC, NULL, 0x0,
740 			"Protocol Version", HFILL }},
741 		{ &hf_quakeworld_connectionless_connect_qport,
742 			{ "QPort", "quakeworld.connectionless.connect.qport",
743 			FT_UINT32, BASE_DEC, NULL, 0x0,
744 			"QPort of the client", HFILL }},
745 		{ &hf_quakeworld_connectionless_connect_challenge,
746 			{ "Challenge", "quakeworld.connectionless.connect.challenge",
747 			FT_INT32, BASE_DEC, NULL, 0x0,
748 			"Challenge from the server", HFILL }},
749 		{ &hf_quakeworld_connectionless_connect_infostring,
750 			{ "Infostring", "quakeworld.connectionless.connect.infostring",
751 			FT_STRING, BASE_NONE, NULL, 0x0,
752 			"Infostring with additional variables", HFILL }},
753 		{ &hf_quakeworld_connectionless_connect_infostring_key_value,
754 			{ "Key/Value", "quakeworld.connectionless.connect.infostring.key_value",
755 			FT_STRING, BASE_NONE, NULL, 0x0,
756 			"Key and Value", HFILL }},
757 		{ &hf_quakeworld_connectionless_connect_infostring_key,
758 			{ "Key", "quakeworld.connectionless.connect.infostring.key",
759 			FT_STRING, BASE_NONE, NULL, 0x0,
760 			"Infostring Key", HFILL }},
761 		{ &hf_quakeworld_connectionless_connect_infostring_value,
762 			{ "Value", "quakeworld.connectionless.connect.infostring.value",
763 			FT_STRING, BASE_NONE, NULL, 0x0,
764 			"Infostring Value", HFILL }},
765 		{ &hf_quakeworld_connectionless_rcon_password,
766 			{ "Password", "quakeworld.connectionless.rcon.password",
767 			FT_STRING, BASE_NONE, NULL, 0x0,
768 			"Rcon Password", HFILL }},
769 		{ &hf_quakeworld_connectionless_rcon_command,
770 			{ "Command", "quakeworld.connectionless.rcon.command",
771 			FT_STRING, BASE_NONE, NULL, 0x0,
772 			NULL, HFILL }},
773 		{ &hf_quakeworld_game_seq1,
774 			{ "Sequence Number", "quakeworld.game.seq1",
775 			FT_UINT32, BASE_DEC, NULL, 0x0,
776 			"Sequence number of the current packet", HFILL }},
777 		{ &hf_quakeworld_game_rel1,
778 			{ "Reliable", "quakeworld.game.rel1",
779 			FT_BOOLEAN, BASE_NONE, NULL, 0x0,
780 			"Packet is reliable and may be retransmitted", HFILL }},
781 		{ &hf_quakeworld_game_seq2,
782 			{ "Sequence Number", "quakeworld.game.seq2",
783 			FT_UINT32, BASE_DEC, NULL, 0x0,
784 			"Sequence number of the last received packet", HFILL }},
785 		{ &hf_quakeworld_game_rel2,
786 			{ "Reliable", "quakeworld.game.rel2",
787 			FT_BOOLEAN, BASE_NONE, NULL, 0x0,
788 			"Packet was reliable and may be retransmitted", HFILL }},
789 		{ &hf_quakeworld_game_qport,
790 			{ "QPort", "quakeworld.game.qport",
791 			FT_UINT32, BASE_DEC, NULL, 0x0,
792 			"QuakeWorld Client Port", HFILL }}
793 	};
794 	static gint *ett[] = {
795 		&ett_quakeworld,
796 		&ett_quakeworld_connectionless,
797 		&ett_quakeworld_connectionless_text,
798 		&ett_quakeworld_connectionless_arguments,
799 		&ett_quakeworld_connectionless_connect_infostring,
800 		&ett_quakeworld_connectionless_connect_infostring_key_value,
801 		&ett_quakeworld_game,
802 		&ett_quakeworld_game_seq1,
803 		&ett_quakeworld_game_seq2,
804 		&ett_quakeworld_game_clc,
805 		&ett_quakeworld_game_svc
806 	};
807 
808 	static ei_register_info ei[] = {
809 		{ &ei_quakeworld_connectionless_command_invalid, { "quakeworld.connectionless.command.invalid",
810 			PI_MALFORMED, PI_ERROR, "Invalid connectionless command", EXPFILL }}
811 	};
812 
813 	proto_quakeworld = proto_register_protocol("QuakeWorld Network Protocol", "QUAKEWORLD", "quakeworld");
814 	proto_register_field_array(proto_quakeworld, hf, array_length(hf));
815 	proto_register_subtree_array(ett, array_length(ett));
816 
817 	/* Register a configuration option for port */
818 	prefs_register_protocol(proto_quakeworld, apply_quakeworld_prefs);
819 
820 	expert_quakeworld = expert_register_protocol(proto_quakeworld);
821 	expert_register_field_array(expert_quakeworld, ei, array_length(ei));
822 }
823 
824 
825 void
proto_reg_handoff_quakeworld(void)826 proto_reg_handoff_quakeworld(void)
827 {
828 	dissector_handle_t quakeworld_handle;
829 
830 	quakeworld_handle = create_dissector_handle(dissect_quakeworld, proto_quakeworld);
831 	dissector_add_uint_with_preference("udp.port", PORT_MASTER, quakeworld_handle);
832         apply_quakeworld_prefs();
833 }
834 
835 /*
836  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
837  *
838  * Local variables:
839  * c-basic-offset: 8
840  * tab-width: 8
841  * indent-tabs-mode: t
842  * End:
843  *
844  * vi: set shiftwidth=8 tabstop=8 noexpandtab:
845  * :indentSize=8:tabSize=8:noTabs=false:
846  */
847