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