2 * Routines for QuakeWorld packet dissection
4 * Uwe Girlich <uwe@planetquake.com>
5 * http://www.idsoftware.com/q1source/q1source.zip
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * Copied from packet-quake.c
13 * SPDX-License-Identifier: GPL-2.0-or-later
19 #include <epan/packet.h>
20 #include <epan/prefs.h>
21 #include <epan/expert.h>
23 #include <wsutil/strtoi.h>
25 void proto_register_quakeworld(void);
26 void proto_reg_handoff_quakeworld(void);
28 static dissector_handle_t quakeworld_handle
;
30 static int proto_quakeworld
;
32 static int hf_quakeworld_s2c
;
33 static int hf_quakeworld_c2s
;
34 static int hf_quakeworld_connectionless
;
35 static int hf_quakeworld_game
;
36 static int hf_quakeworld_connectionless_marker
;
37 static int hf_quakeworld_connectionless_text
;
38 static int hf_quakeworld_connectionless_command
;
39 static int hf_quakeworld_connectionless_arguments
;
40 static int hf_quakeworld_connectionless_connect_version
;
41 static int hf_quakeworld_connectionless_connect_qport
;
42 static int hf_quakeworld_connectionless_connect_challenge
;
43 static int hf_quakeworld_connectionless_connect_infostring
;
44 static int hf_quakeworld_connectionless_connect_infostring_key_value
;
45 static int hf_quakeworld_connectionless_connect_infostring_key
;
46 static int hf_quakeworld_connectionless_connect_infostring_value
;
47 static int hf_quakeworld_connectionless_rcon_password
;
48 static int hf_quakeworld_connectionless_rcon_command
;
49 static int hf_quakeworld_game_seq1
;
50 static int hf_quakeworld_game_rel1
;
51 static int hf_quakeworld_game_seq2
;
52 static int hf_quakeworld_game_rel2
;
53 static int hf_quakeworld_game_qport
;
55 static int ett_quakeworld
;
56 static int ett_quakeworld_connectionless
;
57 static int ett_quakeworld_connectionless_text
;
58 static int ett_quakeworld_connectionless_arguments
;
59 static int ett_quakeworld_connectionless_connect_infostring
;
60 static int ett_quakeworld_connectionless_connect_infostring_key_value
;
61 static int ett_quakeworld_game
;
62 static int ett_quakeworld_game_seq1
;
63 static int ett_quakeworld_game_seq2
;
64 static int ett_quakeworld_game_clc
;
65 static int ett_quakeworld_game_svc
;
67 static expert_field ei_quakeworld_connectionless_command_invalid
;
70 helper functions, they may ave to go somewhere else
71 they are mostly copied without change from
72 quakeworldsource/client/cmd.c
73 quakeworldsource/client/common.c
76 #define MAX_TEXT_SIZE 2048
79 COM_Parse (const char *data
, int data_len
, int* token_start
, int* token_len
)
82 char* com_token
= (char*)wmem_alloc(wmem_packet_scope(), data_len
+1);
96 return NULL
; /* end of file; */
97 if ((c
!= ' ') && (!g_ascii_iscntrl(c
)))
103 /* skip // comments */
104 if ((c
=='/') && (data
[1]=='/')) {
105 while (*data
&& *data
!= '\n'){
112 /* handle quoted strings specially */
116 while (*token_len
< data_len
) {
118 if ((c
=='\"') || (c
=='\0')) {
119 com_token
[*token_len
] = '\0';
122 com_token
[*token_len
] = c
;
127 if (*token_len
== data_len
) {
128 com_token
[*token_len
] = '\0';
132 /* parse a regular word */
134 com_token
[*token_len
] = c
;
138 } while (( c
!= ' ') && (!g_ascii_iscntrl(c
)) && (*token_len
< data_len
));
140 com_token
[*token_len
] = '\0';
147 static const char *cmd_argv
[MAX_ARGS
];
148 static const char *cmd_null_string
= "";
149 static int cmd_argv_start
[MAX_ARGS
];
150 static int cmd_argv_length
[MAX_ARGS
];
164 if ( arg
>= cmd_argc
)
165 return cmd_null_string
;
166 return cmd_argv
[arg
];
171 Cmd_Argv_start(int arg
)
173 if ( arg
>= cmd_argc
)
175 return cmd_argv_start
[arg
];
180 Cmd_Argv_length(int arg
)
182 if ( arg
>= cmd_argc
)
184 return cmd_argv_length
[arg
];
189 Cmd_TokenizeString(const char* text
, int text_len
)
193 int com_token_length
;
197 while (start
< text_len
) {
199 /* skip whitespace up to a \n */
200 while (*text
&& *text
<= ' ' && *text
!= '\n' && start
< text_len
) {
206 /* a newline separates commands in the buffer */
211 if ((!*text
) || (start
== text_len
))
214 text
= COM_Parse (text
, text_len
-start
, &com_token_start
, &com_token_length
);
218 if (cmd_argc
< MAX_ARGS
) {
219 cmd_argv
[cmd_argc
] = (char*)text
;
220 cmd_argv_start
[cmd_argc
] = start
+ com_token_start
;
221 cmd_argv_length
[cmd_argc
] = com_token_length
;
225 start
+= com_token_start
+ com_token_length
;
231 dissect_id_infostring(tvbuff_t
*tvb
, proto_tree
* tree
,
232 int offset
, char* infostring
,
233 int ett_key_value
, int hf_key_value
, int hf_key
, int hf_value
)
235 char *newpos
= infostring
;
236 bool end_of_info
= false;
238 /* to look at all the key/value pairs, we destroy infostring */
239 while(!end_of_info
) {
248 if (*keypos
== '\0') break;
249 if (*keypos
== '\\') keypos
++;
253 *(keypos
+ keylength
) != '\\' &&
254 *(keypos
+ keylength
) != '\0'
258 keyvaluesep
= keypos
+ keylength
;
259 if (*keyvaluesep
== '\0') break;
260 valuepos
= keyvaluesep
+1;
263 *(valuepos
+ valuelength
) != '\\' &&
264 *(valuepos
+ valuelength
) != '\0'
268 valueend
= valuepos
+ valuelength
;
269 if (*valueend
== '\0') {
272 *(keyvaluesep
) = '=';
276 proto_item
* sub_item
;
277 proto_tree
* sub_tree
;
279 sub_item
= proto_tree_add_string(tree
,
282 offset
+ (int)(keypos
-infostring
),
283 keylength
+ 1 + valuelength
, keypos
);
284 sub_tree
= proto_item_add_subtree(
287 *(keyvaluesep
) = '\0';
288 proto_tree_add_string(sub_tree
,
291 offset
+ (int)(keypos
-infostring
),
293 proto_tree_add_string(sub_tree
,
296 offset
+ (int)(valuepos
-infostring
),
297 valuelength
, valuepos
);
299 newpos
= valueend
+ 1;
304 static const value_string names_direction
[] = {
306 { DIR_C2S
, "Client to Server" },
308 { DIR_S2C
, "Server to Client" },
313 /* I took this name and value directly out of the QW source. */
314 #define PORT_MASTER 27500 /* Not IANA registered */
315 static range_t
*gbl_quakeworldServerPorts
;
317 /* out of band message id bytes (taken out of quakeworldsource/client/protocol.h */
319 /* M = master, S = server, C = client, A = any */
320 /* the second character will allways be \n if the message isn't a single */
321 /* byte long (?? not true anymore?) */
323 #define S2C_CHALLENGE 'c'
324 #define S2C_CONNECTION 'j'
325 #define A2A_PING 'k' /* respond with an A2A_ACK */
326 #define A2A_ACK 'l' /* general acknowledgement without info */
327 #define A2A_NACK 'm' /* [+ comment] general failure */
328 #define A2A_ECHO 'e' /* for echoing */
329 #define A2C_PRINT 'n' /* print a message on client */
331 #define S2M_HEARTBEAT 'a' /* + serverinfo + userlist + fraglist */
332 #define A2C_CLIENT_COMMAND 'B' /* + command line */
333 #define S2M_SHUTDOWN 'C'
337 dissect_quakeworld_ConnectionlessPacket(tvbuff_t
*tvb
, packet_info
*pinfo
,
338 proto_tree
*tree
, int direction
)
341 proto_tree
*text_tree
= NULL
;
342 proto_item
*pi
= NULL
;
349 bool command_finished
= false;
351 marker
= tvb_get_ntohl(tvb
, 0);
352 cl_tree
= proto_tree_add_subtree(tree
, tvb
, 0, -1, ett_quakeworld_connectionless
, NULL
, "Connectionless");
354 proto_tree_add_uint(cl_tree
, hf_quakeworld_connectionless_marker
,
357 /* all the rest of the packet is just text */
360 text
= tvb_get_stringz_enc(pinfo
->pool
, tvb
, offset
, &len
, ENC_ASCII
|ENC_NA
);
361 /* actually, we should look for a eol char and stop already there */
364 proto_item
*text_item
;
365 text_item
= proto_tree_add_string(cl_tree
, hf_quakeworld_connectionless_text
,
366 tvb
, offset
, len
, text
);
367 text_tree
= proto_item_add_subtree(text_item
, ett_quakeworld_connectionless_text
);
370 if (direction
== DIR_C2S
) {
371 /* client to server commands */
374 Cmd_TokenizeString(text
, len
);
377 /* client to sever commands */
378 if (strcmp(c
,"ping") == 0) {
381 } else if (strcmp(c
,"status") == 0) {
384 } else if (strcmp(c
,"log") == 0) {
387 } else if (strcmp(c
,"connect") == 0) {
388 uint32_t version
= 0;
390 uint32_t challenge
= 0;
391 bool version_valid
= true;
392 bool qport_valid
= true;
393 bool challenge_valid
= true;
394 const char *infostring
;
395 proto_tree
*argument_tree
= NULL
;
397 command_len
= Cmd_Argv_length(0);
399 proto_item
*argument_item
;
400 pi
= proto_tree_add_string(text_tree
, hf_quakeworld_connectionless_command
,
401 tvb
, offset
, command_len
, command
);
402 argument_item
= proto_tree_add_string(text_tree
,
403 hf_quakeworld_connectionless_arguments
,
404 tvb
, offset
+ Cmd_Argv_start(1), len
+ 1 - Cmd_Argv_start(1),
405 text
+ Cmd_Argv_start(1));
406 argument_tree
= proto_item_add_subtree(argument_item
,
407 ett_quakeworld_connectionless_arguments
);
408 command_finished
=true;
410 version_valid
= ws_strtou32(Cmd_Argv(1), NULL
, &version
);
411 qport_valid
= ws_strtou16(Cmd_Argv(2), NULL
, &qport
);
412 challenge_valid
= ws_strtou32(Cmd_Argv(3), NULL
, &challenge
);
413 infostring
= Cmd_Argv(4);
415 if (text_tree
&& (!version_valid
|| !qport_valid
|| !challenge_valid
))
416 expert_add_info(pinfo
, pi
, &ei_quakeworld_connectionless_command_invalid
);
419 proto_item
*info_item
;
420 proto_tree
*info_tree
;
421 proto_tree_add_uint(argument_tree
,
422 hf_quakeworld_connectionless_connect_version
,
424 offset
+ Cmd_Argv_start(1),
425 Cmd_Argv_length(1), version
);
426 proto_tree_add_uint(argument_tree
,
427 hf_quakeworld_connectionless_connect_qport
,
429 offset
+ Cmd_Argv_start(2),
430 Cmd_Argv_length(2), qport
);
431 proto_tree_add_int(argument_tree
,
432 hf_quakeworld_connectionless_connect_challenge
,
434 offset
+ Cmd_Argv_start(3),
435 Cmd_Argv_length(3), challenge
);
436 info_item
= proto_tree_add_string(argument_tree
,
437 hf_quakeworld_connectionless_connect_infostring
,
439 offset
+ Cmd_Argv_start(4),
440 Cmd_Argv_length(4), infostring
);
441 info_tree
= proto_item_add_subtree(
442 info_item
, ett_quakeworld_connectionless_connect_infostring
);
443 dissect_id_infostring(tvb
, info_tree
, offset
+ Cmd_Argv_start(4),
444 wmem_strdup(pinfo
->pool
, infostring
),
445 ett_quakeworld_connectionless_connect_infostring_key_value
,
446 hf_quakeworld_connectionless_connect_infostring_key_value
,
447 hf_quakeworld_connectionless_connect_infostring_key
,
448 hf_quakeworld_connectionless_connect_infostring_value
);
450 } else if (strcmp(c
,"getchallenge") == 0) {
451 command
= "Get Challenge";
452 command_len
= Cmd_Argv_length(0);
453 } else if (strcmp(c
,"rcon") == 0) {
454 const char* password
;
456 char remaining
[MAX_TEXT_SIZE
+1];
457 proto_tree
*argument_tree
= NULL
;
458 command
= "Remote Command";
459 command_len
= Cmd_Argv_length(0);
461 proto_item
*argument_item
;
462 proto_tree_add_string(text_tree
, hf_quakeworld_connectionless_command
,
463 tvb
, offset
, command_len
, command
);
464 argument_item
= proto_tree_add_string(text_tree
,
465 hf_quakeworld_connectionless_arguments
,
466 tvb
, offset
+ Cmd_Argv_start(1), len
- Cmd_Argv_start(1),
467 text
+ Cmd_Argv_start(1));
468 argument_tree
= proto_item_add_subtree(argument_item
,
469 ett_quakeworld_connectionless_arguments
);
470 command_finished
=true;
472 password
= Cmd_Argv(1);
474 proto_tree_add_string(argument_tree
,
475 hf_quakeworld_connectionless_rcon_password
,
477 offset
+ Cmd_Argv_start(1),
478 Cmd_Argv_length(1), password
);
481 for (i
=2; i
<Cmd_Argc() ; i
++) {
482 (void) g_strlcat (remaining
, Cmd_Argv(i
), MAX_TEXT_SIZE
+1);
483 (void) g_strlcat (remaining
, " ", MAX_TEXT_SIZE
+1);
486 proto_tree_add_string(argument_tree
,
487 hf_quakeworld_connectionless_rcon_command
,
488 tvb
, offset
+ Cmd_Argv_start(2),
489 Cmd_Argv_start(Cmd_Argc()-1) + Cmd_Argv_length(Cmd_Argc()-1) -
493 } else if (c
[0]==A2A_PING
&& ( c
[1]=='\0' || c
[1]=='\n')) {
496 } else if (c
[0]==A2A_ACK
&& ( c
[1]=='\0' || c
[1]=='\n')) {
501 command_len
= len
- 1;
505 /* server to client commands */
506 if (text
[0] == S2C_CONNECTION
) {
507 command
= "Connected";
509 } else if (text
[0] == A2C_CLIENT_COMMAND
) {
510 command
= "Client Command";
512 /* stringz (command), stringz (localid) */
513 } else if (text
[0] == A2C_PRINT
) {
517 } else if (text
[0] == A2A_PING
) {
520 } else if (text
[0] == S2C_CHALLENGE
) {
521 command
= "Challenge";
523 /* string, conversion */
526 command_len
= len
- 1;
530 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " %s", command
);
532 if (!command_finished
) {
533 proto_tree_add_string(text_tree
, hf_quakeworld_connectionless_command
,
534 tvb
, offset
, command_len
, command
);
541 dissect_quakeworld_client_commands(tvbuff_t
*tvb
, packet_info
*pinfo
,
544 /* If I have too much time at hand, I'll fill it with all
545 the information from my QWD specs:
546 http://www.planetquake.com/demospecs/qwd/
548 call_data_dissector(tvb
, pinfo
, tree
);
553 dissect_quakeworld_server_commands(tvbuff_t
*tvb
, packet_info
*pinfo
,
556 /* If I have too much time at hand, I'll fill it with all
557 the information from my QWD specs:
558 http://www.planetquake.com/demospecs/qwd/
560 call_data_dissector(tvb
, pinfo
, tree
);
564 static const value_string names_reliable
[] = {
565 { 0, "Non Reliable" },
572 dissect_quakeworld_GamePacket(tvbuff_t
*tvb
, packet_info
*pinfo
,
573 proto_tree
*tree
, int direction
)
575 proto_tree
*game_tree
= NULL
;
581 unsigned rest_length
;
583 direction
= value_is_in_range(gbl_quakeworldServerPorts
, pinfo
->destport
) ?
586 game_tree
= proto_tree_add_subtree(tree
, tvb
, 0, -1, ett_quakeworld_game
, NULL
, "Game");
590 seq1
= tvb_get_letohl(tvb
, offset
);
591 rel1
= seq1
& 0x80000000 ? 1 : 0;
594 proto_tree
*seq1_tree
= proto_tree_add_subtree_format(game_tree
,
595 tvb
, offset
, 4, ett_quakeworld_game_seq1
, NULL
, "Current Sequence: %u (%s)",
596 seq1
, val_to_str(rel1
,names_reliable
,"%u"));
597 proto_tree_add_uint(seq1_tree
, hf_quakeworld_game_seq1
,
598 tvb
, offset
, 4, seq1
);
599 proto_tree_add_boolean(seq1_tree
, hf_quakeworld_game_rel1
,
600 tvb
, offset
+3, 1, rel1
);
604 seq2
= tvb_get_letohl(tvb
, offset
);
605 rel2
= seq2
& 0x80000000 ? 1 : 0;
608 proto_tree
*seq2_tree
= proto_tree_add_subtree_format(game_tree
,
609 tvb
, offset
, 4, ett_quakeworld_game_seq2
, NULL
, "Acknowledge Sequence: %u (%s)",
610 seq2
, val_to_str(rel2
,names_reliable
,"%u"));
611 proto_tree_add_uint(seq2_tree
, hf_quakeworld_game_seq2
, tvb
, offset
, 4, seq2
);
612 proto_tree_add_boolean(seq2_tree
, hf_quakeworld_game_rel2
, tvb
, offset
+3, 1, rel2
);
616 if (direction
== DIR_C2S
) {
617 /* client to server */
618 uint16_t qport
= tvb_get_letohs(tvb
, offset
);
620 proto_tree_add_uint(game_tree
, hf_quakeworld_game_qport
, tvb
, offset
, 2, qport
);
625 /* all the rest is pure game data */
626 rest_length
= tvb_reported_length(tvb
) - offset
;
628 tvbuff_t
*next_tvb
= tvb_new_subset_remaining(tvb
, offset
);
631 if (direction
== DIR_C2S
) {
632 c_tree
= proto_tree_add_subtree(game_tree
, next_tvb
,
633 0, -1, ett_quakeworld_game_clc
, NULL
, "Client Commands");
634 dissect_quakeworld_client_commands(next_tvb
, pinfo
, c_tree
);
637 c_tree
= proto_tree_add_subtree(game_tree
, next_tvb
,
638 0, -1, ett_quakeworld_game_svc
, NULL
, "Server Commands");
640 dissect_quakeworld_server_commands(next_tvb
, pinfo
, c_tree
);
647 dissect_quakeworld(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data _U_
)
649 proto_tree
*quakeworld_tree
= NULL
;
652 direction
= value_is_in_range(gbl_quakeworldServerPorts
, pinfo
->destport
) ?
655 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "QUAKEWORLD");
656 col_add_str(pinfo
->cinfo
, COL_INFO
, val_to_str(direction
,
657 names_direction
, "%u"));
660 proto_item
*quakeworld_item
;
661 quakeworld_item
= proto_tree_add_item(tree
, proto_quakeworld
,
663 quakeworld_tree
= proto_item_add_subtree(quakeworld_item
, ett_quakeworld
);
664 proto_tree_add_uint_format(quakeworld_tree
,
665 direction
== DIR_S2C
?
669 "Direction: %s", val_to_str(direction
, names_direction
, "%u"));
672 if (tvb_get_ntohl(tvb
, 0) == 0xffffffff) {
673 col_append_str(pinfo
->cinfo
, COL_INFO
, " Connectionless");
674 proto_tree_add_uint_format(quakeworld_tree
,
675 hf_quakeworld_connectionless
,
677 "Type: Connectionless");
678 dissect_quakeworld_ConnectionlessPacket(
679 tvb
, pinfo
, quakeworld_tree
, direction
);
682 col_append_str(pinfo
->cinfo
, COL_INFO
, " Game");
683 proto_tree_add_uint_format(quakeworld_tree
,
687 dissect_quakeworld_GamePacket(
688 tvb
, pinfo
, quakeworld_tree
, direction
);
690 return tvb_captured_length(tvb
);
694 apply_quakeworld_prefs(void)
696 /* Port preference used to determine client/server */
697 gbl_quakeworldServerPorts
= prefs_get_range_value("quakeworld", "udp.port");
701 proto_register_quakeworld(void)
703 expert_module_t
* expert_quakeworld
;
705 static hf_register_info hf
[] = {
706 { &hf_quakeworld_c2s
,
707 { "Client to Server", "quakeworld.c2s",
708 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
710 { &hf_quakeworld_s2c
,
711 { "Server to Client", "quakeworld.s2c",
712 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
714 { &hf_quakeworld_connectionless
,
715 { "Connectionless", "quakeworld.connectionless",
716 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
718 { &hf_quakeworld_game
,
719 { "Game", "quakeworld.game",
720 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
722 { &hf_quakeworld_connectionless_marker
,
723 { "Marker", "quakeworld.connectionless.marker",
724 FT_UINT32
, BASE_HEX
, NULL
, 0x0,
726 { &hf_quakeworld_connectionless_text
,
727 { "Text", "quakeworld.connectionless.text",
728 FT_STRING
, BASE_NONE
, NULL
, 0x0,
730 { &hf_quakeworld_connectionless_command
,
731 { "Command", "quakeworld.connectionless.command",
732 FT_STRING
, BASE_NONE
, NULL
, 0x0,
734 { &hf_quakeworld_connectionless_arguments
,
735 { "Arguments", "quakeworld.connectionless.arguments",
736 FT_STRING
, BASE_NONE
, NULL
, 0x0,
738 { &hf_quakeworld_connectionless_connect_version
,
739 { "Version", "quakeworld.connectionless.connect.version",
740 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
741 "Protocol Version", HFILL
}},
742 { &hf_quakeworld_connectionless_connect_qport
,
743 { "QPort", "quakeworld.connectionless.connect.qport",
744 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
745 "QPort of the client", HFILL
}},
746 { &hf_quakeworld_connectionless_connect_challenge
,
747 { "Challenge", "quakeworld.connectionless.connect.challenge",
748 FT_INT32
, BASE_DEC
, NULL
, 0x0,
749 "Challenge from the server", HFILL
}},
750 { &hf_quakeworld_connectionless_connect_infostring
,
751 { "Infostring", "quakeworld.connectionless.connect.infostring",
752 FT_STRING
, BASE_NONE
, NULL
, 0x0,
753 "Infostring with additional variables", HFILL
}},
754 { &hf_quakeworld_connectionless_connect_infostring_key_value
,
755 { "Key/Value", "quakeworld.connectionless.connect.infostring.key_value",
756 FT_STRING
, BASE_NONE
, NULL
, 0x0,
757 "Key and Value", HFILL
}},
758 { &hf_quakeworld_connectionless_connect_infostring_key
,
759 { "Key", "quakeworld.connectionless.connect.infostring.key",
760 FT_STRING
, BASE_NONE
, NULL
, 0x0,
761 "Infostring Key", HFILL
}},
762 { &hf_quakeworld_connectionless_connect_infostring_value
,
763 { "Value", "quakeworld.connectionless.connect.infostring.value",
764 FT_STRING
, BASE_NONE
, NULL
, 0x0,
765 "Infostring Value", HFILL
}},
766 { &hf_quakeworld_connectionless_rcon_password
,
767 { "Password", "quakeworld.connectionless.rcon.password",
768 FT_STRING
, BASE_NONE
, NULL
, 0x0,
769 "Rcon Password", HFILL
}},
770 { &hf_quakeworld_connectionless_rcon_command
,
771 { "Command", "quakeworld.connectionless.rcon.command",
772 FT_STRING
, BASE_NONE
, NULL
, 0x0,
774 { &hf_quakeworld_game_seq1
,
775 { "Sequence Number", "quakeworld.game.seq1",
776 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
777 "Sequence number of the current packet", HFILL
}},
778 { &hf_quakeworld_game_rel1
,
779 { "Reliable", "quakeworld.game.rel1",
780 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
781 "Packet is reliable and may be retransmitted", HFILL
}},
782 { &hf_quakeworld_game_seq2
,
783 { "Sequence Number", "quakeworld.game.seq2",
784 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
785 "Sequence number of the last received packet", HFILL
}},
786 { &hf_quakeworld_game_rel2
,
787 { "Reliable", "quakeworld.game.rel2",
788 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
789 "Packet was reliable and may be retransmitted", HFILL
}},
790 { &hf_quakeworld_game_qport
,
791 { "QPort", "quakeworld.game.qport",
792 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
793 "QuakeWorld Client Port", HFILL
}}
795 static int *ett
[] = {
797 &ett_quakeworld_connectionless
,
798 &ett_quakeworld_connectionless_text
,
799 &ett_quakeworld_connectionless_arguments
,
800 &ett_quakeworld_connectionless_connect_infostring
,
801 &ett_quakeworld_connectionless_connect_infostring_key_value
,
802 &ett_quakeworld_game
,
803 &ett_quakeworld_game_seq1
,
804 &ett_quakeworld_game_seq2
,
805 &ett_quakeworld_game_clc
,
806 &ett_quakeworld_game_svc
809 static ei_register_info ei
[] = {
810 { &ei_quakeworld_connectionless_command_invalid
, { "quakeworld.connectionless.command.invalid",
811 PI_MALFORMED
, PI_ERROR
, "Invalid connectionless command", EXPFILL
}}
814 proto_quakeworld
= proto_register_protocol("QuakeWorld Network Protocol", "QUAKEWORLD", "quakeworld");
815 proto_register_field_array(proto_quakeworld
, hf
, array_length(hf
));
816 proto_register_subtree_array(ett
, array_length(ett
));
818 /* Register the dissector handle */
819 quakeworld_handle
= register_dissector("quakeworld", dissect_quakeworld
, proto_quakeworld
);
821 /* Register a configuration option for port */
822 prefs_register_protocol(proto_quakeworld
, apply_quakeworld_prefs
);
824 expert_quakeworld
= expert_register_protocol(proto_quakeworld
);
825 expert_register_field_array(expert_quakeworld
, ei
, array_length(ei
));
830 proto_reg_handoff_quakeworld(void)
832 dissector_add_uint_with_preference("udp.port", PORT_MASTER
, quakeworld_handle
);
833 apply_quakeworld_prefs();
837 * Editor modelines - https://www.wireshark.org/tools/modelines.html
842 * indent-tabs-mode: t
845 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
846 * :indentSize=8:tabSize=8:noTabs=false: