#include "server.h"
void SV_SetMaster_f(void) {
int i, slot;
if(!dedicated->value) {
Com_Printf("Only dedicated servers use masters.\n");
return;
}
Cvar_Set("public", "1");
for(i = 1; i < MAX_MASTERS; i++)
memset(&master_adr[i], 0, sizeof(master_adr[i]));
slot = 1; for(i = 1; i < Cmd_Argc(); i++) {
if(slot == MAX_MASTERS)
break;
if(!NET_StringToAdr(Cmd_Argv(i), &master_adr[i])) {
Com_Printf("Bad address: %s\n", Cmd_Argv(i));
continue;
}
if(master_adr[slot].port == 0)
master_adr[slot].port = BigShort(PORT_MASTER);
Com_Printf("Master server at %s\n", NET_AdrToString(master_adr[slot]));
Com_Printf("Sending a ping.\n");
Netchan_OutOfBandPrint(NS_SERVER, master_adr[slot], "ping");
slot++;
}
svs.last_heartbeat = -9999999;
}
bool SV_SetPlayer(void) {
client_t *cl;
int i;
int idnum;
char *s;
if(Cmd_Argc() < 2)
return false;
s = Cmd_Argv(1);
if(s[0] >= '0' && s[0] <= '9') {
idnum = atoi(Cmd_Argv(1));
if(idnum < 0 || idnum >= maxclients->value) {
Com_Printf("Bad client slot: %i\n", idnum);
return false;
}
sv_client = &svs.clients[idnum];
sv_player = sv_client->edict;
if(!sv_client->state) {
Com_Printf("Client %i is not active\n", idnum);
return false;
}
return true;
}
for(i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) {
if(!cl->state)
continue;
if(!strcmp(cl->name, s)) {
sv_client = cl;
sv_player = sv_client->edict;
return true;
}
}
Com_Printf("Userid %s is not on the server\n", s);
return false;
}
void SV_WipeSavegame(char *savename) {
char name[MAX_OSPATH];
char *s;
Com_DPrintf("SV_WipeSaveGame(%s)\n", savename);
Com_sprintf(name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), savename);
remove(name);
Com_sprintf(name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir(), savename);
remove(name);
Com_sprintf(name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir(), savename);
s = Sys_FindFirst(name, 0, 0);
while(s) {
remove(s);
s = Sys_FindNext(0, 0);
}
Sys_FindClose();
Com_sprintf(name, sizeof(name), "%s/save/%s/*.sv2", FS_Gamedir(), savename);
s = Sys_FindFirst(name, 0, 0);
while(s) {
remove(s);
s = Sys_FindNext(0, 0);
}
Sys_FindClose();
}
void SV_CopyFile(char *src, char *dst) {
FILE *f1, *f2;
int l;
byte buffer[65536];
Com_DPrintf("SV_CopyFile (%s, %s)\n", src, dst);
f1 = fopen(src, "rb");
if(!f1)
return;
f2 = fopen(dst, "wb");
if(!f2) {
fclose(f1);
return;
}
while(1) {
l = fread(buffer, 1, sizeof(buffer), f1);
if(!l)
break;
fwrite(buffer, 1, l, f2);
}
fclose(f1);
fclose(f2);
}
void SV_CopySaveGame(char *src, char *dst) {
char name[MAX_OSPATH], name2[MAX_OSPATH];
int l, len;
char *found;
Com_DPrintf("SV_CopySaveGame(%s, %s)\n", src, dst);
SV_WipeSavegame(dst);
Com_sprintf(name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), src);
Com_sprintf(name2, sizeof(name2), "%s/save/%s/server.ssv", FS_Gamedir(), dst);
FS_CreatePath(name2);
SV_CopyFile(name, name2);
Com_sprintf(name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir(), src);
Com_sprintf(name2, sizeof(name2), "%s/save/%s/game.ssv", FS_Gamedir(), dst);
SV_CopyFile(name, name2);
Com_sprintf(name, sizeof(name), "%s/save/%s/", FS_Gamedir(), src);
len = strlen(name);
Com_sprintf(name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir(), src);
found = Sys_FindFirst(name, 0, 0);
while(found) {
strcpy(name + len, found + len);
Com_sprintf(name2, sizeof(name2), "%s/save/%s/%s", FS_Gamedir(), dst, found + len);
SV_CopyFile(name, name2);
l = strlen(name);
strcpy(name + l - 3, "sv2");
l = strlen(name2);
strcpy(name2 + l - 3, "sv2");
SV_CopyFile(name, name2);
found = Sys_FindNext(0, 0);
}
Sys_FindClose();
}
void SV_WriteLevelFile(void) {
char name[MAX_OSPATH];
FILE *f;
Com_DPrintf("SV_WriteLevelFile()\n");
Com_sprintf(name, sizeof(name), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name);
f = fopen(name, "wb");
if(!f) {
Com_Printf("Failed to open %s\n", name);
return;
}
fwrite(sv.configstrings, sizeof(sv.configstrings), 1, f);
CM_WritePortalState(CMODEL_A, f);
fclose(f);
Com_sprintf(name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
ge->WriteLevel(name);
}
void SV_ReadLevelFile(void) {
char name[MAX_OSPATH];
FILE *f;
Com_DPrintf("SV_ReadLevelFile()\n");
Com_sprintf(name, sizeof(name), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name);
f = fopen(name, "rb");
if(!f) {
Com_Printf("Failed to open %s\n", name);
return;
}
FS_Read(sv.configstrings, sizeof(sv.configstrings), f);
CM_ReadPortalState(CMODEL_A, f);
fclose(f);
Com_sprintf(name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
ge->ReadLevel(name);
}
void SV_WriteServerFile(bool autosave) {
FILE *f;
cvar_t *var;
char name[MAX_OSPATH], string[128];
char comment[32];
time_t aclock;
struct tm *newtime;
Com_DPrintf("SV_WriteServerFile(%s)\n", autosave ? "true" : "false");
Com_sprintf(name, sizeof(name), "%s/save/current/server.ssv", FS_Gamedir());
f = fopen(name, "wb");
if(!f) {
Com_Printf("Couldn't write %s\n", name);
return;
}
memset(comment, 0, sizeof(comment));
if(!autosave) {
time(&aclock);
newtime = localtime(&aclock);
Com_sprintf(comment, sizeof(comment), "%2i:%i%i %2i/%2i ", newtime->tm_hour, newtime->tm_min / 10,
newtime->tm_min % 10, newtime->tm_mon + 1, newtime->tm_mday);
strncat(comment, sv.configstrings[CS_NAME], sizeof(comment) - 1 - strlen(comment));
} else { Com_sprintf(comment, sizeof(comment), "ENTERING %s", sv.configstrings[CS_NAME]);
}
fwrite(comment, 1, sizeof(comment), f);
fwrite(svs.mapcmd, 1, sizeof(svs.mapcmd), f);
for(var = cvar_vars; var; var = var->next) {
if(!(var->flags & CVAR_LATCH))
continue;
if(strlen(var->name) >= sizeof(name) - 1 || strlen(var->string) >= sizeof(string) - 1) {
Com_Printf("Cvar too long: %s = %s\n", var->name, var->string);
continue;
}
memset(name, 0, sizeof(name));
memset(string, 0, sizeof(string));
strcpy(name, var->name);
strcpy(string, var->string);
fwrite(name, 1, sizeof(name), f);
fwrite(string, 1, sizeof(string), f);
}
fclose(f);
Com_sprintf(name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir());
ge->WriteGame(name, autosave);
}
void SV_ReadServerFile(void) {
FILE *f;
char name[MAX_OSPATH], string[128];
char comment[32];
char mapcmd[MAX_TOKEN_CHARS];
Com_DPrintf("SV_ReadServerFile()\n");
Com_sprintf(name, sizeof(name), "%s/save/current/server.ssv", FS_Gamedir());
f = fopen(name, "rb");
if(!f) {
Com_Printf("Couldn't read %s\n", name);
return;
}
FS_Read(comment, sizeof(comment), f);
FS_Read(mapcmd, sizeof(mapcmd), f);
while(1) {
if(!fread(name, 1, sizeof(name), f))
break;
FS_Read(string, sizeof(string), f);
Com_DPrintf("Set %s = %s\n", name, string);
Cvar_ForceSet(name, string);
}
fclose(f);
SV_InitGame();
strcpy(svs.mapcmd, mapcmd);
Com_sprintf(name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir());
ge->ReadGame(name);
}
void SV_DemoMap_f(void) { SV_Map(true, Cmd_Argv(1), false); }
void SV_GameMap_f(void) {
char *map;
int i;
client_t *cl;
bool *savedInuse;
if(Cmd_Argc() != 2) {
Com_Printf("USAGE: gamemap <map>\n");
return;
}
Com_DPrintf("SV_GameMap(%s)\n", Cmd_Argv(1));
FS_CreatePath(va("%s/save/current/", FS_Gamedir()));
map = Cmd_Argv(1);
if(map[0] == '*') {
SV_WipeSavegame("current");
} else { if(sv.state == ss_game) {
savedInuse = malloc(maxclients->value * sizeof(bool));
for(i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) {
savedInuse[i] = cl->edict->inuse;
cl->edict->inuse = false;
}
SV_WriteLevelFile();
for(i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
cl->edict->inuse = savedInuse[i];
free(savedInuse);
}
}
SV_Map(false, Cmd_Argv(1), false);
strncpy(svs.mapcmd, Cmd_Argv(1), sizeof(svs.mapcmd) - 1);
if(!dedicated->value) {
SV_WriteServerFile(true);
SV_CopySaveGame("current", "save0");
}
}
void SV_Map_f(void) {
char *map;
char expanded[MAX_QPATH];
map = Cmd_Argv(1);
if(!strstr(map, ".")) {
Com_sprintf(expanded, sizeof(expanded), "maps/%s.bsp", map);
if(FS_LoadFile(expanded, NULL) == -1) {
Com_Printf("Can't find %s\n", expanded);
return;
}
}
sv.state = ss_dead; SV_WipeSavegame("current");
SV_GameMap_f();
}
void SV_Loadgame_f(void) {
char name[MAX_OSPATH];
FILE *f;
char *dir;
if(Cmd_Argc() != 2) {
Com_Printf("USAGE: loadgame <directory>\n");
return;
}
Com_Printf("Loading game...\n");
dir = Cmd_Argv(1);
if(strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) {
Com_Printf("Bad savedir.\n");
}
Com_sprintf(name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), Cmd_Argv(1));
f = fopen(name, "rb");
if(!f) {
Com_Printf("No such savegame: %s\n", name);
return;
}
fclose(f);
SV_CopySaveGame(Cmd_Argv(1), "current");
SV_ReadServerFile();
sv.state = ss_dead; SV_Map(false, svs.mapcmd, true);
}
void SV_Savegame_f(void) {
char *dir;
if(sv.state != ss_game) {
Com_Printf("You must be in a game to save.\n");
return;
}
if(Cmd_Argc() != 2) {
Com_Printf("USAGE: savegame <directory>\n");
return;
}
if(Cvar_VariableValue("deathmatch")) {
Com_Printf("Can't savegame in a deathmatch\n");
return;
}
if(!strcmp(Cmd_Argv(1), "current")) {
Com_Printf("Can't save to 'current'\n");
return;
}
if(maxclients->value == 1 && svs.clients[0].edict->client->ps.stats[STAT_HEALTH] <= 0) {
Com_Printf("\nCan't savegame while dead!\n");
return;
}
dir = Cmd_Argv(1);
if(strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) {
Com_Printf("Bad savedir.\n");
}
Com_Printf("Saving game...\n");
SV_WriteLevelFile();
SV_WriteServerFile(false);
SV_CopySaveGame("current", dir);
Com_Printf("Done.\n");
}
void SV_Kick_f(void) {
if(!svs.initialized) {
Com_Printf("No server running.\n");
return;
}
if(Cmd_Argc() != 2) {
Com_Printf("Usage: kick <userid>\n");
return;
}
if(!SV_SetPlayer())
return;
SV_BroadcastPrintf(PRINT_HIGH, "%s was kicked\n", sv_client->name);
SV_ClientPrintf(sv_client, PRINT_HIGH, "You were kicked from the game\n");
SV_DropClient(sv_client);
sv_client->lastmessage = svs.realtime; }
void SV_Status_f(void) {
int i, j, l;
client_t *cl;
char *s;
int ping;
if(!svs.clients) {
Com_Printf("No server running.\n");
return;
}
Com_Printf("map : %s\n", sv.name);
Com_Printf("num score ping name lastmsg address qport \n");
Com_Printf("--- ----- ---- --------------- ------- --------------------- ------\n");
for(i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) {
if(!cl->state)
continue;
Com_Printf("%3i ", i);
Com_Printf("%5i ", cl->edict->client->ps.stats[STAT_FRAGS]);
if(cl->state == cs_connected)
Com_Printf("CNCT ");
else if(cl->state == cs_zombie)
Com_Printf("ZMBI ");
else {
ping = cl->ping < 9999 ? cl->ping : 9999;
Com_Printf("%4i ", ping);
}
Com_Printf("%s", cl->name);
l = 16 - strlen(cl->name);
for(j = 0; j < l; j++)
Com_Printf(" ");
Com_Printf("%7i ", svs.realtime - cl->lastmessage);
s = NET_AdrToString(cl->netchan.remote_address);
Com_Printf("%s", s);
l = 22 - strlen(s);
for(j = 0; j < l; j++)
Com_Printf(" ");
Com_Printf("%5i", cl->netchan.qport);
Com_Printf("\n");
}
Com_Printf("\n");
}
void SV_ConSay_f(void) {
client_t *client;
int j;
char *p;
char text[1024];
if(Cmd_Argc() < 2)
return;
strcpy(text, "console: ");
p = Cmd_Args();
if(*p == '"') {
p++;
p[strlen(p) - 1] = 0;
}
strcat(text, p);
for(j = 0, client = svs.clients; j < maxclients->value; j++, client++) {
if(client->state != cs_spawned)
continue;
SV_ClientPrintf(client, PRINT_CHAT, "%s\n", text);
}
}
void SV_Heartbeat_f(void) { svs.last_heartbeat = -9999999; }
void SV_Serverinfo_f(void) {
Com_Printf("Server info settings:\n");
Info_Print(Cvar_Serverinfo());
}
void SV_DumpUser_f(void) {
if(Cmd_Argc() != 2) {
Com_Printf("Usage: info <userid>\n");
return;
}
if(!SV_SetPlayer())
return;
Com_Printf("userinfo\n");
Com_Printf("--------\n");
Info_Print(sv_client->userinfo);
}
void SV_ServerRecord_f(void) {
char name[MAX_OSPATH];
char buf_data[32768];
sizebuf_t buf;
int len;
int i;
if(Cmd_Argc() != 2) {
Com_Printf("serverrecord <demoname>\n");
return;
}
if(svs.demofile) {
Com_Printf("Already recording.\n");
return;
}
if(sv.state != ss_game) {
Com_Printf("You must be in a level to record.\n");
return;
}
Com_sprintf(name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1));
Com_Printf("recording to %s.\n", name);
FS_CreatePath(name);
svs.demofile = fopen(name, "wb");
if(!svs.demofile) {
Com_Printf("ERROR: couldn't open.\n");
return;
}
SZ_Init(&svs.demo_multicast, svs.demo_multicast_buf, sizeof(svs.demo_multicast_buf));
SZ_Init(&buf, (void *)buf_data, sizeof(buf_data));
MSG_WriteByte(&buf, svc_serverdata);
MSG_WriteLong(&buf, PROTOCOL_VERSION);
MSG_WriteLong(&buf, svs.spawncount);
MSG_WriteByte(&buf, 2); MSG_WriteString(&buf, Cvar_VariableString("gamedir"));
MSG_WriteShort(&buf, -1);
MSG_WriteString(&buf, sv.configstrings[CS_NAME]);
for(i = 0; i < MAX_CONFIGSTRINGS; i++)
if(sv.configstrings[i][0]) {
MSG_WriteByte(&buf, svc_configstring);
MSG_WriteShort(&buf, i);
MSG_WriteString(&buf, sv.configstrings[i]);
}
Com_DPrintf("signon message length: %i\n", buf.cursize);
len = LittleLong(buf.cursize);
fwrite(&len, 4, 1, svs.demofile);
fwrite(buf.data, buf.cursize, 1, svs.demofile);
}
void SV_ServerStop_f(void) {
if(!svs.demofile) {
Com_Printf("Not doing a serverrecord.\n");
return;
}
fclose(svs.demofile);
svs.demofile = NULL;
Com_Printf("Recording completed.\n");
}
void SV_KillServer_f(void) {
if(!svs.initialized)
return;
SV_Shutdown("Server was killed.\n", false);
NET_Config(false); }
void SV_ServerCommand_f(void) {
if(!ge) {
Com_Printf("No game loaded.\n");
return;
}
ge->ServerCommand();
}
static int get_user_version(void *ud_, int ncols, char **column_names, char **data) {
int *ud = (int *)ud_;
*ud = atoi(data[0]);
return 1;
}
#define SQL(...) #__VA_ARGS__
static const char *database_setup_scripts[] = {
SQL( CREATE TABLE character(name TEXT, account_uuid TEXT, configstring TEXT); ) };
void SV_ReloadDatabase_f(void) {
char temp[MAX_QPATH];
if(sv_database != NULL) {
sqlite3_close(sv_database);
}
sprintf(temp, sizeof(temp), "%s/server.db", FS_Gamedir());
int err;
if((err = sqlite3_open(temp, &sv_database))) {
Com_Printf("failed to open server sqlite3 database: %i", err);
return;
}
int user_version;
if(!SQLite_exec(sv_database, "PRAGMA user_version;", get_user_version, &user_version)) {
sqlite3_close(sv_database);
sv_database = NULL;
return;
}
Com_Printf("sv_database.user_version = %i\n", user_version);
for(int i = user_version; i < sizeof(database_setup_scripts) / sizeof(database_setup_scripts[0]); i++) {
if(!SQLite_exec(sv_database, database_setup_scripts[i], NULL, NULL)) {
sqlite3_close(sv_database);
sv_database = NULL;
return;
}
}
sprintf(temp, sizeof(temp), "PRAGMA user_version = %i;", user_version);
if(!SQLite_exec(sv_database, temp, NULL, NULL)) {
sqlite3_close(sv_database);
sv_database = NULL;
return;
}
Com_Printf("sv_database loaded\n");
}
void SV_InitOperatorCommands(void) {
Cmd_AddCommand("heartbeat", SV_Heartbeat_f);
Cmd_AddCommand("kick", SV_Kick_f);
Cmd_AddCommand("status", SV_Status_f);
Cmd_AddCommand("serverinfo", SV_Serverinfo_f);
Cmd_AddCommand("dumpuser", SV_DumpUser_f);
Cmd_AddCommand("map", SV_Map_f);
Cmd_AddCommand("demomap", SV_DemoMap_f);
Cmd_AddCommand("gamemap", SV_GameMap_f);
Cmd_AddCommand("setmaster", SV_SetMaster_f);
if(dedicated->value)
Cmd_AddCommand("say", SV_ConSay_f);
Cmd_AddCommand("serverrecord", SV_ServerRecord_f);
Cmd_AddCommand("serverstop", SV_ServerStop_f);
Cmd_AddCommand("save", SV_Savegame_f);
Cmd_AddCommand("load", SV_Loadgame_f);
Cmd_AddCommand("killserver", SV_KillServer_f);
Cmd_AddCommand("sv", SV_ServerCommand_f);
Cmd_AddCommand("sv_reload_database", SV_ReloadDatabase_f);
}