Files
Cesarus85--mjolnir-vr/source/networking/network_server_message_handler.c
T
MrBruh 5aafe7af9e Mark the second batch's changes to the game's sources as the port's
Several of the fixes taken from the forks changed source/ without the
port: comment that marks a change from the original (the second copy's
datagram ports, the downloaded maps' refusals, the main menu's clock,
the one-magazine HUD states, the render lists' sizes and others), and a
comment still named a function of that fork's. Each change now says so.
2026-10-19 23:06:19 +13:00

2912 lines
92 KiB
C

/*
NETWORK_SERVER_MESSAGE_HANDLER.C
symbols in this file:
0011E890 0090:
_code_0011e890 (0000)
0011E920 00a0:
_code_0011e920 (0000)
0011E9C0 0090:
_code_0011e9c0 (0000)
0011EA50 0090:
_code_0011ea50 (0000)
0011EAE0 00a0:
_code_0011eae0 (0000)
0011EB80 00a0:
_code_0011eb80 (0000)
0011EC20 0020:
_code_0011ec20 (0000)
0011EC40 0040:
_network_game_server_send_message_to_machine (0000)
0011EC80 0110:
_network_game_server_send_message_to_all_machines (0000)
0011ED90 0090:
_network_game_server_send_player_joined_info_ingame (0000)
0011EE20 00c0:
_network_game_server_send_game_data_pregame (0000)
0011EEE0 0240:
_code_0011eee0 (0000)
0011F120 00c0:
_code_0011f120 (0000)
0011F1E0 03f0:
_code_0011f1e0 (0000)
0011F5D0 00c0:
_code_0011f5d0 (0000)
0011F690 00c0:
_code_0011f690 (0000)
0011F750 00e0:
_code_0011f750 (0000)
0011F830 00d0:
_code_0011f830 (0000)
0011F900 00d0:
_code_0011f900 (0000)
0011F9D0 00f0:
_code_0011f9d0 (0000)
0011FAC0 0310:
_network_game_server_handle_datagram (0000)
0011FDD0 0400:
_network_game_server_handle_client_message (0000)
00286F00 005a:
??_C@_0FK@KMJJAKFL@failed?5to?5handle?5a?5message_clien@ (0000)
00286F60 0043:
??_C@_0ED@BONJCMOD@server?5failed?5to?5decode?5a?5messag@ (0000)
00286FA8 0065:
??_C@_0GF@BGPKJGEH@failed?5to?5handle?5a?5message_type_@ (0000)
00287010 004e:
??_C@_0EO@HMJBCLNP@server?5failed?5to?5decode?5a?5messag@ (0000)
00287060 0056:
??_C@_0FG@LPGCCINN@failed?5to?5handle?5a?5message_clien@ (0000)
002870B8 0037:
??_C@_0DH@MMOKOPCN@server?5failed?5to?5decode?5a?5messag@ (0000)
002870F0 005e:
??_C@_0FO@NHCIMMDF@failed?5to?5handle?5a?5message_clien@ (0000)
00287150 004a:
??_C@_0EK@ONPDOJKK@server?5failed?5to?5decode?5a?5messag@ (0000)
002871A0 0068:
??_C@_0GI@DLKCJODI@failed?5to?5handle?5a?5message_clien@ (0000)
00287208 004f:
??_C@_0EP@DAOANLDM@server?5failed?5to?5decode?5a?5messag@ (0000)
00287258 0033:
??_C@_0DD@FJKDMFNA@server?5failed?5to?5remove?5a?5networ@ (0000)
00287290 005b:
??_C@_0FL@NDHDFCNK@failed?5to?5handle?5a?5message_clien@ (0000)
002872F0 0045:
??_C@_0EF@MJLJMBIP@network_game_server_switch_machi@ (0000)
00287338 0059:
??_C@_0FJ@PEAAKLFN@network_game_server_write?$CI?$CJ?5fail@ (0000)
00287394 0027:
??_C@_0CH@EEEHHOGO@message_length?$DM?$DNsizeof?$CImessage_b@ (0000)
002873BC 0012:
??_C@_0BC@FDHBPMJE@server?5?$CG?$CG?5message?$AA@ (0000)
002873D0 003b:
??_C@_0DL@ICDCPLFI@c?3?2halo?2SOURCE?2networking?2networ@ (0000)
0028740C 003c:
??_C@_0DM@MOMFHJIG@failed?5to?5create?5a?5message_serve@ (0000)
00287448 0072:
??_C@_0HC@FGJDKKLC@network_game_server_send_message@ (0000)
002874C0 0058:
??_C@_0FI@LBJCENJB@failed?5to?5handle?5a?5message_serve@ (0000)
00287518 003f:
??_C@_0DP@CCCLFFGC@failed?5to?5create?5a?5message_serve@ (0000)
00287558 004b:
??_C@_0EL@NFHCHAEG@failed?5to?5send?5message_server_ga@ (0000)
002875A4 0039:
??_C@_0DJ@PCNJDMDI@failed?5to?5create?5a?5message_serve@ (0000)
002875E0 0054:
??_C@_0FE@HMKEDIDF@network_game_server_write?$CI?$CJ?5fail@ (0000)
00287634 002b:
??_C@_0CL@FOJJMNII@server?5?$CG?$CG?5source_address?5?$CG?$CG?5clie@ (0000)
00287660 002f:
??_C@_0CP@JCCANCCC@failed?5to?5create?5a?5message_serve@ (0000)
00287690 0043:
??_C@_0ED@LGNNJLKJ@network_game_server_write?$CI?$CJ?5fail@ (0000)
002876D8 0042:
??_C@_0EC@MGMKMMHI@server?5failed?5to?5decode?5a?5messag@ (0000)
0028771C 003b:
??_C@_0DL@JAHACCAG@failed?5to?5create?5a?5message_serve@ (0000)
00287758 0043:
??_C@_0ED@PLLDKIEI@client?5machine?5?8?$CFs?8?5?$EA?$CFs?5tried?5to@ (0000)
002877A0 0041:
??_C@_0EB@ONCPMINI@client?5machine?5?8?$CFs?8?5?$EA?$CFs?5tried?5to@ (0000)
002877E8 0043:
??_C@_0ED@DKNIPKFL@network_game_server_write?$CI?$CJ?5fail@ (0000)
00287830 0044:
??_C@_0EE@MGEHHOCB@server?5failed?5to?5accept?5valid?5cl@ (0000)
00287878 0075:
??_C@_0HF@HFOMEJBD@network_game_server_send_game_da@ (0000)
002878F0 0039:
??_C@_0DJ@CKOCAMEF@sent?5_message_type_server_machin@ (0000)
00287930 0064:
??_C@_0GE@MLODGBHN@network_game_server_write?$CI?$CJ?5fail@ (0000)
00287994 0029:
??_C@_0CJ@EPMEBAJK@network_machine_is_valid?$CIclient_@ (0000)
002879C0 0040:
??_C@_0EA@HPKAIHHK@server?5refused?5client?5?8?$CFs?8?5becau@ (0000)
00287A00 000d:
??_C@_0N@MPMCKMFC@d?3?2hosts?4txt?$AA@ (0000)
00287A10 002d:
??_C@_0CN@LBHCOBFK@ignoring?5redundant?5join?5request?5@ (0000)
00287A40 0062:
??_C@_0GC@IBIEHGFG@failed?5to?5handle?5a?5message_clien@ (0000)
00287AA8 004b:
??_C@_0EL@BFEPJEFO@server?5failed?5to?5decode?5a?5messag@ (0000)
00287AF8 0070:
??_C@_0HA@DHHGOECG@server?5failed?5to?5add?5a?5network?5p@ (0000)
00287B68 0072:
??_C@_0HC@LODCIJJJ@server?5failed?5to?5send?5pregame?5ga@ (0000)
00287BE0 0065:
??_C@_0GF@EKICMCEL@failed?5to?5handle?5a?5message_clien@ (0000)
00287C48 004e:
??_C@_0EO@JJCAGDLG@server?5failed?5to?5decode?5a?5messag@ (0000)
00287C98 0076:
??_C@_0HG@LNIKCCLC@server?5failed?5to?5remove?5a?5networ@ (0000)
00287D10 0075:
??_C@_0HF@KPAHGLDA@server?5failed?5to?5send?5pregame?5ga@ (0000)
00287D88 0058:
??_C@_0FI@HEMIANGD@failed?5to?5handle?5a?5message_clien@ (0000)
00287DE0 0041:
??_C@_0EB@EACNFEBF@server?5failed?5to?5decode?5a?5messag@ (0000)
00287E28 0075:
??_C@_0HF@CJBDOMOO@network_game_server_adjust_machi@ (0000)
00287EA0 0068:
??_C@_0GI@IHEHKNKB@server?5failed?5to?5send?5pregame?5ga@ (0000)
00287F08 0036:
??_C@_0DG@ECGMMKBK@server?5received?5machine?5settings@ (0000)
00287F40 005f:
??_C@_0FP@BKMGBKPC@failed?5to?5handle?5a?5message_clien@ (0000)
00287FA0 0048:
??_C@_0EI@NDLJFMCJ@server?5failed?5to?5decode?5a?5messag@ (0000)
00287FE8 006b:
??_C@_0GL@MGDAOAGD@network_game_update_player?$CI?$CJ?5fai@ (0000)
00288058 006f:
??_C@_0GP@IGEJNGEG@server?5failed?5to?5send?5pregame?5ga@ (0000)
002880C8 0028:
??_C@_0CI@NAMNDBJF@server?5received?5updated?5player?5s@ (0000)
002880F0 006a:
??_C@_0GK@PIEMGCFK@failed?5to?5handle?5a?5message_clien@ (0000)
00288160 004b:
??_C@_0EL@FGPNNAHJ@server?5failed?5to?5decode?5a?5messag@ (0000)
002881B0 0080:
??_C@_0IA@GMAELFDC@network_game_server_remove_machi@ (0000)
00288230 0072:
??_C@_0HC@JPOEIOK@server?5failed?5to?5send?5pregame?5ga@ (0000)
002882A8 0061:
??_C@_0GB@BICEKEIA@failed?5to?5handle?5a?5message_clien@ (0000)
00288310 004d:
??_C@_0EN@FBHIPCPE@server?5failed?5to?5decode?5a?5messag@ (0000)
00288360 003a:
??_C@_0DK@HGNCDCGJ@server?5received?5a?5malformed?1dama@ (0000)
002883A0 0047:
??_C@_0EH@HDCJJGCF@server?5received?5low?9level?5error?5@ (0000)
002883E8 0046:
??_C@_0EG@NGIBKKFK@server?5received?5a?5bad?5message?5ty@ (0000)
00288430 003f:
??_C@_0DP@DJCDAEKP@failed?5to?5decode?5a?5message_clien@ (0000)
00288470 003e:
??_C@_0DO@EMEMPFEN@server?5failed?5to?5advertise?5game?5@ (0000)
002884B0 002e:
??_C@_0CO@IFNMPAGP@failed?5to?5decode?5a?5message_clien@ (0000)
002884E0 0026:
??_C@_0CG@PBGOHBCL@server?5failed?5to?5handle?5a?5client@ (0000)
00288508 0042:
??_C@_0EC@OIEIALKJ@ignoring?5a?5message_client_game_u@ (0000)
00288550 0062:
??_C@_0GC@EMAKPFP@failed?5to?5handle?5a?5message_clien@ (0000)
002885B4 0035:
??_C@_0DF@NPLDDCCG@failed?5to?5decode?5a?5message_clien@ (0000)
002885F0 0046:
??_C@_0EG@DEJFBBLL@server?5received?5datagram?5with?5an@ (0000)
00288638 004c:
??_C@_0EM@NPFBANHI@server?5received?5a?5datagram?5with?5@ (0000)
00288684 003c:
??_C@_0DM@HFFMPMPG@server?5received?5a?5datagram?5with?5@ (0000)
002886C0 0081:
??_C@_0IB@OPFACLKA@server?5?$CG?$CG?5message?5?$CG?$CG?5source_addr@ (0000)
00288744 003a:
??_C@_0DK@NEANDKEL@server?5received?5a?5malformed?1dama@ (0000)
00288780 0047:
??_C@_0EH@PCKBOEPO@server?5received?5low?9level?5error?5@ (0000)
002887C8 0046:
??_C@_0EG@GPPJDFA@server?5received?5a?5bad?5message?5ty@ (0000)
00288810 003e:
??_C@_0DO@LBJNGIAJ@bad?5or?5inappropriate?5packet?5type@ (0000)
00288850 004e:
??_C@_0EO@OPDINPHL@network_game_server_handle_messa@ (0000)
002888A0 0045:
??_C@_0EF@FHPBCOMI@network_game_server_handle_messa@ (0000)
002888E8 0052:
??_C@_0FC@HBMALEKE@network_game_server_handle_messa@ (0000)
00288940 0050:
??_C@_0FA@OHGBEJHJ@network_game_server_handle_messa@ (0000)
00288990 004d:
??_C@_0EN@IOBKAAMM@network_game_server_handle_messa@ (0000)
002889E0 003a:
??_C@_0DK@DJBGFGMF@network_game_server_handle_messa@ (0000)
00288A20 0046:
??_C@_0EG@KNLLMEKN@network_game_server_handle_messa@ (0000)
00288A68 004b:
??_C@_0EL@IBNDPBLB@network_game_server_handle_messa@ (0000)
00288AB8 0044:
??_C@_0EE@KLMGADOJ@network_game_server_handle_messa@ (0000)
00288B00 0051:
??_C@_0FB@PPCOJHGP@network_game_server_handle_messa@ (0000)
00288B58 004e:
??_C@_0EO@EEEMKLCF@network_game_server_handle_messa@ (0000)
00288BA8 0045:
??_C@_0EF@FHABLMKA@network_game_server_handle_messa@ (0000)
00288BF0 0048:
??_C@_0EI@INBBOEGP@an?5un?9validated?5client?5sent?5some@ (0000)
00288C38 0044:
??_C@_0EE@EPDDKPMF@server?5received?5a?5client?5message@ (0000)
00288C7C 0032:
??_C@_0DC@DAGHEIED@server?5received?5client?5message?5w@ (0000)
00288CB0 0054:
??_C@_0FE@BPJHKGCF@server?5?$CG?$CG?5machine?5?$CG?$CG?5message?5?$CG?$CG?5@ (0000)
*/
/* ---------- headers */
#include "cseries.h"
#include "bungie_net/common/message_header.h"
#include "bungie_net/network/transport.h"
#include "bungie_net/network/transport_endpoint_winsock.h"
#include "game/game.h"
#include "game/game_engine.h"
#include "game/players.h"
#include "networking/network_connection.h"
#include "networking/network_game_globals.h"
#include "networking/network_game_manager.h"
#include "networking/network_game_protocol.h"
#include "networking/network_messages.h"
#include "networking/network_server_manager_internal.h"
#include "networking/network_server_message_handler.h"
#include "text/unicode.h"
/* system_milliseconds(), for the settings update interval */
#include "cseries/cseries_windows.h"
/* port/linux/game/network_distributed.c's */
void network_distributed_handle_message(long machine_index, word const *message, word size);
/* port/linux/game/network_voice.c's: voice chat, in the lobby too */
boolean network_voice_handles_message(word const *message, word size);
void network_voice_handle_message(long machine_index, word const *message, word size);
/* port/linux/game/map_share.c's: map sharing, in the lobby too */
boolean map_share_handles_message(word const *message, word size);
void map_share_handle_message(long machine_index, word const *message, word size, boolean from_stream);
void network_distributed_handle_stream_message(long machine_index, word const *message, word size);
/* ---------- constants */
enum
{
/* the native builds' session limits and protocol
(port/linux/include/halo_port_limits.h) */
MAXIMUM_NETWORK_MACHINE_COUNT = HALO_PORT_MAXIMUM_NETWORK_MACHINES,
NETWORK_MESSAGE_BUFFER_SIZE = HALO_PORT_MAXIMUM_NETWORK_MESSAGE_SIZE,
NETWORK_GAME_MESSAGE_VERSION = HALO_PORT_NETWORK_GAME_MESSAGE_VERSION,
MAXIMUM_MACHINE_NAME_LENGTH = 32,
NETWORK_GAME_MAP_NAME_LENGTH = 0x100,
JOIN_GAME_TOKEN_LENGTH = 16,
TRANSPORT_ERROR_MESSAGE_TEXT_LENGTH = 0x80,
REMOVE_PLAYER_INGAME_GAME_TIME_DELAY = 33,
TRANSPORT_NONCE_LENGTH = 8,
NETWORK_GAME_NAME_LENGTH = 16,
MAXIMUM_NUMBER_OF_PLAYERS = HALO_PORT_MAXIMUM_NETWORK_PLAYERS,
NUMBER_OF_MULTIPLAYER_TEAMS = 2,
/* port: the least time between two advertisements of the game
(milliseconds: network_game_server_handle_message_client_broadcast_game_search) */
GAME_ADVERTISEMENT_INTERVAL = 250,
/* port: the least time between two answers to one address's pings, and
the addresses kept to tell, no more of them answered in that time
(network_game_server_handle_message_client_ping) */
PING_REPLY_INTERVAL = 250,
MAXIMUM_PING_REPLY_ADDRESSES = 32,
};
enum
{
_game_advertisement_open_bit = 1,
_game_advertisement_has_teams_bit,
_game_advertisement_oddball_variant_bit,
};
#define MINIMUM_TRANSPORT_ERROR_MESSAGE_SIZE (sizeof(word) + TRANSPORT_ERROR_MESSAGE_TEXT_LENGTH + sizeof(byte))
/* port: the text of a transport error message as it is logged: printable
ASCII only (it is a machine's to send), ending with the buffer */
static char const *transport_error_message_text(
byte const *error_message)
{
static char text[TRANSPORT_ERROR_MESSAGE_TEXT_LENGTH + 1];
long index;
for (index = 0; index < TRANSPORT_ERROR_MESSAGE_TEXT_LENGTH && error_message[index]; index++)
text[index] = error_message[index] >= 0x20 && error_message[index] < 0x7F ? (char)error_message[index] : '?';
text[index] = 0;
return text;
}
enum
{
_message_type_error = 1,
_message_type_data = 2,
};
enum network_game_server_state
{
_network_game_server_state_pregame = 0,
_network_game_server_state_ingame,
_network_game_server_state_postgame,
};
enum network_game_packet_class
{
_network_game_packet_class_client_search = 0,
_network_game_packet_class_client_pregame = 3,
_network_game_packet_class_client_ingame = 5,
_network_game_packet_class_client_postgame = 7,
};
enum network_game_server_rejection_reason
{
_network_game_server_rejection_reason_bad_join_token = 2,
_network_game_server_rejection_reason_game_not_open = 5,
};
enum network_game_server_countdown_event
{
_network_game_server_countdown_event_player_left = 0,
_network_game_server_countdown_event_player_joined,
_network_game_server_countdown_event_stop,
_network_game_server_countdown_event_start_immediately,
};
/* ---------- macros */
#define network_machine_is_valid(machine) \
((machine) && (machine)->machine_index >= 0 && \
(machine)->machine_index < MAXIMUM_NETWORK_MACHINE_COUNT)
/* ---------- structures */
struct network_connection;
struct network_game_server;
struct network_game_server_client_machine;
typedef char network_game_players_offset_assert[
offsetof(struct network_game, players) == HALO_PORT_NETWORK_GAME_PLAYERS_OFFSET ? 1 : -1];
typedef char network_game_size_assert[
sizeof(struct network_game) == HALO_PORT_NETWORK_GAME_SIZE ? 1 : -1];
struct network_message
{
word header;
};
struct message_client_broadcast_game_search
{
word port;
short version;
byte nonce[TRANSPORT_NONCE_LENGTH];
};
struct message_client_ping
{
long timestamp;
short port;
byte padding[2];
};
struct message_client_game_update
{
unsigned long update_number;
short unknown;
short player_count;
/* a machine's players, which 16 players / 4 machines only happened to give */
struct player_action actions[MAXIMUM_LOCAL_PLAYERS];
};
struct message_server_pong
{
long timestamp;
};
struct message_server_game_advertise
{
byte client_nonce[TRANSPORT_NONCE_LENGTH];
byte nonce[TRANSPORT_NONCE_LENGTH];
XNKID key_id;
XNKEY key;
XNADDR xnaddr;
word port;
word version;
word platform;
wchar_t game_name[NETWORK_GAME_NAME_LENGTH];
byte reserved[0x1A];
struct network_game_map map;
short engine_type;
short machine_count;
short player_count;
short maximum_player_count;
short variant_setting;
word flags;
byte join_game_token[JOIN_GAME_TOKEN_LENGTH];
};
struct message_client_join_game_request
{
wchar_t machine_name[MAXIMUM_MACHINE_NAME_LENGTH];
byte join_game_token[JOIN_GAME_TOKEN_LENGTH];
/* port: the machine's hardware id, as hex, as it tells it (p2p.c) */
char hardware_id[0x20];
};
struct message_client_settings_request
{
wchar_t machine_name[MAXIMUM_MACHINE_NAME_LENGTH];
char machine_index;
byte padding[3];
};
struct message_client_game_start_request
{
long countdown_time;
};
struct message_client_graceful_game_exit_pregame
{
long unused;
};
struct message_client_map_is_precached_pregame
{
char map_name[NETWORK_GAME_MAP_NAME_LENGTH];
};
struct message_client_loaded
{
long unused;
};
struct message_client_switch_to_pregame
{
long unused;
};
struct message_client_graceful_game_exit_postgame
{
long unused;
};
struct message_server_machine_accepted
{
long random_seed;
short machine_index;
byte padding[2];
};
struct message_server_machine_rejected
{
short reason;
};
/* the game settings record no longer fits one message: it goes out as
consecutive pieces, which the clients put back together */
struct message_server_game_settings_update
{
word total_size;
word offset;
word length;
word pad;
byte data[HALO_PORT_NETWORK_GAME_SETTINGS_FRAGMENT_SIZE];
};
struct message_server_remove_player_ingame
{
struct network_player player;
long game_time;
};
/* ---------- prototypes */
static boolean network_game_server_handle_message_client_broadcast_game_search(
struct network_game_server *server,
struct transport_address *source_address,
struct message_client_broadcast_game_search *client_message);
static boolean network_game_server_handle_message_client_ping(
struct network_game_server *server,
struct transport_address *source_address,
struct message_client_ping *client_message);
static boolean network_game_server_handle_message_client_join_game_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_add_player_request_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_remove_player_request_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_settings_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_player_settings_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_game_start_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_graceful_game_exit_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_map_is_precached_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_loaded(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_add_player_request_ingame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_remove_player_request_ingame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_remove_player_request_postgame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_switch_to_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
static boolean network_game_server_handle_message_client_graceful_game_exit_postgame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size);
/* ---------- globals */
/* port: where each machine's distributed datagrams come from, by slot.
The host replies there, since a client may not be on the client port. */
static struct
{
struct network_connection *connection;
struct transport_address address;
} network_distributed_return_addresses[MAXIMUM_NETWORK_MACHINE_COUNT];
/* ---------- private code */
/* port: a name a machine sends (its machine's, or a player's: they come
from the wire, from anyone joining by any link) kept to what draws as one
line of text (player_name_clean); one with nothing left that the host's
ban command can type the default given */
static void network_game_server_clean_name(
wchar_t *name,
long count,
wchar_t const *default_name)
{
if (!player_name_clean(name, count))
{
long index;
for (index = 0; index < count - 1 && default_name[index]; index++)
name[index] = default_name[index];
name[index] = 0;
}
}
/* port: a name as the host's ban command reads it: in ASCII
(player_name_character_ascii), in lower case */
static char network_game_server_name_character(
wchar_t character)
{
char ascii = player_name_character_ascii(character);
return ascii >= 'A' && ascii <= 'Z' ? (char)(ascii - 'A' + 'a') : ascii;
}
/* port: whether another player of the game than the one at own_index has a
name the ban command would read as the same */
static boolean network_game_server_player_name_taken(
struct network_game_server *server,
wchar_t const *name,
long own_index)
{
struct network_game *game = network_game_server_get_game(server);
long index;
for (index = 0; index < (long)NUMBEROF(game->players); index++)
{
wchar_t const *other = game->players[index].name;
long character;
if (index == own_index || !network_player_is_valid(&game->players[index]))
continue;
for (character = 0; character < (long)NUMBEROF(game->players[index].name); character++)
{
if (!name[character] || !other[character])
{
if (!name[character] && !other[character])
return TRUE;
break;
}
if (network_game_server_name_character(name[character]) != network_game_server_name_character(other[character]))
break;
}
}
return FALSE;
}
/* port: a player's name from the wire made one the host's ban command can
always single out: kept to text that draws, with something it can type
(else "Player"), and, the same as another player's, numbered ("Player 2") */
static void network_game_server_clean_player_name(
struct network_game_server *server,
struct network_player *player,
long own_index)
{
wchar_t base[NUMBEROF(player->name)];
long number;
network_game_server_clean_name(player->name, NUMBEROF(player->name), L"Player");
csmemcpy(base, player->name, sizeof(base));
for (number = 2; number < 100 && network_game_server_player_name_taken(server, player->name, own_index); number++)
{
wchar_t suffix[4];
long suffix_length = number < 10 ? 2 : 3;
long length = ustrlen(base);
suffix[0] = L' ';
suffix[1] = number < 10 ? (wchar_t)(L'0' + number) : (wchar_t)(L'0' + number / 10);
suffix[2] = (wchar_t)(L'0' + number % 10);
suffix[suffix_length] = 0;
if (length > (long)NUMBEROF(player->name) - 1 - suffix_length)
length = (long)NUMBEROF(player->name) - 1 - suffix_length;
csmemcpy(player->name, base, length * sizeof(wchar_t));
while (length > 0 && player->name[length - 1] == L' ')
length--;
csmemcpy(player->name + length, suffix, (suffix_length + 1) * sizeof(wchar_t));
}
}
static boolean network_game_server_write(
struct network_connection *connection,
void *message,
word buffer_size,
struct transport_address *destination_address,
boolean reliable)
{
return network_connection_write(
connection,
message,
buffer_size,
destination_address,
reliable);
}
/* port: whether a line about a stray datagram (which anyone may send, as
fast as they like) goes in the log: at most one a second, with how many were
left out (the host writes its log a line at a time) */
static boolean network_game_server_datagram_event_logged(
void)
{
static unsigned long last_time = 0;
static long left_out_count = 0;
unsigned long now = system_milliseconds();
if (last_time && now - last_time < MILLISECONDS_PER_SECOND)
{
left_out_count++;
return FALSE;
}
if (left_out_count)
network_event("(%ld more stray datagrams not logged)", left_out_count);
left_out_count = 0;
last_time = now ? now : 1;
return TRUE;
}
/* ---------- public code */
boolean network_game_server_send_message_to_machine(
struct network_game_server *server,
struct network_machine *machine,
struct network_message *message)
{
boolean result = FALSE;
struct network_connection *connection =
network_game_server_get_machine_connection(server, machine);
if (connection)
{
word message_size = GET_MESSAGE_SIZE(message->header);
result = network_game_server_write(
connection,
message,
message_size,
NULL,
1);
}
return result;
}
/* a machine that joined a distributed game in progress and has not loaded
it yet (it hears none of the game's messages until it has) */
static boolean network_game_server_machine_is_loading_late(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
return network_game_server_get_state(server, NULL) == _network_game_server_state_ingame &&
!network_game_server_client_machine_is_loaded(server, machine);
}
boolean network_game_server_send_message_to_client_machine(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
void *message)
{
byte buffer[NETWORK_MESSAGE_BUFFER_SIZE];
word size = GET_MESSAGE_SIZE(((struct network_message *)message)->header);
struct network_connection *connection = network_game_server_get_client_connection(machine);
(void)server;
if (!connection || !network_connection_active(connection) || size > sizeof(buffer))
return FALSE;
/* (the write swaps the header in place) */
csmemcpy(buffer, message, size);
return network_game_server_write(connection, buffer, size, NULL, 1);
}
boolean network_game_server_send_game_settings_to_client_machine(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
void const *game,
long game_size)
{
struct message_server_game_settings_update message;
long offset;
for (offset = 0; offset < game_size; offset += sizeof(message.data))
{
void *encoded_message;
message.total_size = (word)game_size;
message.offset = (word)offset;
message.length = (word)MIN((long)sizeof(message.data), game_size - offset);
message.pad = 0;
csmemset(message.data, 0, sizeof(message.data));
csmemcpy(message.data, (byte const *)game + offset, message.length);
encoded_message = create_network_game_message(_message_server_game_settings_update, &message, sizeof(message));
if (!encoded_message || !network_game_server_send_message_to_client_machine(server, machine, encoded_message))
return FALSE;
}
return TRUE;
}
/* the client machines in the game, as the message handlers number them
(the distributed netcode sends each its own datagram); their count */
short network_distributed_server_machines(
long *machine_indices,
short maximum)
{
struct network_game_server *server = global_network_game_server_get();
short count = 0;
long client_index;
if (!server)
return 0;
for (client_index = 0; client_index < MAXIMUM_NETWORK_MACHINE_COUNT && count < maximum; client_index++)
{
struct network_game_server_client_machine *machine =
network_game_server_get_client_machine_at_index(server, client_index);
struct network_connection *connection;
long game_machine_index;
if (!network_game_server_client_machine_is_joined_to_game(server, machine) ||
network_game_server_machine_is_loading_late(server, machine))
{
continue;
}
connection = network_game_server_get_client_connection(machine);
if (!connection || !network_connection_active(connection))
continue;
/* (port: and not a machine without a slot) */
if (!network_game_server_get_client_machine(server, machine, &game_machine_index) ||
game_machine_index == NONE)
{
continue;
}
machine_indices[count++] = game_machine_index;
}
return count;
}
/* the client machine of a machine in the game, at its slot (NULL if none) */
static struct network_game_server_client_machine *network_game_server_client_machine_in_game(
struct network_game_server *server,
long machine_index)
{
struct network_game_server_client_machine *machine;
long game_machine_index;
if (!VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT))
return NULL;
machine = network_game_server_get_client_machine_at_index(server, machine_index);
if (!network_game_server_client_machine_is_joined_to_game(server, machine))
return NULL;
network_game_server_get_client_machine(server, machine, &game_machine_index);
return game_machine_index == machine_index ? machine : NULL;
}
/* the distributed netcode's per-tick state (port/linux/game/network_distributed.c),
unreliably (a lost one is overtaken by the next) to one machine in the game */
boolean network_distributed_server_send_to_machine(
long machine_index,
void *message,
word size)
{
struct network_game_server *server = global_network_game_server_get();
byte buffer[NETWORK_MESSAGE_BUFFER_SIZE];
struct network_game_server_client_machine *machine;
struct network_connection *connection;
struct transport_address address;
if (!server || size > sizeof(buffer))
return FALSE;
/* (a machine's slot is its index, network_game_server_accept_client_machine_into_game) */
machine = network_game_server_client_machine_in_game(server, machine_index);
if (!machine)
return FALSE;
connection = network_game_server_get_client_connection(machine);
if (!connection || !network_connection_active(connection))
return FALSE;
/* to where the client's datagrams come from, else the client port at
its connection's address (the write swaps the header in place) */
network_connection_get_address(connection, &address, NULL);
address.port = NETWORK_GAME_CLIENT_PORT;
if (network_distributed_return_addresses[machine_index].connection == connection &&
network_distributed_return_addresses[machine_index].address.address.long_words[0] ==
address.address.long_words[0])
{
address = network_distributed_return_addresses[machine_index].address;
}
csmemcpy(buffer, message, size);
return network_game_server_write(network_game_server_get_connection(server), buffer, size, &address, 0);
}
/* reliably to one machine in the game (the host's objects, to a client
that has loaded) */
boolean network_distributed_server_send_to_machine_reliably(
long machine_index,
void *message,
word size)
{
struct network_game_server *server = global_network_game_server_get();
byte buffer[NETWORK_MESSAGE_BUFFER_SIZE];
struct network_game_server_client_machine *machine;
struct network_connection *connection;
if (!server || size > sizeof(buffer))
return FALSE;
/* (machine_index is the game's machine, as the message handlers have
it: the client machine at that slot) */
machine = network_game_server_client_machine_in_game(server, machine_index);
if (!machine)
return FALSE;
connection = network_game_server_get_client_connection(machine);
if (!connection || !network_connection_active(connection))
return FALSE;
csmemcpy(buffer, message, size);
return network_game_server_write(connection, buffer, size, NULL, 1);
}
/* to every machine, reliably (the netcode's objects appearing and going
must not be lost) */
boolean network_distributed_server_send_to_all_reliably(
void *message,
word size)
{
struct network_game_server *server = global_network_game_server_get();
boolean result = TRUE;
long machine_index;
if (!server)
return FALSE;
for (machine_index = 0; machine_index < MAXIMUM_NETWORK_MACHINE_COUNT; machine_index++)
{
struct network_game_server_client_machine *machine =
network_game_server_get_client_machine_at_index(server, machine_index);
if (network_game_server_client_machine_is_joined_to_game(server, machine) &&
!network_game_server_machine_is_loading_late(server, machine))
{
struct network_connection *connection = network_game_server_get_client_connection(machine);
byte buffer[NETWORK_MESSAGE_BUFFER_SIZE];
if (connection && network_connection_active(connection) && size <= sizeof(buffer))
{
csmemcpy(buffer, message, size);
result &= network_game_server_write(connection, buffer, size, NULL, 1);
}
}
}
return result;
}
boolean network_game_server_send_message_to_all_machines(
struct network_game_server *server,
struct network_message *message)
{
byte message_buffer[NETWORK_MESSAGE_BUFFER_SIZE];
boolean result = TRUE;
word message_length;
long machine_index;
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x187,
server && message);
message_length = GET_MESSAGE_SIZE(message->header);
/* port: the assert is not checked in release builds */
if (message_length > sizeof(message_buffer))
{
network_event("network_game_server_send_message_to_all_machines() got a message of #%d bytes", message_length);
return FALSE;
}
for (machine_index = 0; machine_index < MAXIMUM_NETWORK_MACHINE_COUNT; machine_index++)
{
struct network_game_server_client_machine *machine =
network_game_server_get_client_machine_at_index(server, machine_index);
if (network_game_server_client_machine_is_joined_to_game(server, machine)
/* (not a machine still loading the game in progress: it takes
none of the game's messages until it has) */
&& !network_game_server_machine_is_loading_late(server, machine)
)
{
struct network_connection *connection =
network_game_server_get_client_connection(machine);
if (connection && network_connection_active(connection))
{
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x19A,
message_length<=sizeof(message_buffer));
csmemcpy(message_buffer, message, message_length);
if (!network_game_server_write(
connection,
message_buffer,
message_length,
NULL,
1))
{
network_event(
"network_game_server_write() failed in network_game_server_send_message_to_all_machines()");
result = FALSE;
}
}
}
}
return result;
}
boolean network_game_server_send_player_joined_info_ingame(
struct network_game_server *server,
struct network_player *player)
{
struct network_player message;
void *encoded_message;
boolean result;
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x1B0,
server && player);
message = *player;
encoded_message = create_network_game_message(
_message_server_add_player_ingame,
&message,
sizeof(message));
if (encoded_message)
{
result = network_game_server_send_message_to_all_machines(server, encoded_message);
if (!result)
{
network_event(
"network_game_server_send_message_to_all_machines() failed in network_game_server_send_player_joined_info_ingame()");
}
return result;
}
network_event("failed to create a message_server_add_player_ingame message");
return FALSE;
}
/* With up to 128 machines, sending the whole settings record (13 KB) to every
machine on each lobby change would flood the network while a lobby fills, so
changes are collected and sent at most this often; the server's pregame idle
sends what is pending. */
enum
{
NETWORK_GAME_SETTINGS_UPDATE_INTERVAL = MILLISECONDS_PER_SECOND / 4,
};
static boolean network_game_settings_update_pending = FALSE;
static unsigned long network_game_settings_update_time = 0;
boolean network_game_server_send_game_settings_to_all_machines(
struct network_game_server *server,
void const *game,
long game_size)
{
struct message_server_game_settings_update message;
long offset;
boolean result = TRUE;
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x1C8,
server);
/* every piece goes out even if one fails for a machine: the others
would otherwise keep the old settings (a machine whose connection failed
is closed, and is skipped when the update is sent again) */
for (offset = 0; offset < game_size; offset += sizeof(message.data))
{
void *encoded_message;
message.total_size = (word)game_size;
message.offset = (word)offset;
message.length = (word)MIN((long)sizeof(message.data), game_size - offset);
message.pad = 0;
csmemset(message.data, 0, sizeof(message.data));
csmemcpy(message.data, (byte const *)game + offset, message.length);
encoded_message = create_network_game_message(
_message_server_game_settings_update,
&message,
sizeof(message));
if (encoded_message)
{
/* (a piece that failed stays failed: the next is no retry of it) */
if (!network_game_server_send_message_to_all_machines(server, encoded_message))
{
network_event(
"failed to send message_server_game_settings_update message to all machines");
result = FALSE;
}
}
else
{
network_event("failed to create a message_server_game_settings_update message");
result = FALSE;
}
}
network_game_settings_update_pending = !result;
network_game_settings_update_time = system_milliseconds();
return result;
}
boolean network_game_server_flush_game_data_pregame(
struct network_game_server *server)
{
boolean result = TRUE;
if (network_game_settings_update_pending &&
system_milliseconds() - network_game_settings_update_time >= NETWORK_GAME_SETTINGS_UPDATE_INTERVAL)
{
struct network_game *game = network_game_server_get_game(server);
if (game)
{
result = network_game_server_send_game_settings_to_all_machines(server, game, sizeof(*game));
}
else
{
network_event(
"failed to handle a message_server_game_settings_update because their was no server game");
network_game_settings_update_pending = FALSE;
result = FALSE;
}
}
return result;
}
boolean network_game_server_send_game_data_pregame(
struct network_game_server *server)
{
network_game_settings_update_pending = TRUE;
return network_game_server_flush_game_data_pregame(server);
}
boolean network_game_server_handle_client_message(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
word *message,
short message_buffer_size)
{
boolean result = TRUE;
word message_type;
byte packet_type;
/* port: the size comes from the wire, and an assert is no check (an
empty message read its packet type from before the buffer): the machine
that sent it is dropped */
if (message_buffer_size <= (short)sizeof(message_header) ||
message_buffer_size != GET_MESSAGE_SIZE(*message))
{
network_event("server received a client message of a bad size (#%d)", message_buffer_size);
return FALSE;
}
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0xCF,
server && machine && message && (message_buffer_size == GET_MESSAGE_SIZE(*message)));
message_type = (byte)GET_MESSAGE_TYPE(*message);
if (GET_MESSAGE_FLAGS(*message))
{
network_event("server received client message with invalid flags");
}
else
{
switch (message_type)
{
case _message_type_packet:
packet_type = ((byte *)message)[message_buffer_size - 1];
if (network_game_server_client_machine_is_joined_to_game(server, machine) ||
packet_type == _message_client_join_game_request)
{
switch (packet_type)
{
case _message_client_join_game_request:
result = network_game_server_handle_message_client_join_game_request(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_join_game_request() failed");
}
break;
case _message_client_add_player_request_pregame:
result = network_game_server_handle_message_client_add_player_request_pregame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_add_player_request_pregame() failed");
}
break;
case _message_client_remove_player_request_pregame:
result = network_game_server_handle_message_client_remove_player_request_pregame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_remove_player_request_pregame() failed");
}
break;
case _message_client_settings_request:
result = network_game_server_handle_message_client_settings_request(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_settings_request() failed");
}
break;
case _message_client_player_settings_request:
result = network_game_server_handle_message_client_player_settings_request(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_player_settings_request() failed");
}
break;
case _message_client_game_start_request:
result = network_game_server_handle_message_client_game_start_request(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_game_start_request() failed");
}
break;
case _message_client_graceful_game_exit_pregame:
result = network_game_server_handle_message_client_graceful_game_exit_pregame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_graceful_game_exit_pregame() failed");
}
break;
case _message_client_map_is_precached_pregame:
result = network_game_server_handle_message_client_map_is_precached_pregame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_graceful_game_exit_pregame() failed");
}
break;
case _message_client_loaded:
result = network_game_server_handle_message_client_loaded(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_loaded() failed");
}
break;
case _message_client_add_player_request_ingame:
result = network_game_server_handle_message_client_add_player_request_ingame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_add_player_request_ingame() failed");
}
break;
case _message_client_remove_player_request_ingame:
result = network_game_server_handle_message_client_remove_player_request_ingame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_remove_player_request_ingame() failed");
}
break;
case _message_client_remove_player_request_postgame:
result = network_game_server_handle_message_client_remove_player_request_postgame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_remove_player_request_postgame() failed");
}
break;
case _message_client_switch_to_pregame:
result = network_game_server_handle_message_client_switch_to_pregame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_switch_to_pregame() failed");
}
break;
/* (port: its own handler, which decodes it as the
postgame message it is: the pregame's, which this
called, took none out of the pregame, nor could decode
one) */
case _message_client_graceful_game_exit_postgame:
result = network_game_server_handle_message_client_graceful_game_exit_postgame(
server,
machine,
message,
message_buffer_size);
if (!result)
{
network_event("network_game_server_handle_message_client_graceful_game_exit_postgame() failed");
}
break;
default:
network_event(
"bad or inappropriate packet type received from a client (#%d)",
packet_type);
break;
}
}
else
{
network_event("an un-validated client sent something other than a join request message");
}
break;
case _message_type_data:
/* the distributed netcode's messages (port/linux/NETCODE.md) */
/* (port: voice chat's from any machine that joined, in the
lobby too: network_voice.c) */
if (network_voice_handles_message(message, message_buffer_size))
{
long machine_index;
if (network_game_server_client_machine_is_joined_to_game(server, machine) &&
network_game_server_get_client_machine(server, machine, &machine_index))
{
network_voice_handle_message(machine_index, message, message_buffer_size);
}
break;
}
/* (port: map sharing's from any machine that joined, in the
lobby too, from its stream alone: map_share.c) */
if (map_share_handles_message(message, message_buffer_size))
{
long machine_index;
if (network_game_server_client_machine_is_joined_to_game(server, machine) &&
network_game_server_get_client_machine(server, machine, &machine_index))
{
network_game_server_client_machine_heard(server, machine);
map_share_handle_message(machine_index, message, message_buffer_size, TRUE);
}
break;
}
/* (in game, from a machine that has loaded it, as a datagram
is: else dropped) */
if (network_game_server_client_machine_is_joined_to_game(server, machine) &&
network_game_server_get_state(server, NULL) == _network_game_server_state_ingame &&
network_game_server_client_machine_is_loaded(server, machine))
{
long machine_index;
network_game_server_get_client_machine(server, machine, &machine_index);
/* (port: from its stream, which only it can send on) */
network_distributed_handle_stream_message(machine_index, message, message_buffer_size);
}
break;
case _message_type_error:
if (message_buffer_size >= MINIMUM_TRANSPORT_ERROR_MESSAGE_SIZE)
{
byte *error_message = (byte *)(message + 1);
/* (the text need not end in the message; printable only, so
that it forges no line of the log) */
network_event(
"server received low-level error message from a client: error= #%d (%s)",
error_message[TRANSPORT_ERROR_MESSAGE_TEXT_LENGTH],
transport_error_message_text(error_message));
}
else
{
network_event("server received a malformed/damaged message from a client");
}
break;
default:
network_event(
"server received a client message with an unknown message type (#%d)",
message_type);
break;
}
}
return result;
}
boolean network_game_server_handle_datagram(
struct network_game_server *server,
word *message,
short datagram_size,
struct transport_address *source_address)
{
word message_type;
/* port: the size comes from the wire, and an assert is no check: a
datagram of a bad size is dropped (anyone may send one) */
if (datagram_size <= (short)sizeof(message_header) ||
datagram_size != GET_MESSAGE_SIZE(*message))
{
return TRUE;
}
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x40,
server && message && source_address &&
(datagram_size > sizeof(message_header)) &&
(datagram_size == GET_MESSAGE_SIZE(*message)));
message_type = (byte)GET_MESSAGE_TYPE(*message);
if (GET_MESSAGE_FLAGS(*message))
{
if (network_game_server_datagram_event_logged())
{
network_event(
"server received a datagram with invalid flags; sender= '%s'",
transport_address_to_string(source_address));
}
}
else
{
switch (message_type)
{
case _message_type_packet:
{
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
short packet_type = ((byte *)message)[datagram_size - 1];
datagram_size -= sizeof(word);
switch (packet_type)
{
case _message_client_broadcast_game_search:
if (network_game_should_accept_remote_connections())
{
struct message_client_broadcast_game_search game_search;
if (decode_network_game_message(
&game_search,
message + 1,
&datagram_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_search))
{
if (!network_game_server_handle_message_client_broadcast_game_search(
server,
source_address,
&game_search))
{
network_event(
"server failed to advertise game to prospective client at '%s'",
transport_address_to_string(source_address));
}
}
else
{
network_event("failed to decode a message_client_broadcast_game_search packet");
}
}
break;
case _message_client_ping:
if (network_game_should_accept_remote_connections())
{
struct message_client_ping ping;
if (decode_network_game_message(
&ping,
message + 1,
&datagram_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_search))
{
if (!network_game_server_handle_message_client_ping(
server,
source_address,
&ping))
{
network_event("server failed to handle a client ping");
}
}
else
{
network_event("failed to decode a message_client_ping packet");
}
}
break;
case _message_client_game_update:
/* (ten times a second from every machine in the game, and
from those still in it as it ends: one not in the game, or
not now, is dropped without a line in the log for each) */
if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame)
{
struct network_game_server_client_machine *client_machine =
network_game_server_get_client_machine_at_address(
server,
source_address->address.long_words[0]);
if (client_machine)
{
struct message_client_game_update game_update;
if (decode_network_game_message(
&game_update,
message + 1,
&datagram_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_ingame))
{
network_game_server_client_machine_heard(server, client_machine);
network_game_server_handle_client_update_packet(
server,
client_machine,
&game_update);
}
else
{
network_event("failed to decode a message_client_game_update packet");
}
}
}
break;
default:
if (network_game_server_datagram_event_logged())
{
network_event(
"server received datagram with an unexpected packet type; sender= '%s'",
transport_address_to_string(source_address));
}
break;
}
}
break;
case _message_type_data:
/* the distributed netcode's messages (port/linux/NETCODE.md) */
/* (port: voice chat's from any machine that joined, in the
lobby too, its key checked: network_voice.c) */
if (network_voice_handles_message(message, datagram_size))
{
struct network_game_server_client_machine *client_machine =
network_game_server_get_client_machine_at_address(server, source_address->address.long_words[0]);
long machine_index;
if (client_machine && network_game_server_get_client_machine(server, client_machine, &machine_index))
network_voice_handle_message(machine_index, message, datagram_size);
break;
}
/* (from a machine in the game, which the lookup finds only among
those that joined, and only in game: else dropped) */
if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame)
{
/* port: not the host's own machine first (a second game on its
computer shares its address) */
struct network_game_server_client_machine *client_machine =
network_game_server_get_remote_client_machine_at_address(server, source_address->address.long_words[0]);
/* (and has loaded it, as for its reliable messages) */
if (client_machine && network_game_server_client_machine_is_loaded(server, client_machine))
{
long machine_index;
network_game_server_client_machine_heard(server, client_machine);
network_game_server_get_client_machine(server, client_machine, &machine_index);
/* port: where its datagrams come from, which the host answers
(network_distributed_return_addresses) */
if (VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT))
{
network_distributed_return_addresses[machine_index].connection =
network_game_server_get_client_connection(client_machine);
network_distributed_return_addresses[machine_index].address = *source_address;
}
network_distributed_handle_message(machine_index, message, datagram_size);
}
}
break;
case _message_type_error:
if (!network_game_server_datagram_event_logged())
{
/* (logged at most once a second) */
}
else if (datagram_size >= MINIMUM_TRANSPORT_ERROR_MESSAGE_SIZE)
{
byte *error_message = (byte *)(message + 1);
network_event(
"server received low-level error message: error= #%d (%s); sender= '%s'",
error_message[TRANSPORT_ERROR_MESSAGE_TEXT_LENGTH],
transport_error_message_text(error_message),
transport_address_to_string(source_address));
}
else
{
network_event(
"server received a malformed/damaged message; sender= '%s'",
transport_address_to_string(source_address));
}
break;
default:
if (network_game_server_datagram_event_logged())
{
network_event(
"server received a datagram with an unknown message type (#%d); sender= '%s'",
message_type,
transport_address_to_string(source_address));
}
break;
}
}
return TRUE;
}
/* ---------- private code */
/* a player a client machine asked to add in game, queued: only one of its
own (the queue takes the player's machine for the machine that asked) */
static void network_game_server_queue_client_player(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
struct network_player *player)
{
long machine_index;
network_game_server_get_client_machine(server, client_machine, &machine_index);
if (player->machine_index != machine_index)
{
network_event("client machine #%ld tried to add a player of another machine", machine_index);
return;
}
network_game_server_clean_player_name(server, player, NONE);
network_game_server_queue_player_for_addition(server, player);
}
static boolean network_game_server_handle_message_client_broadcast_game_search(
struct network_game_server *server,
struct transport_address *source_address,
struct message_client_broadcast_game_search *client_message)
{
boolean result = TRUE;
/* port: the advertisement is broadcast (to every internet play peer
too): one each so often answers every machine searching, and a flood of
searches no more. A searcher not on the client port hears no broadcast,
so it is answered directly every time. */
static unsigned long last_advertised_time = 0;
unsigned long now = system_milliseconds();
boolean broadcast_due = !last_advertised_time || now - last_advertised_time >= GAME_ADVERTISEMENT_INTERVAL;
boolean answer_directly = source_address && source_address->port != NETWORK_GAME_CLIENT_PORT;
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x21F,
server && source_address && client_message);
if (client_message->version == NETWORK_GAME_MESSAGE_VERSION)
{
struct network_game *game = network_game_server_get_game(server);
/* (the time taken only by a search answered; port: one off the client
port is always answered) */
if (game && !broadcast_due && !answer_directly)
return TRUE;
if (game)
{
struct message_server_game_advertise advertisement = {0};
struct transport_address address;
struct network_message *reply;
address.address_length = IPV4_ADDRESS_LENGTH;
address.address.long_words[0] = NONE;
address.port = NETWORK_GAME_CLIENT_PORT;
csmemcpy(
advertisement.client_nonce,
client_message->nonce,
sizeof(client_message->nonce));
transport_get_nonce(advertisement.nonce, TRANSPORT_NONCE_LENGTH);
advertisement.key_id = transport_get_key_id();
advertisement.key = transport_get_key();
{
XNADDR xnaddr;
advertisement.xnaddr = *transport_get_xnaddr(&xnaddr);
}
advertisement.port = NETWORK_GAME_SERVER_PORT;
advertisement.version = NETWORK_GAME_MESSAGE_VERSION;
advertisement.platform = 0;
ustrncpy(advertisement.game_name, game->name, NETWORK_GAME_NAME_LENGTH - 1);
advertisement.engine_type = (short)game->variant.game_engine_index;
csmemcpy(&advertisement.map, &game->map, sizeof(game->map));
advertisement.machine_count = game->machine_count;
advertisement.player_count = game->player_count;
advertisement.maximum_player_count = game->maximum_players;
advertisement.variant_setting = (short)game->variant.universal_variant.score_to_win;
advertisement.flags = 0;
if (game->variant.universal_variant.teams == TRUE)
{
advertisement.flags = FLAG(_game_advertisement_has_teams_bit);
}
if (game->variant.game_engine_index == game_engine_oddball &&
game->variant.game_engine_variant.oddball.oddball_ball_type == 2)
{
advertisement.flags |= FLAG(_game_advertisement_oddball_variant_bit);
}
/* the native builds' network version and netcode (a client
refuses a host of another version, or of the lockstep netcode
older builds had: network_client_manager.c) */
advertisement.reserved[HALO_PORT_ADVERTISED_VERSION_OFFSET] = (byte)(HALO_PORT_NETWORK_VERSION & 0xFF);
advertisement.reserved[HALO_PORT_ADVERTISED_VERSION_OFFSET + 1] = (byte)(HALO_PORT_NETWORK_VERSION >> 8);
advertisement.reserved[HALO_PORT_ADVERTISED_FLAGS_OFFSET] =
HALO_PORT_ADVERTISED_DISTRIBUTED_FLAG;
if (network_game_server_get_state(server, NULL) != _network_game_server_state_pregame ||
network_game_server_game_is_loading(server))
{
advertisement.reserved[HALO_PORT_ADVERTISED_FLAGS_OFFSET] |= HALO_PORT_ADVERTISED_IN_PROGRESS_FLAG;
}
/* (open to joins: not while the machines load the game, nor
once it is over, nor in progress when no player can join it) */
if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame
? network_game_server_accepts_late_joins(server)
: network_game_server_game_is_open(server) && network_game_has_free_player_slot(game))
{
advertisement.flags |= FLAG(_game_advertisement_open_bit);
}
network_game_generate_join_game_token(advertisement.join_game_token);
reply = create_network_game_message(
_message_server_game_advertise,
&advertisement,
sizeof(advertisement));
if (reply)
{
struct network_connection *connection =
network_game_server_get_connection(server);
/* port: the broadcast when it is due, and a searcher off the
client port answered directly */
if (broadcast_due)
{
last_advertised_time = now ? now : 1;
result = network_game_server_write(connection, reply, GET_MESSAGE_SIZE(reply->header), &address, 0);
if (!result)
{
network_event(
"network_game_server_write() failed in handle_message_client_broadcast_game_search()");
}
/* (the write swapped the header in place: a fresh copy for the next) */
reply = answer_directly ? create_network_game_message(_message_server_game_advertise,
&advertisement, sizeof(advertisement)) : NULL;
}
if (answer_directly && reply)
network_game_server_write(connection, reply, GET_MESSAGE_SIZE(reply->header), source_address, 0);
}
else
{
network_event("failed to create a message_server_game_advertise message");
}
}
}
return result;
}
static boolean network_game_server_handle_message_client_ping(
struct network_game_server *server,
struct transport_address *source_address,
struct message_client_ping *client_message)
{
struct message_server_pong pong;
struct network_message *reply;
boolean result = FALSE;
/* port: an address answered no more often than PING_REPLY_INTERVAL, and
no more than MAXIMUM_PING_REPLY_ADDRESSES in that time (the answer goes
to the client port at the address it came from, which anyone can send
one as: a flood of pings would be a flood of answers, at another's
machine). A searching client pings once a second */
static struct
{
unsigned long address;
unsigned long time;
} replied[MAXIMUM_PING_REPLY_ADDRESSES];
unsigned long now = system_milliseconds();
short index;
short slot = NONE;
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x26A,
server && source_address && client_message);
for (index = 0; index < MAXIMUM_PING_REPLY_ADDRESSES; index++)
{
boolean recent = replied[index].address && now - replied[index].time < PING_REPLY_INTERVAL;
if (replied[index].address == source_address->address.long_words[0])
{
if (recent)
return TRUE;
slot = index;
break;
}
if (!recent && slot == NONE)
slot = index;
}
if (slot == NONE)
return TRUE;
replied[slot].address = source_address->address.long_words[0];
replied[slot].time = now;
pong.timestamp = client_message->timestamp;
reply = create_network_game_message(
_message_server_pong,
&pong,
sizeof(pong));
if (reply)
{
struct transport_address address;
address.address_length = IPV4_ADDRESS_LENGTH;
address.address.long_words[0] = source_address->address.long_words[0];
/* port: to the client port, as the game's advertisement goes, not
the port the ping names (every client names that one; any other
was an answer at whatever else listens there) */
address.port = NETWORK_GAME_CLIENT_PORT;
result = network_game_server_write(
network_game_server_get_connection(server),
reply,
GET_MESSAGE_SIZE(reply->header),
&address,
0);
if (!result)
{
network_event(
"network_game_server_write() failed in handle_message_client_ping()");
}
}
else
{
network_event("failed to create a message_server_pong message");
}
return result;
}
static boolean network_game_server_handle_message_client_join_game_request(
struct network_game_server *server,
struct network_game_server_client_machine *server_client_machine,
word *message,
short message_size)
{
boolean result = TRUE;
/* (or a distributed game in progress: network_game_server_accepts_late_joins) */
boolean late_join = network_game_server_accepts_late_joins(server);
if (network_game_server_get_state(server, NULL) == _network_game_server_state_pregame || late_join)
{
struct message_client_join_game_request join_game_request;
short packet_type = _message_client_join_game_request;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (network_game_server_client_machine_is_joined_to_game(server, server_client_machine))
{
network_event("ignoring redundant join request from machine");
}
else if (decode_network_game_message(
&join_game_request,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
struct transport_address source_address;
wchar_t machine_name[MAXIMUM_MACHINE_NAME_LENGTH];
/* port: a lobby with no player slot free (its machines' players
fill it) is full: a machine that joined could add none, and held
the countdown for ever */
boolean full = network_game_server_get_state(server, NULL) == _network_game_server_state_pregame &&
!network_game_has_free_player_slot(network_game_server_get_game(server));
/* port: its hardware id, as it tells it: hex only (anything else
left out), no more than its field; the host logs it, and refuses
one it banned (network_game_server_accept_client_machine_into_game) */
{
char hardware_id[sizeof(join_game_request.hardware_id) + 1];
csmemcpy(hardware_id, join_game_request.hardware_id, sizeof(join_game_request.hardware_id));
hardware_id[sizeof(join_game_request.hardware_id)] = 0;
network_game_server_set_machine_hardware_id(server_client_machine, hardware_id);
}
/* (the name comes from the wire, and need not end, nor be text
that draws: kept to what does; then in ASCII, for the log) */
network_game_server_clean_name(join_game_request.machine_name, MAXIMUM_MACHINE_NAME_LENGTH, L"Machine");
csmemcpy(machine_name, join_game_request.machine_name, sizeof(machine_name));
wide_to_ascii(
join_game_request.machine_name,
(char *)join_game_request.machine_name,
sizeof(join_game_request.machine_name));
network_connection_get_address(
network_game_server_get_client_connection(server_client_machine),
&source_address,
FALSE);
if ((network_game_server_get_state(server, NULL) == _network_game_server_state_pregame || late_join) &&
network_game_server_game_is_open(server) &&
!full)
{
byte join_game_token[JOIN_GAME_TOKEN_LENGTH];
network_game_generate_join_game_token(join_game_token);
if (!csmemcmp(
join_game_request.join_game_token,
join_game_token,
sizeof(join_game_token)))
{
/* (the Xbox game refused a machine not named in the
development kit's d:\hosts.txt: a stray file of that name
on a PC refused machines) */
if (network_game_server_accept_client_machine_into_game(server, server_client_machine))
{
struct message_server_machine_accepted acceptance;
struct network_message *reply;
struct network_machine *client_machine;
long machine_index = NONE;
client_machine = network_game_server_get_client_machine(
server,
server_client_machine,
&machine_index);
network_game_server_get_game(server);
match_assert(
"c:\\halo\\SOURCE\\networking\\network_server_message_handler.c",
0x2CE,
network_machine_is_valid(client_machine));
/* port: a machine joining the game in progress is named
by its join (its settings request, which names a machine
in the lobby, is refused in game) */
if (late_join && client_machine)
csmemcpy(client_machine->name, machine_name, sizeof(client_machine->name));
acceptance.machine_index = (short)machine_index;
acceptance.random_seed = network_game_get_random_seed();
reply = create_network_game_message(
_message_server_machine_accepted,
&acceptance,
sizeof(acceptance));
if (reply)
{
word message_size = GET_MESSAGE_SIZE(reply->header);
struct network_connection *connection =
network_game_server_get_client_connection(server_client_machine);
result = network_game_server_write(
connection,
reply,
message_size,
NULL,
1);
if (!result)
{
network_event(
"network_game_server_write() failed in network_game_server_handle_message_client_join_game_request()");
}
else
{
network_event(
"sent _message_type_server_machine_accepted message to %d",
machine_index);
}
if (result == TRUE)
{
/* (a machine joining the game in progress: to it alone,
the others are in game) */
if (late_join)
{
result = network_game_server_send_game_settings_to_client_machine(server,
server_client_machine, network_game_server_get_game(server),
sizeof(struct network_game));
}
/* (to every machine: one the write failed for is
closed, which is not this machine's failure) */
else if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"network_game_server_send_game_data_pregame() failed in network_game_server_handle_message_client_join_game_request()");
}
if (!result)
{
network_event(
"network_game_server_send_game_settings_to_client_machine() failed in network_game_server_handle_message_client_join_game_request()");
}
}
}
else
{
result = FALSE;
}
}
else
{
struct message_server_machine_rejected rejection;
struct network_message *reply;
/* port: a banned machine is told so (it was told the
game is not open) */
rejection.reason = network_game_server_last_refusal_code();
network_event(
"server failed to accept valid client machine '%s' @%s into the game",
join_game_request.machine_name,
transport_address_to_string(&source_address));
reply = create_network_game_message(
_message_server_machine_rejected,
&rejection,
sizeof(rejection));
if (reply)
{
word message_size = GET_MESSAGE_SIZE(reply->header);
struct network_connection *connection =
network_game_server_get_client_connection(server_client_machine);
if (!network_game_server_write(
connection,
reply,
message_size,
NULL,
1))
{
network_event(
"network_game_server_write() failed while sending a rejection reply");
}
}
result = FALSE;
}
}
else
{
struct message_server_machine_rejected rejection;
struct network_message *reply;
rejection.reason = _network_game_server_rejection_reason_bad_join_token;
network_event(
"client machine '%s' @%s tried to join game with a bad join token",
join_game_request.machine_name,
transport_address_to_string(&source_address));
reply = create_network_game_message(
_message_server_machine_rejected,
&rejection,
sizeof(rejection));
if (reply)
{
word message_size = GET_MESSAGE_SIZE(reply->header);
struct network_connection *connection =
network_game_server_get_client_connection(server_client_machine);
if (!network_game_server_write(
connection,
reply,
message_size,
NULL,
1))
{
network_event(
"network_game_server_write() failed while sending a rejection reply");
}
}
else
{
network_event("failed to create a message_server_machine_rejected message");
}
result = FALSE;
}
}
else
{
struct message_server_machine_rejected rejection;
struct network_message *reply;
rejection.reason = full ? _rejection_code_game_is_full : _network_game_server_rejection_reason_game_not_open;
network_event(
"client machine '%s' @%s tried to join game when they should not be",
join_game_request.machine_name,
transport_address_to_string(&source_address));
reply = create_network_game_message(
_message_server_machine_rejected,
&rejection,
sizeof(rejection));
if (reply)
{
word message_size = GET_MESSAGE_SIZE(reply->header);
struct network_connection *connection =
network_game_server_get_client_connection(server_client_machine);
if (!network_game_server_write(
connection,
reply,
message_size,
NULL,
1))
{
network_event(
"network_game_server_write() failed while sending a rejection reply");
}
}
else
{
network_event("failed to create a message_server_machine_rejected message");
}
result = FALSE;
}
}
else
{
network_event("server failed to decode a message_client_join_game_request packet");
result = FALSE;
}
}
else if (!network_game_server_client_machine_is_joined_to_game(server, server_client_machine))
{
/* port: a game that cannot be joined now (full, loading, over) says
so, rather than leaving the machine to time out */
struct message_server_machine_rejected rejection;
struct network_message *reply;
rejection.reason = network_game_server_get_state(server, NULL) == _network_game_server_state_ingame &&
network_game_server_game_is_open(server) ?
_rejection_code_game_is_full : _network_game_server_rejection_reason_game_not_open;
reply = create_network_game_message(_message_server_machine_rejected, &rejection, sizeof(rejection));
if (reply)
{
network_game_server_write(network_game_server_get_client_connection(server_client_machine), reply,
GET_MESSAGE_SIZE(reply->header), NULL, 1);
}
result = FALSE;
}
return result;
}
static boolean network_game_server_handle_message_client_add_player_request_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
/* (port: not once the game has started: every machine loads it from
the settings sent with the start) */
if (network_game_server_lobby_is_open(server))
{
struct network_player player;
short packet_type = _message_client_add_player_request_pregame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
/* (port: its name kept to text that draws, as every name from
the wire, and told apart from the other players') */
network_game_server_clean_player_name(server, &player, NONE);
if (network_game_server_add_player_to_game(server, client_machine, &player))
{
if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"server failed to send pregame game data in network_game_server_handle_message_client_add_player_request_pregame()");
}
}
else
{
network_event(
"server failed to add a network player in network_game_server_handle_message_client_add_player_request_pregame()");
}
}
else
{
network_event("server failed to decode a message_client_add_player_request_pregame packet");
}
}
/* a machine joining the game in progress: its players are added as in
game (network_game_server_start_late_joiner then starts it; one that
cannot, the game full, is refused there) */
else if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame &&
!network_game_server_client_machine_is_loaded(server, client_machine))
{
struct network_player player;
short packet_type = _message_client_add_player_request_pregame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
network_game_server_queue_client_player(server, client_machine, &player);
}
else
{
network_event("server failed to decode a message_client_add_player_request_pregame packet");
}
}
else
{
network_event(
"failed to handle a message_client_add_player_request_pregame because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_remove_player_request_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
/* (port: not once the game has started: every machine loads it from
the settings sent with the start) */
if (network_game_server_lobby_is_open(server))
{
struct network_player player;
short packet_type = _message_client_remove_player_request_pregame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
if (network_game_server_remove_player_from_game(server, client_machine, &player))
{
if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"server failed to send pregame game data in network_game_server_handle_message_client_remove_player_request_pregame()");
}
}
else
{
network_event(
"server failed to remove a network player in network_game_server_handle_message_client_remove_player_request_pregame()");
}
}
else
{
network_event("server failed to decode a message_client_remove_player_request_pregame packet");
}
}
else
{
network_event(
"failed to handle a message_client_remove_player_request_pregame because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_settings_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
/* (port: not once the game has started: every machine loads it from
the settings sent with the start) */
if (network_game_server_lobby_is_open(server))
{
struct network_machine machine_settings;
short packet_type = _message_client_settings_request;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&machine_settings,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
/* (the name comes from the wire, and need not end, nor be text
that draws: kept to what does) */
network_game_server_clean_name(machine_settings.name, NUMBEROF(machine_settings.name), L"Machine");
if (network_game_server_adjust_machine_settings(server, client_machine, &machine_settings))
{
network_event(
"server received machine settings for machine #%d/'%s'",
machine_settings.machine_index,
wide_to_ascii(
machine_settings.name,
(char *)machine_settings.name,
sizeof(machine_settings.name)));
if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"server failed to send pregame game data in network_game_server_handle_message_client_settings_request()");
}
}
else
{
network_event(
"network_game_server_adjust_machine_settings() failed in network_game_server_handle_message_client_settings_request()");
}
}
else
{
network_event("server failed to decode a message_client_settings_request packet");
}
}
else
{
network_event(
"failed to handle a message_client_settings_request because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_player_settings_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
/* (port: not once the game has started: every machine loads it from
the settings sent with the start) */
if (network_game_server_lobby_is_open(server))
{
struct network_player player;
short packet_type = _message_client_player_settings_request;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
struct network_game *game = network_game_server_get_game(server);
long machine_index;
/* port: a machine's own player, of its slot (the settings come
from the wire, and the Xbox game took any machine's, and wrote a
slot out of the list over the machines), with a name that ends,
and on a team of the game's: else the one the host has it on */
network_game_server_get_client_machine(server, client_machine, &machine_index);
network_game_server_clean_player_name(server, &player,
VALID_INDEX(player.player_list_index, MAXIMUM_NUMBER_OF_PLAYERS) ? player.player_list_index : NONE);
if (VALID_INDEX(player.player_list_index, MAXIMUM_NUMBER_OF_PLAYERS) &&
(!game->variant.universal_variant.teams ||
!VALID_INDEX(player.team_index, NUMBER_OF_MULTIPLAYER_TEAMS)))
{
player.team_index = game->players[player.player_list_index].team_index;
}
if (player.machine_index != machine_index)
{
network_event(
"client machine #%ld tried to update a player of another machine",
machine_index);
}
else if (network_game_update_player(game, &player))
{
network_event("server received updated player settings");
if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"server failed to send pregame game data in network_game_server_handle_message_client_player_settings_request()");
}
}
else
{
network_event(
"network_game_update_player() failed in network_game_server_handle_message_client_player_settings_request()");
}
}
else
{
network_event("server failed to decode a message_client_player_settings_request packet");
}
}
else
{
network_event(
"failed to handle a message_client_player_settings_request because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_game_start_request(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_pregame)
{
struct message_client_game_start_request game_start_request;
short packet_type = _message_client_game_start_request;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&game_start_request,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
/* (a short on the wire: the rest of the long is not written) */
short countdown_event = (short)game_start_request.countdown_time;
/* port: the pregame screen's faster and slower, from any machine;
to stop the countdown or start at once (a script's), the host's own
(the Xbox game took any event from any machine, and a machine
could hold the lobby or skip the countdown) */
if ((countdown_event == _network_game_server_countdown_event_player_left &&
network_game_server_client_machine_may_slow_countdown(server, client_machine)) ||
countdown_event == _network_game_server_countdown_event_player_joined ||
((countdown_event == _network_game_server_countdown_event_stop ||
countdown_event == _network_game_server_countdown_event_start_immediately) &&
network_game_server_client_machine_is_local(server, client_machine)))
{
network_game_server_update_countdown(server, countdown_event);
}
else
{
network_event("ignoring a game start request (#%d) from a client machine", countdown_event);
}
}
else
{
network_event("server failed to decode a message_client_game_start_request packet");
}
}
else
{
network_event(
"failed to handle a message_client_game_start_request because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_graceful_game_exit_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_pregame)
{
struct message_client_graceful_game_exit_pregame graceful_game_exit;
short packet_type = _message_client_graceful_game_exit_pregame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&graceful_game_exit,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
struct network_machine *machine = network_game_server_get_client_machine(server, client_machine, NULL);
/* (port: none for a machine without a slot) */
if (machine && network_game_server_remove_machine_from_game(server, machine))
{
/* (not to machines loading the game) */
if (network_game_server_lobby_is_open(server) &&
!network_game_server_send_game_data_pregame(server))
{
network_event(
"server failed to send pregame game data in network_game_server_handle_message_client_graceful_game_exit_pregame()");
}
}
else
{
network_event(
"network_game_server_remove_machine_from_game() failed in network_game_server_handle_message_client_graceful_game_exit_pregame()");
}
}
else
{
network_event("server failed to decode a message_client_graceful_game_exit_pregame packet");
}
}
else
{
network_event(
"failed to handle a message_client_graceful_game_exit_pregame message because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_map_is_precached_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_pregame)
{
struct message_client_map_is_precached_pregame map_is_precached;
short packet_type = _message_client_map_is_precached_pregame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&map_is_precached,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_pregame))
{
network_game_server_client_machine_is_precached(
server,
client_machine,
map_is_precached.map_name);
}
else
{
network_event("server failed to decode a message_type_client_map_is_precached_pregame packet");
}
}
else
{
network_event(
"failed to handle a message_type_client_map_is_precached_pregame because the server is not in pregame");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_loaded(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
boolean result = TRUE;
if (network_game_server_get_state(server, NULL) == _network_game_server_state_pregame)
{
struct message_client_loaded loaded;
short packet_type = _message_client_loaded;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&loaded,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_ingame))
{
network_game_server_client_machine_game_loading_complete(server, client_machine);
}
else
{
network_event("server failed to decode a message_client_loaded packet");
result = FALSE;
}
}
/* a machine that joined the game in progress has loaded it (or it
loaded it as the game ended: not a reason to drop it) */
else if ((network_game_server_get_state(server, NULL) == _network_game_server_state_ingame &&
!network_game_server_client_machine_is_loaded(server, client_machine)) ||
network_game_server_get_state(server, NULL) == _network_game_server_state_postgame)
{
network_game_server_late_joiner_loaded(server, client_machine);
}
else
{
network_event(
"failed to handle a message_client_loaded message because the server is not in pregame");
result = FALSE;
}
return result;
}
static boolean network_game_server_handle_message_client_add_player_request_ingame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame)
{
struct network_player player;
short packet_type = _message_client_add_player_request_ingame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_ingame))
{
network_game_server_queue_client_player(server, client_machine, &player);
}
else
{
network_event("server failed to decode a message_client_add_player_request_ingame packet");
}
}
else
{
network_event(
"failed to handle a message_client_add_player_request_ingame because the server is not in game");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_remove_player_request_ingame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
boolean result = TRUE;
if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame)
{
struct network_player player;
short packet_type = _message_client_remove_player_request_ingame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_ingame))
{
/* port: only a player of the machine that asks (the Xbox game
removed any machine's) */
player.name[NUMBEROF(player.name) - 1] = 0;
if (network_game_server_remove_player_from_game(server, client_machine, &player))
{
struct message_server_remove_player_ingame remove_player;
void *encoded_message;
remove_player.player = player;
remove_player.game_time = game_time_get() + REMOVE_PLAYER_INGAME_GAME_TIME_DELAY;
encoded_message = create_network_game_message(
_message_server_remove_player_ingame,
&remove_player,
sizeof(remove_player));
/* (to every machine: one the write failed for is closed,
which is not this machine's failure) */
if (encoded_message &&
!network_game_server_send_message_to_all_machines(server, encoded_message))
{
network_event(
"network_game_server_send_message_to_all_machines() failed in network_game_server_handle_message_client_remove_player_request_ingame()");
}
}
}
else
{
network_event("server failed to decode a message_client_remove_player_request_ingame packet");
}
}
else
{
network_event(
"failed to handle a message_client_remove_player_request_ingame because the server is not in game");
}
return result;
}
static boolean network_game_server_handle_message_client_remove_player_request_postgame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_postgame)
{
struct network_player player;
short packet_type = _message_client_remove_player_request_postgame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&player,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_postgame))
{
if (!network_game_server_remove_player_from_game(server, client_machine, &player))
{
network_event("server failed to remove a network player post-game");
}
}
else
{
network_event("server failed to decode a message_client_remove_player_request_postgame packet");
}
}
else
{
network_event(
"failed to handle a message_client_remove_player_request_postgame because the server is not in post-game");
}
return TRUE;
}
static boolean network_game_server_handle_message_client_switch_to_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
boolean result = TRUE;
if (network_game_server_get_state(server, NULL) == _network_game_server_state_postgame)
{
struct message_client_switch_to_pregame switch_to_pregame;
short packet_type = _message_client_switch_to_pregame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&switch_to_pregame,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_postgame))
{
result = network_game_server_switch_machine_from_postgame_to_pregame(server, client_machine);
if (!result)
{
network_event("network_game_server_switch_machine_from_postgame_to_pregame() failed");
}
}
else
{
network_event("server failed to decode a message_client_remove_player_request_postgame packet");
}
}
else
{
network_event(
"failed to handle a message_client_switch_to_pregame because the server is not in post-game");
}
return result;
}
/* port: a machine leaving the game's scores, as one leaving the lobby does
(network_game_server_handle_message_client_graceful_game_exit_pregame) */
static boolean network_game_server_handle_message_client_graceful_game_exit_postgame(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
word *message,
short message_size)
{
if (network_game_server_get_state(server, NULL) == _network_game_server_state_postgame)
{
struct message_client_graceful_game_exit_postgame graceful_game_exit;
short packet_type = _message_client_graceful_game_exit_postgame;
short packet_version = NETWORK_GAME_MESSAGE_VERSION;
message_size -= sizeof(word);
if (decode_network_game_message(
&graceful_game_exit,
message + 1,
&message_size,
&packet_type,
&packet_version,
_network_game_packet_class_client_postgame))
{
struct network_machine *machine = network_game_server_get_client_machine(server, client_machine, NULL);
if (!machine || !network_game_server_remove_machine_from_game(server, machine))
{
network_event(
"network_game_server_remove_machine_from_game() failed in network_game_server_handle_message_client_graceful_game_exit_postgame()");
}
}
else
{
network_event("server failed to decode a message_client_graceful_game_exit_postgame packet");
}
}
else
{
network_event(
"failed to handle a message_client_graceful_game_exit_postgame message because the server is not in post-game");
}
return TRUE;
}