/* * Sv_NextDownload_f */ static void Sv_NextDownload_f(void) { int r, size, percent; byte buf[MAX_MSG_SIZE]; size_buf_t msg; if (!sv_client->download) return; r = sv_client->download_size - sv_client->download_count; if (r > 1024) // cap the download chunk at 1024 bytes r = 1024; Sb_Init(&msg, buf, MAX_MSG_SIZE); Msg_WriteByte(&msg, SV_CMD_DOWNLOAD); Msg_WriteShort(&msg, r); sv_client->download_count += r; size = sv_client->download_size; if (!size) size = 1; percent = sv_client->download_count * 100 / size; Msg_WriteByte(&msg, percent); Sb_Write(&msg, sv_client->download + sv_client->download_count - r, r); Sb_Write(&sv_client->netchan.message, msg.data, msg.size); if (sv_client->download_count != sv_client->download_size) return; Fs_FreeFile(sv_client->download); sv_client->download = NULL; }
/* * Sv_Shutdown * * Called when server is shutting down due to error or an explicit `quit`. */ void Sv_Shutdown(const char *msg) { Sv_ShutdownServer(msg); Sv_ShutdownMasters(); Net_Config(NS_SERVER, false); Sb_Init(&net_message, net_message_buffer, sizeof(net_message_buffer)); memset(&svs, 0, sizeof(svs)); }
/* * @brief Entry point for spawning a new server or changing maps / demos. Brings any * connected clients along for the ride by broadcasting a reconnect before * clearing state. Special effort is made to ensure that a locally connected * client sees the reconnect message immediately. */ void Sv_InitServer(const char *server, sv_state_t state) { #ifdef BUILD_CLIENT extern void Cl_Disconnect(void); #endif char path[MAX_QPATH]; Com_Debug("Sv_InitServer: %s (%d)\n", server, state); Cbuf_CopyToDefer(); // ensure that the requested map or demo exists if (state == SV_ACTIVE_DEMO) g_snprintf(path, sizeof(path), "demos/%s.dem", server); else g_snprintf(path, sizeof(path), "maps/%s.bsp", server); if (!Fs_Exists(path)) { Com_Print("Couldn't open %s\n", path); return; } // inform any connected clients to reconnect to us Sv_ShutdownMessage("Server restarting...\n", true); #ifdef BUILD_CLIENT // disconnect any local client, they'll immediately reconnect Cl_Disconnect(); #endif // clear the sv_server_t structure Sv_ClearState(); Com_Print("Server initialization...\n"); // initialize the clients, loading the game module if we need it Sv_InitClients(); // load the map or demo and related media Sv_LoadMedia(server, state); sv.state = state; Sb_Init(&sv.multicast, sv.multicast_buffer, sizeof(sv.multicast_buffer)); Com_Print("Server initialized\n"); Com_InitSubsystem(Q2W_SERVER); svs.initialized = true; }
/* * Sv_Init * * Only called at Quake2World startup, not for each game. */ void Sv_Init(void) { memset(&svs, 0, sizeof(svs)); sv_rcon_password = Cvar_Get("rcon_password", "", 0, NULL); sv_download_url = Cvar_Get("sv_download_url", "", CVAR_SERVER_INFO, NULL); sv_enforce_time = Cvar_Get("sv_enforce_time", va("%d", CMD_MSEC_MAX_DRIFT_ERRORS), 0, NULL); sv_hostname = Cvar_Get("sv_hostname", "Quake2World", CVAR_SERVER_INFO | CVAR_ARCHIVE, NULL); sv_public = Cvar_Get("sv_public", "0", 0, NULL); if (dedicated->value) sv_max_clients = Cvar_Get("sv_max_clients", "8", CVAR_SERVER_INFO | CVAR_LATCH, NULL); else sv_max_clients = Cvar_Get("sv_max_clients", "1", CVAR_SERVER_INFO | CVAR_LATCH, NULL); sv_framerate = Cvar_Get("sv_framerate", va("%d", SERVER_FRAME_RATE), CVAR_SERVER_INFO | CVAR_LATCH, NULL); sv_timeout = Cvar_Get("sv_timeout", va("%d", SERVER_TIMEOUT), 0, NULL); sv_udp_download = Cvar_Get("sv_udp_download", "1", CVAR_ARCHIVE, NULL); // set this so clients and server browsers can see it Cvar_Get("sv_protocol", va("%i", PROTOCOL), CVAR_SERVER_INFO | CVAR_NO_SET, NULL); Sv_InitCommands(); Sv_InitMasters(); Sb_Init(&net_message, net_message_buffer, sizeof(net_message_buffer)); Net_Config(NS_SERVER, true); }
/* * Cl_WriteDemoHeader * * Writes server_data, config_strings, and baselines once a non-delta * compressed frame arrives from the server. */ static void Cl_WriteDemoHeader(void) { byte buffer[MAX_MSG_SIZE]; size_buf_t msg; int i; int len; entity_state_t null_state; // write out messages to hold the startup information Sb_Init(&msg, buffer, sizeof(buffer)); // write the server data Msg_WriteByte(&msg, SV_CMD_SERVER_DATA); Msg_WriteLong(&msg, PROTOCOL); Msg_WriteLong(&msg, cl.server_count); Msg_WriteLong(&msg, cl.server_frame_rate); Msg_WriteByte(&msg, 1); // demo_server byte Msg_WriteString(&msg, Cvar_GetString("game")); Msg_WriteShort(&msg, cl.player_num); Msg_WriteString(&msg, cl.config_strings[CS_NAME]); // and config_strings for (i = 0; i < MAX_CONFIG_STRINGS; i++) { if (*cl.config_strings[i] != '\0') { if (msg.size + strlen(cl.config_strings[i]) + 32 > msg.max_size) { // write it out len = LittleLong(msg.size); Fs_Write(&len, 4, 1, cls.demo_file); Fs_Write(msg.data, msg.size, 1, cls.demo_file); msg.size = 0; } Msg_WriteByte(&msg, SV_CMD_CONFIG_STRING); Msg_WriteShort(&msg, i); Msg_WriteString(&msg, cl.config_strings[i]); } } // and baselines for (i = 0; i < MAX_EDICTS; i++) { entity_state_t *ent = &cl.entities[i].baseline; if (!ent->number) continue; if (msg.size + 64 > msg.max_size) { // write it out len = LittleLong(msg.size); Fs_Write(&len, 4, 1, cls.demo_file); Fs_Write(msg.data, msg.size, 1, cls.demo_file); msg.size = 0; } memset(&null_state, 0, sizeof(null_state)); Msg_WriteByte(&msg, SV_CMD_ENTITY_BASELINE); Msg_WriteDeltaEntity(&null_state, &cl.entities[i].baseline, &msg, true, true); } Msg_WriteByte(&msg, SV_CMD_CBUF_TEXT); Msg_WriteString(&msg, "precache 0\n"); // write it to the demo file len = LittleLong(msg.size); Fs_Write(&len, 4, 1, cls.demo_file); Fs_Write(msg.data, msg.size, 1, cls.demo_file); Com_Print("Recording to %s.\n", cls.demo_path); // the rest of the demo file will be individual frames }
/* * Svc_Connect * * A connection request that did not come from the master. */ static void Svc_Connect(void) { char user_info[MAX_USER_INFO_STRING]; sv_client_t *cl, *client; net_addr_t addr; int version; byte qport; unsigned int challenge; int i; Com_Debug("Svc_Connect()\n"); addr = net_from; version = atoi(Cmd_Argv(1)); // resolve protocol if (version != PROTOCOL) { Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nServer is version %d.\n", PROTOCOL); return; } qport = strtoul(Cmd_Argv(2), NULL, 0) & 0xff; challenge = strtoul(Cmd_Argv(3), NULL, 0); //copy user_info, leave room for ip stuffing strncpy(user_info, Cmd_Argv(4), sizeof(user_info) - 1 - 25); user_info[sizeof(user_info) - 1] = 0; if (*user_info == '\0') { // catch empty user_info Com_Print("Empty user_info from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nConnection refused.\n"); return; } if (strchr(user_info, '\xFF')) { // catch end of message in string exploit Com_Print("Illegal user_info contained xFF from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nConnection refused.\n"); return; } if (strlen(GetUserInfo(user_info, "ip"))) { // catch spoofed ips Com_Print("Illegal user_info contained ip from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nConnection refused.\n"); return; } if (!ValidateUserInfo(user_info)) { // catch otherwise invalid user_info Com_Print("Invalid user_info from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nConnection refused.\n"); return; } // force the ip so the game can filter on it SetUserInfo(user_info, "ip", Net_NetaddrToString(addr)); // enforce a valid challenge to avoid denial of service attack for (i = 0; i < MAX_CHALLENGES; i++) { if (Net_CompareClientNetaddr(addr, svs.challenges[i].addr)) { if (challenge == svs.challenges[i].challenge) { svs.challenges[i].challenge = 0; break; // good } Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nBad challenge.\n"); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nNo challenge for address.\n"); return; } // resolve the client slot client = NULL; // first check for an ungraceful reconnect (client crashed, perhaps) for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) { const net_chan_t *ch = &cl->netchan; if (cl->state == SV_CLIENT_FREE) // not in use, not interested continue; // the base address and either the qport or real port must match if (Net_CompareClientNetaddr(addr, ch->remote_address) && (qport == ch->qport || ch->remote_address.port == addr.port)) { client = cl; break; } } // otherwise, treat as a fresh connect to a new slot if (!client) { for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) { if (cl->state == SV_CLIENT_FREE) { // we have a free one client = cl; break; } } } // no soup for you, next!! if (!client) { Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nServer is full.\n"); Com_Debug("Rejected a connection.\n"); return; } // give the game a chance to reject this connection or modify the user_info if (!(svs.game->ClientConnect(client->edict, user_info))) { if (*GetUserInfo(user_info, "rejmsg")) { Netchan_OutOfBandPrint(NS_SERVER, addr, "print\n%s\nConnection refused.\n", GetUserInfo(user_info, "rejmsg")); } else { Netchan_OutOfBandPrint(NS_SERVER, addr, "print\nConnection refused.\n"); } Com_Debug("Game rejected a connection.\n"); return; } // parse some info from the info strings strncpy(client->user_info, user_info, sizeof(client->user_info) - 1); Sv_UserInfoChanged(client); // send the connect packet to the client Netchan_OutOfBandPrint(NS_SERVER, addr, "client_connect %s", sv_download_url->string); Netchan_Setup(NS_SERVER, &client->netchan, addr, qport); Sb_Init(&client->datagram, client->datagram_buf, sizeof(client->datagram_buf)); client->datagram.allow_overflow = true; client->last_message = svs.real_time; // don't timeout client->state = SV_CLIENT_CONNECTED; }