Files
Descent3/Descent3/dedicated_server.cpp
Jacob Coby a6a6869a7e 64 Bit-Safe Updates
Imported Icculus' 64-bit changes.

Changes `[un]signed long` data types to `[un]signed int`.
2024-04-29 14:10:09 -04:00

1071 lines
30 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* $Logfile: /DescentIII/Main/dedicated_server.cpp $
* $Revision: 1.5 $
* $Date: 2004/03/11 03:46:52 $
* $Author: kevinb $
*
* Dedicated server code
*
* $Log: dedicated_server.cpp,v $
* Revision 1.5 2004/03/11 03:46:52 kevinb
* Merge of 1.5 code from windows. Hopefully I didn't break anything!
*
* Revision 1.4 2001/01/13 21:48:45 icculus
* patched to (re)compile on win32.
*
* Revision 1.3 2000/06/03 14:33:49 icculus
* Merge with Outrage 1.4 tree...
*
*
* 67 10/17/01 4:52p Matt
* Use safe printf() to fix buffer overflow.
*
* 66 7/09/01 5:45p Matt
* Added buffer overflow checking to dedicated server read.
*
* 65 4/20/00 4:46p Matt
* Dedicated server messages now support sending to one player (or team)
*
* 64 4/20/00 2:23p Matt
* Bail from parse routine if null command; used to have an Int3(). Fixed
* the crash when ":" was typed in the dedicated server console.
*
* 63 3/23/00 2:34p Matt
* Fixed table error (max string len) that caused crashes when parsing
* MultiSettingsFile keywords.
*
* 62 10/21/99 5:39p Matt
* Mac merge
*
* 61 8/23/99 5:12p Kevin
* Fixed double frame interval calling for DMFC
*
* 60 7/20/99 12:57p Jason
* added dedicated server message_of_the_day
*
* 59 7/15/99 12:47p Jeff
* fixed dedicated server quiting when a player logs out from telnet.
* Adding logout command
*
* 58 7/14/99 8:57p Jeff
* don't parse out semi-colons
*
* 57 7/09/99 3:49p Jason
* fixed bugs/issues for patch
*
* 56 7/06/99 5:52p Kevin
* PXO & multiplayer fixes for the patch
*
* 55 6/10/99 4:12p Kevin
* Fixed SetLevel in dedicated server for HEAT.NET, and added
* level_names.str for level name localization.
*
* 54 5/24/99 9:55p Jason
* fixed stupid dedicated server ship allow thing. I swear I'm going to
* start killing people who keep adding things to multiplayer when they
* really don't know what they are doing. STOP!
*
* 53 5/12/99 1:57p Jason
* fixed yet more buggy/ugly code
*
* 52 5/10/99 5:35p Kevin
* New command line options for heat and scoring API enhancements
*
* 51 5/09/99 1:34p Kevin
* Added diffuculty level system to multiplayer
*
* 50 5/07/99 5:00p Jeff
* handle errors better with a dedicated server if it couldn't load the
* multiplayer dll
*
* 49 5/02/99 2:32p Kevin
* fixed various dedicated server problems.
*
* $NoKeywords: $
*/
#include <string.h>
#include <stdlib.h>
#ifndef __LINUX__
typedef int socklen_t;
#endif
#include "pstypes.h"
#include "pserror.h"
#include "pstring.h"
#include "cfile.h"
#include "inffile.h"
#include "dedicated_server.h"
#include "multi.h"
#include "args.h"
#include "AppConsole.h"
#include "ddio.h"
#include "newui.h"
#include "ui.h"
#include "multi_dll_mgr.h"
#include "multi_ui.h"
#include "Mission.h"
#include "multi_server.h"
#include "Macros.h"
#include "game.h"
#include "mem.h"
#include "stringtable.h"
#include "multi_save_settings.h"
#include "objinfo.h"
#include "rtperformance.h"
#include "player.h"
#include "stringtable.h"
#include "init.h"
#include "ship.h"
#include "hud.h"
bool Dedicated_server = false;
int Dedicated_start_level = 1;
int Dummy_dedicated_var;
char Dummy_dedicated_string[_MAX_PATH];
// If true, allow remote connections
int Dedicated_allow_remote = 0;
ushort Dedicated_listen_port = 2092;
char dedicated_telnet_password[65];
int Dedicated_num_teams = 1;
int CheckMissionForScript(char *mission, char *script, int dedicated_server_num_teams);
extern char Multi_message_of_the_day[];
extern char PXO_hosted_lobby_name[];
// These define the types of variables that can be set in the code through
// the dedicated server
cvar_entry CVars[] = {
{"PPS", CVAR_TYPE_INT, &Netgame.packets_per_second, 2, 20, CVAR_GAMEINIT}, // 0
{"TimeLimit", CVAR_TYPE_INT, &Netgame.timelimit, 0, 10000, CVAR_GAMEINIT},
{"KillGoal", CVAR_TYPE_INT, &Netgame.killgoal, 0, 10000, CVAR_GAMEINIT},
{"RespawnTime", CVAR_TYPE_INT, &Netgame.respawn_time, 0, 10000, CVAR_GAMEINIT},
{"GameName", CVAR_TYPE_STRING, &Netgame.name, -1, NETGAME_NAME_LEN, CVAR_GAMEINIT}, // 4
{"MissionName", CVAR_TYPE_STRING, &Netgame.mission, -1, MSN_NAMELEN, CVAR_GAMEINIT},
{"Scriptname", CVAR_TYPE_STRING, &Netgame.scriptname, -1, NETGAME_SCRIPT_LEN, CVAR_GAMEINIT},
{"ConnectionName", CVAR_TYPE_STRING, &Netgame.connection_name, -1, PAGENAME_LEN, CVAR_GAMEINIT},
{"Quit", CVAR_TYPE_NONE, NULL, -1, -1, CVAR_GAMEPLAY}, // 8
{"EndLevel", CVAR_TYPE_NONE, NULL, -1, -1, CVAR_GAMEPLAY},
#if 0 // def DEMO
{"MaxPlayers",CVAR_TYPE_INT,&Netgame.max_players,2,16,CVAR_GAMEINIT}, //10
#else
{"MaxPlayers", CVAR_TYPE_INT, &Netgame.max_players, 2, MAX_PLAYERS, CVAR_GAMEINIT}, // 10
#endif
{"Say", CVAR_TYPE_STRING, NULL, -1, 200, CVAR_GAMEPLAY}, // 11
{"UseSmoothing", CVAR_TYPE_INT, &Dummy_dedicated_var, 0, 1, CVAR_GAMEINIT}, // 12
{"SendRotVel", CVAR_TYPE_INT, &Dummy_dedicated_var, 0, 1, CVAR_GAMEINIT}, // 13
{"MultiSettingsFile", CVAR_TYPE_STRING, &Dummy_dedicated_string, -1, 40, CVAR_GAMEINIT}, // 14
{"DisallowPowerup", CVAR_TYPE_STRING, &Dummy_dedicated_string, -1, 40, CVAR_GAMEINIT | CVAR_GAMEPLAY}, // 15
{"AllowPowerup", CVAR_TYPE_STRING, &Dummy_dedicated_string, -1, 40, CVAR_GAMEINIT | CVAR_GAMEPLAY}, // 16
{"StartRtLog", CVAR_TYPE_NONE, NULL, -1, -1, CVAR_GAMEPLAY}, // 17
{"StopRtLog", CVAR_TYPE_NONE, NULL, -1, -1, CVAR_GAMEPLAY}, // 18
{"PXOUsername", CVAR_TYPE_STRING, &Auto_login_name, -1, 50, CVAR_GAMEINIT}, // 19
{"PXOPassword", CVAR_TYPE_STRING, &Auto_login_pass, -1, 50, CVAR_GAMEINIT}, // 20
{"BrightPlayers", CVAR_TYPE_INT, &Dummy_dedicated_var, -1, -1, CVAR_GAMEINIT}, // 21
{"Peer2Peer", CVAR_TYPE_INT, &Dummy_dedicated_var, -1, -1, CVAR_GAMEINIT}, // 22
{"AccurateCollisions", CVAR_TYPE_INT, &Dummy_dedicated_var, -1, -1, CVAR_GAMEINIT}, // 23
{"ConsolePassword", CVAR_TYPE_STRING, &dedicated_telnet_password, -1, 65, CVAR_GAMEINIT | CVAR_GAMEPLAY}, // 24
{"AllowRemoteConsole", CVAR_TYPE_INT, &Dedicated_allow_remote, 0, 1, CVAR_GAMEINIT}, // 25
{"RemoteConsolePort", CVAR_TYPE_INT, &Dedicated_listen_port, 1, 65535, CVAR_GAMEINIT}, // 26
{"Permissable", CVAR_TYPE_INT, &Dummy_dedicated_var, -1, -1, CVAR_GAMEINIT}, // 27
{"NumTeams", CVAR_TYPE_INT, &Dedicated_num_teams, 2, 4, CVAR_GAMEINIT}, // 28
{"PXOHostingLobby", CVAR_TYPE_STRING, &PXO_hosted_lobby_name, -1, 100, CVAR_GAMEINIT | CVAR_GAMEPLAY}, // 29
{"RandomizeRespawn", CVAR_TYPE_INT, &Dummy_dedicated_var, -1, -1, CVAR_GAMEINIT}, // 30
{"AllowMouselook", CVAR_TYPE_INT, &Dummy_dedicated_var, -1, -1, CVAR_GAMEINIT}, // 31
{"AudioTauntDelay", CVAR_TYPE_FLOAT, NULL, -1, -1, CVAR_GAMEINIT}, // 32
{"SetLevel", CVAR_TYPE_INT, NULL, -1, -1, CVAR_GAMEINIT | CVAR_GAMEPLAY}, // 33
{"SetDifficulty", CVAR_TYPE_INT, NULL, 0, 4, CVAR_GAMEINIT}, // 34
{"MOTD", CVAR_TYPE_STRING, &Multi_message_of_the_day, -1, HUD_MESSAGE_LENGTH * 2, CVAR_GAMEINIT}, // 35
};
#define CVAR_TIMELIMIT 1
#define CVAR_KILLGOAL 2
#define CVAR_QUIT 8
#define CVAR_ENDLEVEL 9
#define CVAR_MAXPLAYERS 10
#define CVAR_MESSAGE 11
#define CVAR_USESMOOTHING 12
#define CVAR_SENDROTVEL 13
#define CVAR_SETTINGS 14
#define CVAR_DISALLOW 15
#define CVAR_ALLOW 16
#define CVAR_STARTLOG 17
#define CVAR_STOPLOG 18
#define CVAR_BRIGHTPLAYERS 21
#define CVAR_PEER2PEER 22
#define CVAR_ACCURATE_COLL 23
#define CVAR_PERMISSABLE 27
#define CVAR_RANDOMIZERESPAWN 30
#define CVAR_ALLOWMOUSELOOKERS 31
#define CVAR_AUDIOTAUNTDELAY 32
#define CVAR_SETLEVEL 33
#define CVAR_SETDIFF 34
#define CVAR_MOTD 35
#define MAX_CVARS (sizeof(CVars) / sizeof(cvar_entry))
// Takes the data that the config file has an runs a server based on that data
// Returns true if ok, false if something is wrong
int RunServerConfigs() {
// Load the connection DLL
if (LoadMultiDLL(Netgame.connection_name)) {
CallMultiDLL(MT_AUTO_START);
if (!MultiDLLGameStarting) {
PrintDedicatedMessage(TXT_DS_COULDNTINIT, Netgame.connection_name);
PrintDedicatedMessage("\n");
return 0;
} else {
PrintDedicatedMessage(TXT_DS_DLLINIT);
PrintDedicatedMessage("\n");
}
} else {
PrintDedicatedMessage(TXT_DS_CONNECTLOADERR, Netgame.connection_name);
PrintDedicatedMessage("\n");
return 0;
}
// Put in the correct mission names for the oem builds
#if (defined(OEM) || defined(DEMO))
if (strcmpi("polaris.d3l", Netgame.mission) == 0) {
strcpy(Netgame.mission_name, "Polaris");
} else if (strcmpi("taurus.d3l", Netgame.mission) == 0) {
strcpy(Netgame.mission_name, "Taurus");
} else if (strcmpi("thecore.d3l", Netgame.mission) == 0) {
strcpy(Netgame.mission_name, "The Core");
}
#else
const char *p = GetMissionName(Netgame.mission);
strcpy(Netgame.mission_name, p);
#endif
// Load the mission
if (!LoadMission(Netgame.mission)) {
PrintDedicatedMessage(TXT_DS_LOADMISSIONERR, Netgame.mission);
PrintDedicatedMessage("\n");
return 0;
} else {
PrintDedicatedMessage(TXT_DS_MISSIONLOADED, Netgame.mission);
PrintDedicatedMessage("\n");
}
if (Current_mission.num_levels >= Dedicated_start_level) {
Current_mission.cur_level = Dedicated_start_level;
} else {
Current_mission.cur_level = 1;
}
// Start the actual server
int teams = CheckMissionForScript(Netgame.mission, Netgame.scriptname, Dedicated_num_teams);
if (teams == -1) {
PrintDedicatedMessage(TXT_MSNNOTCOMPATIBLE);
PrintDedicatedMessage("\n");
return 0;
} else {
MultiStartServer(0, Netgame.scriptname, teams);
strcpy(Players[0].callsign, TXT_DS_SERVERNAME);
}
InitDedicatedSocket(Dedicated_listen_port);
return 1;
}
int DedicatedServerLex(const char *command) {
for (int i = 0; i < MAX_CVARS; i++)
if (stricmp(CVars[i].varname, command) == 0)
return i;
return INFFILE_ERROR;
}
// Reads in the server config file for a dedicated server
// Returns true if everything is ok
int LoadServerConfigFile() {
InfFile inf;
char operand[INFFILE_LINELEN]; // operand
int t = FindArgChar("-dedicated", 'd');
// int t=FindArg ("-dedicated");
if (!t) {
PrintDedicatedMessage(TXT_DS_BADCOMMANDLINE);
PrintDedicatedMessage("\n");
return 0;
}
// Set our defaults
MultiResetSettings();
// Make all ships available
for (int i = 0; i < MAX_SHIPS; i++) {
if (Ships[i].used)
PlayerSetShipPermission(-1, Ships[i].name, true);
}
if (GameArgs[t + 1][0]) {
strcpy(Netgame.server_config_name, GameArgs[t + 1]);
} else {
PrintDedicatedMessage(TXT_DS_MISSINGCONFIG);
PrintDedicatedMessage("\n");
return 0;
}
// open file
if (!inf.Open(Netgame.server_config_name, "[server config file]", DedicatedServerLex)) {
PrintDedicatedMessage(TXT_DS_BADCONFIG, Netgame.server_config_name);
PrintDedicatedMessage("\n");
return 0;
}
// check if valid dedicated server file
while (inf.ReadLine()) {
int cmd;
while ((cmd = inf.ParseLine(operand, INFFILE_LINELEN)) > INFFILE_ERROR) {
SetCVar(CVars[cmd].varname, operand, true);
}
}
inf.Close();
if (!RunServerConfigs())
return 0;
return 1;
}
// Starts a dedicated server and loads the server config file
void StartDedicatedServer() {
int t = FindArgChar("-dedicated", 'd');
// int t=FindArg ("-dedicated");
if (!t)
return;
Dedicated_server = true;
}
// Sets the value for a cvar NONE type
void SetCVarNone(int index) {
if (CVars[index].type != CVAR_TYPE_NONE)
return; // Only handles ints
// Do command specific stuff here
if (index == CVAR_QUIT) {
if (NetPlayers[Player_num].sequence == NETSEQ_PLAYING) {
MultiLeaveGame();
SetFunctionMode(QUIT_MODE);
}
}
if (index == CVAR_ENDLEVEL) {
if (NetPlayers[Player_num].sequence == NETSEQ_PLAYING) {
MultiEndLevel();
}
}
if (index == CVAR_STARTLOG)
rtp_StartLog();
if (index == CVAR_STOPLOG)
rtp_StopLog();
}
// Sets the value for a cvar INT type
void SetCVarInt(int index, int val) {
void *dest_variable;
if (CVars[index].type != CVAR_TYPE_INT)
return; // Only handles ints
dest_variable = CVars[index].dest_variable;
if (!(CVars[index].var_min == -1 && CVars[index].var_max == -1)) {
if (val < CVars[index].var_min)
val = (int)CVars[index].var_min;
if (val > CVars[index].var_max)
val = (int)CVars[index].var_max;
}
// Do command specific stuff here
switch (index) {
case CVAR_TIMELIMIT: {
if (val > 0)
Netgame.flags |= NF_TIMER;
else
Netgame.flags &= ~NF_TIMER;
} break;
case CVAR_KILLGOAL: {
if (val > 0)
Netgame.flags |= NF_KILLGOAL;
else
Netgame.flags &= ~NF_KILLGOAL;
} break;
case CVAR_MAXPLAYERS: {
if (val > MAX_PLAYERS)
val = MAX_PLAYERS;
else if (val < 2)
val = 2;
} break;
case CVAR_BRIGHTPLAYERS: {
if (val)
Netgame.flags |= NF_BRIGHT_PLAYERS;
else
Netgame.flags &= ~NF_BRIGHT_PLAYERS;
} break;
case CVAR_USESMOOTHING: {
if (val)
Netgame.flags |= NF_USE_SMOOTHING;
else
Netgame.flags &= ~NF_USE_SMOOTHING;
} break;
case CVAR_PEER2PEER: {
if (val) {
Netgame.flags |= NF_PEER_PEER;
Netgame.flags &= ~NF_PERMISSABLE;
} else
Netgame.flags &= ~NF_PEER_PEER;
} break;
case CVAR_PERMISSABLE: {
if (val) {
Netgame.flags |= NF_PERMISSABLE;
Netgame.flags &= ~NF_PEER_PEER;
} else
Netgame.flags &= ~NF_PERMISSABLE;
} break;
case CVAR_RANDOMIZERESPAWN: {
if (val) {
Netgame.flags |= NF_RANDOMIZE_RESPAWN;
} else
Netgame.flags &= ~NF_RANDOMIZE_RESPAWN;
} break;
case CVAR_ACCURATE_COLL: {
if (val)
Netgame.flags |= NF_USE_ACC_WEAP;
else
Netgame.flags &= ~NF_USE_ACC_WEAP;
} break;
case CVAR_SENDROTVEL: {
if (val)
Netgame.flags |= NF_SENDROTVEL;
else
Netgame.flags &= ~NF_SENDROTVEL;
} break;
case CVAR_SETLEVEL: {
if (NetPlayers[Player_num].sequence == NETSEQ_PLAYING) {
if (val >= 1 && val <= Current_mission.num_levels) {
Multi_next_level = val;
MultiEndLevel();
}
} else {
if (val >= 1) {
Dedicated_start_level = val;
}
}
} break;
case CVAR_ALLOWMOUSELOOKERS: {
if (val)
Netgame.flags |= NF_ALLOW_MLOOK;
else
Netgame.flags &= ~NF_ALLOW_MLOOK;
} break;
case CVAR_SETDIFF: {
Netgame.difficulty = val;
} break;
}
if (dest_variable)
*((int *)dest_variable) = val;
}
// Sets the value for a cvar FLOAT type
void SetCVarFloat(int index, float val) {
void *dest_variable;
if (CVars[index].type != CVAR_TYPE_FLOAT)
return; // Only handles ints
dest_variable = CVars[index].dest_variable;
if (!(CVars[index].var_min == -1 && CVars[index].var_max == -1)) {
if (val < CVars[index].var_min)
val = (float)CVars[index].var_min;
if (val > CVars[index].var_max)
val = (float)CVars[index].var_max;
}
if (dest_variable)
*((float *)dest_variable) = val;
// Do command specific stuff here
switch (index) {
case CVAR_AUDIOTAUNTDELAY:
MultiSetAudioTauntTime(val);
break;
}
}
// Sets the value for a cvar string type
void SetCVarString(int index, const char *val) {
void *dest_variable;
if (CVars[index].type != CVAR_TYPE_STRING)
return; // Only handles ints
if (index == CVAR_MESSAGE) {
// say the message to everyone
char str[255];
int to_whom;
const char *msg = GetMessageDestination(val, &to_whom);
if (to_whom != MULTI_SEND_MESSAGE_ALL)
val = msg;
Psprintf(str, sizeof(str), TXT_HUDSAY, Players[Player_num].callsign, val);
MultiSendMessageFromServer(GR_RGB(0, 128, 255), str, to_whom);
return;
}
if (index == CVAR_SETTINGS) {
if (MultiLoadSettings(val)) {
PrintDedicatedMessage(TXT_DS_SETTINGSLOADED);
PrintDedicatedMessage("\n");
} else {
PrintDedicatedMessage(TXT_DS_SETTINGSERR);
PrintDedicatedMessage("\n");
}
}
if (index == CVAR_DISALLOW) {
int objid = FindObjectIDName(IGNORE_TABLE(val));
if (objid != -1) {
PrintDedicatedMessage(TXT_DS_DISALLOWOBJECT, Object_info[objid].name);
PrintDedicatedMessage("\n");
Object_info[objid].multi_allowed = 0;
}
}
if (index == CVAR_ALLOW) {
int objid = FindObjectIDName(IGNORE_TABLE(val));
if (objid != -1) {
PrintDedicatedMessage(TXT_DS_ALLOWOBJECTS, Object_info[objid].name);
PrintDedicatedMessage("\n");
Object_info[objid].multi_allowed = 1;
}
}
dest_variable = CVars[index].dest_variable;
if (dest_variable) {
strncpy((char *)dest_variable, val, CVars[index].var_max);
}
}
// The accessor function that sets the value of a cvar
void SetCVar(const char *cvar_string, const char *cvar_argument, bool game_init) {
int i;
int done = 0;
for (i = 0; i < MAX_CVARS && !done; i++) {
if (!(stricmp(cvar_string, CVars[i].varname))) {
done = 1;
if (game_init) {
if (!(CVars[i].permissions & CVAR_GAMEINIT)) {
PrintDedicatedMessage(TXT_DS_CANTUSEINIT);
PrintDedicatedMessage("\n");
continue;
}
} else {
if (!(CVars[i].permissions & CVAR_GAMEPLAY)) {
PrintDedicatedMessage(TXT_DS_CANTUSEPLAY);
PrintDedicatedMessage("\n");
continue;
}
}
if (CVars[i].type != CVAR_TYPE_NONE && i != CVAR_MESSAGE) {
PrintDedicatedMessage(TXT_DS_VARSET, cvar_string, cvar_argument);
PrintDedicatedMessage("\n");
}
switch (CVars[i].type) {
case CVAR_TYPE_INT: {
int val = atoi(cvar_argument);
SetCVarInt(i, val);
} break;
case CVAR_TYPE_NONE: {
SetCVarNone(i);
} break;
case CVAR_TYPE_FLOAT: {
float val = (float)atof(cvar_argument);
SetCVarFloat(i, val);
} break;
case CVAR_TYPE_STRING: {
SetCVarString(i, cvar_argument);
} break;
default:
Int3(); // Get Jason, no cvar argument defined
break;
}
}
}
}
// The accessor function that sets the value of a cvar...INT only
void SetCVar(const char *cvar_string, int cvar_argument) {
int i;
int done = 0;
for (i = 0; i < MAX_CVARS && !done; i++) {
if (!(stricmp(cvar_string, CVars[i].varname))) {
done = 1;
switch (CVars[i].type) {
case CVAR_TYPE_INT: {
SetCVarInt(i, cvar_argument);
} break;
default:
Int3(); // You are using the wrong accessor function...this is for INTS only!
break;
}
}
}
}
// The accessor function that sets the value of a cvar...FLOAT only
void SetCVar(const char *cvar_string, float cvar_argument) {
int i;
int done = 0;
for (i = 0; i < MAX_CVARS && !done; i++) {
if (!(stricmp(cvar_string, CVars[i].varname))) {
done = 1;
switch (CVars[i].type) {
case CVAR_TYPE_FLOAT: {
SetCVarFloat(i, cvar_argument);
} break;
default:
Int3(); // You are using the wrong accessor function...this is for INTS only!
break;
}
}
}
}
void ParseLine(char *srcline, char *command, char *operand, int cmdlen, int oprlen) {
// tokenize line.
char *opr, *cmd;
if (strlen(srcline) == 0)
return;
// parse out command and operand (command=str)
// m_lineptr = command, opr = operand, nextcmd = next lineptr.
cmd = strtok(srcline, " \t=:");
if (cmd)
opr = strtok(NULL, "");
else
return; // null command, so bail
// clean out trailing and preceeding trash (tabs, spaces, etc)
CleanupStr(command, cmd, cmdlen);
if (opr)
CleanupStr(operand, opr, oprlen);
else
operand[0] = 0;
}
// Called once per frame for the dedicated server
void DoDedicatedServerFrame() {
char str[255];
char command[255] = {}; // operand
char operand[255]; // operand
ListenDedicatedSocket();
DedicatedReadTelnet();
// CallGameDLL (EVT_GAME_INTERVAL,&DLLInfo);
if (ServerTimeout) {
float now = timer_GetTime();
int sincelastpacket = ((float)(now - LastPacketReceived)) / 60;
// See if it's time to shutdown...
if (sincelastpacket > ServerTimeout) {
int quitno = DedicatedServerLex("Quit");
if (quitno >= 0)
SetCVar(CVars[quitno].varname, "", false);
}
}
bool new_command = con_Input(str, 255);
if (!new_command)
return;
if (str[0] == '$') {
DLLInfo.input_string = str;
CallGameDLL(EVT_CLIENT_INPUT_STRING, &DLLInfo);
return;
}
ParseLine(str, command, operand, 255, 255);
if (!command[0])
return;
int index = DedicatedServerLex(command);
if (index < 0) {
PrintDedicatedMessage(TXT_DS_BADCOMMAND);
PrintDedicatedMessage("\n");
return;
}
// Everything ok, set command
SetCVar(CVars[index].varname, operand, false);
}
// Prints a message to the console if the dedicated server is active
void PrintDedicatedMessage(const char *fmt, ...) {
if (!Dedicated_server)
return;
char buf[CON_MAX_STRINGLEN];
va_list args;
va_start(args, fmt);
Pvsprintf(buf, CON_MAX_STRINGLEN, fmt, args);
va_end(args);
con_Printf(buf);
DedicatedSocketputs(buf);
}
#ifdef __LINUX__
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/termios.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include "linux/linux_fix.h"
#include "errno.h"
#define BOOL bool
#ifndef SOCKET
#define SOCKET int
#endif
#define SOCKADDR_IN sockaddr_in
#define SOCKADDR sockaddr
#define INVALID_SOCKET -1
// Winsock = sockets error translation
#define WSAEWOULDBLOCK EWOULDBLOCK
#define WSAEINVAL EINVAL
#define WSAENOPROTOOPT ENOPROTOOPT
#define WSAGetLastError(a) errno
#define SOCKET_ERROR -1
#endif
typedef struct dedicated_socket {
SOCKET sock;
SOCKADDR_IN addr;
char input[255];
bool validated;
dedicated_socket *prev;
dedicated_socket *next;
} dedicated_socket;
dedicated_socket *Head_sock = NULL;
SOCKET dedicated_listen_socket = INVALID_SOCKET;
#define DEDICATED_LOGIN_PROMPT TXT_DS_ENTERPASS
void InitDedicatedSocket(ushort port) {
SOCKADDR_IN sock_addr;
memset(&sock_addr, 0, sizeof(SOCKADDR_IN));
sock_addr.sin_family = AF_INET;
unsigned int my_ip;
my_ip = nw_GetThisIP();
memcpy(&sock_addr.sin_addr.s_addr, &my_ip, sizeof(uint));
sock_addr.sin_port = htons(port);
dedicated_listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == dedicated_listen_socket) {
mprintf((0, "Unable to create listen socket for dedicated server!\n"));
return;
}
if (SOCKET_ERROR == bind(dedicated_listen_socket, (SOCKADDR *)&sock_addr, sizeof(sock_addr))) {
mprintf((0, "Unable to bind listen socket for dedicated server!\n"));
return;
}
if (listen(dedicated_listen_socket, 1)) {
mprintf((0, "Unable to listen on dedicated server socket!\n"));
return;
}
// Make the socket non-blocking
unsigned int argp = 1;
#if defined(WIN32)
ioctlsocket(dedicated_listen_socket, FIONBIO, &argp);
#elif defined(__LINUX__)
ioctl(dedicated_listen_socket, FIONBIO, &argp);
#endif
}
void ListenDedicatedSocket(void) {
SOCKET incoming_socket;
SOCKADDR_IN conn_addr;
// int addrlen = sizeof(conn_addr);
socklen_t addrlen = sizeof(conn_addr);
incoming_socket = accept(dedicated_listen_socket, (SOCKADDR *)&conn_addr, &addrlen);
if (INVALID_SOCKET != incoming_socket) {
// Make the socket non-blocking
unsigned int argp = 1;
#if defined(WIN32)
ioctlsocket(incoming_socket, FIONBIO, &argp);
#elif defined(__LINUX__)
ioctl(incoming_socket, FIONBIO, &argp);
#endif
if (!Dedicated_allow_remote) {
// Check to see if this came in from the local address
unsigned int localhost = inet_addr("127.0.0.1");
if (memcmp(&localhost, &conn_addr.sin_addr, sizeof(localhost)) != 0) {
mprintf((0, "Rejecting connection from remote host!\n"));
PrintDedicatedMessage(TXT_DS_REJECTREMOTE, inet_ntoa(conn_addr.sin_addr));
PrintDedicatedMessage("\n");
shutdown(incoming_socket, 2);
#if defined(WIN32)
closesocket(incoming_socket);
#elif defined(__LINUX__)
close(incoming_socket);
#endif
return;
}
}
// At this point it's considered a valid connection
PrintDedicatedMessage(TXT_DS_NEWCONNECT, inet_ntoa(conn_addr.sin_addr));
PrintDedicatedMessage("\n");
dedicated_socket *new_socket;
new_socket = (dedicated_socket *)mem_malloc(sizeof(dedicated_socket));
if (Head_sock)
Head_sock->prev = new_socket;
new_socket->next = Head_sock;
new_socket->prev = NULL;
Head_sock = new_socket;
memcpy(&new_socket->addr, &conn_addr, sizeof(new_socket->addr));
new_socket->input[0] = '\0';
new_socket->sock = incoming_socket;
new_socket->validated = false;
// Give the new connection the login prompt
send(new_socket->sock, DEDICATED_LOGIN_PROMPT, strlen(DEDICATED_LOGIN_PROMPT) + 1, 0);
}
}
void DedicatedSocketputs(char *str) {
char newstr[300];
char buf[2];
memset(newstr, 0, 300);
int len = strlen(str);
for (int i = 0; i < len; i++) {
if (str[i] == '\n') {
strcat(newstr, "\r\n");
} else {
buf[0] = str[i];
buf[1] = '\0';
strcat(newstr, buf);
}
}
dedicated_socket *conn;
conn = Head_sock;
while (conn) {
if (conn->validated) {
send(conn->sock, newstr, strlen(newstr) + 1, 0);
}
conn = conn->next;
}
}
dedicated_socket *DedicatedLogoutTelnet(dedicated_socket *conn) {
// user is no longer valid (so we don't attempt to send them a message
conn->validated = false;
PrintDedicatedMessage(TXT_DS_REMOTECLOSE, inet_ntoa(conn->addr.sin_addr));
PrintDedicatedMessage("\n");
shutdown(conn->sock, 2);
#if defined(WIN32)
closesocket(conn->sock);
#elif defined(__LINUX__)
close(conn->sock);
#endif
dedicated_socket *prev;
prev = conn->prev;
if (prev) {
prev->next = conn->next;
} else {
Head_sock = conn->next;
if (Head_sock)
Head_sock->prev = NULL;
}
mem_free(conn);
return prev;
}
void DedicatedReadTelnet(void) {
dedicated_socket *conn;
char buf[5];
conn = Head_sock;
while (conn) {
int bytesread = 1;
while (bytesread >= 0 && conn) {
// Read one byte at a time, looking for a CR
bytesread = recv(conn->sock, buf, 1, 0);
if (bytesread == 0) {
// Connection closed. Remove it from the list, and inform the other(?) users.
conn = DedicatedLogoutTelnet(conn);
break;
} else if (bytesread > 0) {
buf[1] = '\0';
// When we see a CR, process the line
if ((buf[0] == 0x0a) || (buf[0] == 0x0d)) {
if (conn->input[0] == 0) {
continue;
}
send(conn->sock, "\r\n", strlen("\r\n"), 0);
if (conn->validated) {
char command[255] = {}; // operand
char operand[255]; // operand
if (!stricmp(conn->input, "logout")) {
// log the connection out
conn = DedicatedLogoutTelnet(conn);
} else {
// Process the string
PrintDedicatedMessage("[%s] %s\n", inet_ntoa(conn->addr.sin_addr), conn->input);
if (conn->input[0] == '$') {
DLLInfo.input_string = conn->input;
CallGameDLL(EVT_CLIENT_INPUT_STRING, &DLLInfo);
conn->input[0] = '\0';
return;
}
ParseLine(conn->input, command, operand, 255, 255);
conn->input[0] = '\0';
if (!command[0])
return;
int index = DedicatedServerLex(command);
if (index < 0) {
PrintDedicatedMessage(TXT_DS_BADCOMMAND);
PrintDedicatedMessage("\n");
return;
}
// Everything ok, set command
SetCVar(CVars[index].varname, operand, false);
}
} else {
// All we want to see from them is the correct password
if (strcmp(conn->input, dedicated_telnet_password) == 0) {
// They logged in ok!
conn->validated = true;
send(conn->sock, "\r\n", strlen("\r\n") + 1, 0);
PrintDedicatedMessage(TXT_DS_REMOTELOGGEDIN, inet_ntoa(conn->addr.sin_addr));
PrintDedicatedMessage("\n");
conn->input[0] = '\0';
} else {
PrintDedicatedMessage(TXT_DS_BADPASS, inet_ntoa(conn->addr.sin_addr));
PrintDedicatedMessage("\n");
shutdown(conn->sock, 2);
#if defined(WIN32)
closesocket(conn->sock);
#elif defined(__LINUX__)
close(conn->sock);
#endif
}
}
} else {
if (buf[0] == 8) {
// handle backspace
char delstr[5];
delstr[0] = 8;
delstr[1] = ' ';
delstr[2] = 8;
delstr[3] = '\0';
conn->input[strlen(conn->input) - 1] = '\0';
buf[1] = '\0';
send(conn->sock, delstr, strlen(delstr), 0);
} else {
if (strlen(conn->input) + bytesread >= sizeof(conn->input)) {
conn->input[0] = 0;
const char *s = "\n\rCommand line too long\n\r";
send(conn->sock, s, strlen(s), 0);
} else {
strcat(conn->input, buf);
send(conn->sock, buf, 1, 0);
}
}
}
}
}
if (conn)
conn = conn->next;
}
}