#include "qwsvdef.h" #ifndef CLIENTONLY //#ifdef _DEBUG #define NEWSAVEFORMAT //#endif extern cvar_t skill; extern cvar_t deathmatch; extern cvar_t coop; extern cvar_t teamplay; //Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current void SV_SavegameComment (char *text) { int i; char kills[20]; char *mapname = sv.mapname; for (i=0 ; i SAVEGAME_COMMENT_LENGTH) i = SAVEGAME_COMMENT_LENGTH; memcpy (text, mapname, i); } #ifdef Q2SERVER if (ge) //q2 { kills[0] = '\0'; } else #endif #ifdef HLSERVER if (svs.gametype == GT_HALFLIFE) { sprintf (kills,"kills:moo"); } else #endif sprintf (kills,"kills:%3i/%3i", (int)pr_global_struct->killed_monsters, (int)pr_global_struct->total_monsters); memcpy (text+22, kills, strlen(kills)); // convert space to _ to make stdio happy for (i=0 ; istate < cs_spawned) {//try to go for nq/zq compatability as this is a single player game. s = PR_SaveEnts(svprogfuncs, NULL, &len, 2); //get the entity state now, so that we know if we can get the full state in a q1 format. if (s) { if (progstype == PROG_QW) version = 6; else version = 5; } } fprintf (f, "%i\n", version); SV_SavegameComment (comment); fprintf (f, "%s\n", comment); if (version != SAVEGAME_VERSION) { for (i=0; ispawn_parms[i]); //client 1. fprintf (f, "%f\n", skill.value); } else { fprintf(f, "%i\n", sv.allocated_client_slots); for (cl = svs.clients, clnum=0; clnum < sv.allocated_client_slots; cl++,clnum++) { if (cl->state < cs_spawned && !cl->istobeloaded) //don't save if they are still connecting { fprintf(f, "\"\"\n"); continue; } fprintf(f, "\"%s\"\n", cl->name); for (i=0; ispawn_parms[i]); } fprintf (f, "%i\n", progstype); fprintf (f, "%f\n", skill.value); fprintf (f, "%f\n", deathmatch.value); fprintf (f, "%f\n", coop.value); fprintf (f, "%f\n", teamplay.value); } fprintf (f, "%s\n", sv.name); fprintf (f, "%f\n",sv.time); // write the light styles for (i=0 ; iparms->memfree(s); fclose (f); Con_TPrintf (STL_SAVEDONE); SV_BroadcastTPrintf(2, STL_GAMESAVED); } //FIXME: Multiplayer save probably won't work with spectators. void SV_Loadgame_f(void) { char filename[MAX_OSPATH]; FILE *f; char mapname[MAX_QPATH]; float time, tfloat; char str[32768]; int i; edict_t *ent; int version; int pt; int slots; int current_skill; client_t *cl; int clnum; char plname[32]; int filelen, filepos; char *file; if (Cmd_Argc() != 2) { Con_TPrintf (STL_LOADSYNTAX); return; } // if (sv.state != ss_active) // { // Con_Printf("Can't apply: Server isn't running or is still loading\n"); // return; // } sprintf (filename, "%s/saves/%s", com_gamedir, Cmd_Argv(1)); COM_DefaultExtension (filename, ".sav"); // we can't call SCR_BeginLoadingPlaque, because too much stack space has // been used. The menu calls it before stuffing loadgame command // SCR_BeginLoadingPlaque (); Con_TPrintf (STL_LOADGAMEFROM, filename); f = fopen (filename, "rb"); if (!f) { Con_TPrintf (STL_ERRORCOULDNTOPEN); return; } fscanf (f, "%i\n", &version); if (version != SAVEGAME_VERSION && version != 5 && version != 6) //5 for NQ, 6 for ZQ/FQ { fclose (f); Con_TPrintf (STL_BADSAVEVERSION, version, SAVEGAME_VERSION); return; } fscanf (f, "%s\n", str); if (version == 5) { Con_Printf("loading single player game\n"); } else if (version == 6) //this is fuhquake's single player games { Con_Printf("loading single player qw game\n"); } else Con_Printf("loading FTE saved game\n"); for (clnum = 0; clnum < MAX_CLIENTS; clnum++) //clear the server for the level change. { cl = &svs.clients[clnum]; if (cl->state <= cs_zombie) continue; MSG_WriteByte (&cl->netchan.message, svc_stufftext); MSG_WriteString (&cl->netchan.message, "disconnect;wait;reconnect\n"); //kindly ask the client to come again. cl->drop = true; } SV_SendMessagesToAll(); if (version == 5 || version == 6) { slots = 1; for (clnum = 1; clnum < MAX_CLIENTS; clnum++) //kick all players fully. Load only player 1. { cl = &svs.clients[clnum]; cl->istobeloaded = false; cl->state = cs_free; } cl = &svs.clients[0]; #ifdef SERVERONLY strcpy(cl->name, ""); #else strcpy(cl->name, name.string); #endif cl->state = cs_zombie; cl->connection_started = realtime+20; cl->istobeloaded = true; for (i=0 ; ispawn_parms[i]); } else //fte QuakeWorld saves ALL the clients on the server. { fscanf (f, "%f\n", &tfloat); slots = tfloat; if (!slots) //err { fclose (f); Con_Printf ("Corrupted save game"); return; } for (clnum = 0; clnum < sv.allocated_client_slots; clnum++) //work out which players we had when we saved, and hope they accepted the reconnect. { cl = &svs.clients[clnum]; fscanf(f, "%s\n", plname); cl->istobeloaded = false; cl->state = cs_free; COM_Parse(plname); if (!*com_token) continue; strcpy(cl->name, com_token); cl->state = cs_zombie; cl->connection_started = realtime+20; cl->istobeloaded = true; for (i=0 ; ispawn_parms[i]); } for (clnum = sv.allocated_client_slots; clnum < MAX_CLIENTS; clnum++) { //cleanup. cl = &svs.clients[clnum]; cl->istobeloaded = false; cl->state = cs_free; } } if (version == 5 || version == 6) { fscanf (f, "%f\n", &tfloat); current_skill = (int)(tfloat + 0.1); Cvar_Set ("skill", va("%i", current_skill)); Cvar_SetValue ("deathmatch", 0); Cvar_SetValue ("coop", 0); Cvar_SetValue ("teamplay", 0); if (version == 5) { progstype = PROG_NQ; Cvar_Set ("progs", "progs.dat"); //NQ's progs. } else { progstype = PROG_QW; Cvar_Set ("progs", "spprogs.dat"); //zquake's single player qw progs. } pt = 0; } else { fscanf (f, "%f\n", &tfloat); pt = tfloat; // this silliness is so we can load 1.06 save files, which have float skill values fscanf (f, "%f\n", &tfloat); current_skill = (int)(tfloat + 0.1); Cvar_Set ("skill", va("%i", current_skill)); fscanf (f, "%f\n", &tfloat); Cvar_SetValue ("deathmatch", tfloat); fscanf (f, "%f\n", &tfloat); Cvar_SetValue ("coop", tfloat); fscanf (f, "%f\n", &tfloat); Cvar_SetValue ("teamplay", tfloat); } fscanf (f, "%s\n",mapname); fscanf (f, "%f\n",&time); SV_SpawnServer (mapname, NULL, false, false); //always inits MAX_CLIENTS slots. That's okay, because we can cut the max easily. if (sv.state != ss_active) { fclose (f); Con_TPrintf (STL_LOADFAILED); return; } sv.allocated_client_slots = slots; // load the light styles for (i=0 ; iload_ents(svprogfuncs, file, 0); BZ_Free(file); PR_LoadGlabalStruct(); sv.time = time; pr_global_struct->time = sv.physicstime; fclose (f); SV_ClearWorld (); for (i=0 ; iisfree) continue; SV_LinkEdict (ent, false); } for (i=0 ; inext; Z_Free(svs.levcache); svs.levcache = cache; } } void LoadModelsAndSounds(vfsfile_t *f) { char str[32768]; int i; sv.strings.model_precache[0] = PR_AddString(svprogfuncs, "", 0); for (i=1; i < MAX_MODELS; i++) { VFS_GETS(f, str, sizeof(str)); if (!*str) break; sv.strings.model_precache[i] = PR_AddString(svprogfuncs, str, 0); } if (i == MAX_MODELS) { VFS_GETS(f, str, sizeof(str)); if (*str) SV_Error("Too many model precaches in loadgame cache"); } for (; i < MAX_MODELS; i++) sv.strings.model_precache[i] = NULL; // sv.sound_precache[0] = PR_AddString(svprogfuncs, "", 0); for (i=1; i < MAX_SOUNDS; i++) { VFS_GETS(f, str, sizeof(str)); if (!*str) break; // sv.sound_precache[i] = PR_AddString(svprogfuncs, str, 0); } if (i == MAX_SOUNDS) { VFS_GETS(f, str, sizeof(str)); if (*str) SV_Error("Too many sound precaches in loadgame cache"); } for (; i < MAX_SOUNDS; i++) *sv.strings.sound_precache[i] = 0; } qboolean SV_LoadLevelCache(char *level, char *startspot, qboolean ignoreplayers) { eval_t *eval, *e2; char name[MAX_OSPATH]; vfsfile_t *f; char mapname[MAX_QPATH]; float time; char str[32768]; int i,j; edict_t *ent; int version; int current_skill; int clnum; int pt; int modelpos; int filelen, filepos; char *file; gametype_e gametype; levelcache_t *cache; cache = svs.levcache; while(cache) { if (!strcmp(cache->mapname, level)) break; cache = cache->next; } if (!cache) return false; //not visited yet. Ignore the existing caches as fakes. gametype = cache->gametype; sprintf (name, "saves/%s", level); COM_DefaultExtension (name, ".lvc", sizeof(name)); // Con_TPrintf (STL_LOADGAMEFROM, name); #ifdef Q2SERVER if (gametype == GT_QUAKE2) { char syspath[MAX_OSPATH]; SV_SpawnServer (level, startspot, false, false); SV_ClearWorld(); if (!ge) { Con_Printf("Incorrect gamecode type.\n"); return false; } if (!FS_NativePath(name, FS_GAMEONLY, syspath, sizeof(syspath))) return false; ge->ReadLevel(syspath); for (i=0 ; i<100 ; i++) //run for 10 secs to iron out a few bugs. ge->RunFrame (); return true; } #endif // we can't call SCR_BeginLoadingPlaque, because too much stack space has // been used. The menu calls it before stuffing loadgame command // SCR_BeginLoadingPlaque (); f = FS_OpenVFS(name, "rb", FS_GAME); if (!f) { Con_TPrintf (STL_ERRORCOULDNTOPEN); return false; } VFS_GETS(f, str, sizeof(str)); version = atoi(str); if (version != CACHEGAME_VERSION) { VFS_CLOSE (f); Con_TPrintf (STL_BADSAVEVERSION, version, CACHEGAME_VERSION); return false; } VFS_GETS(f, str, sizeof(str)); //comment SV_SendMessagesToAll(); VFS_GETS(f, str, sizeof(str)); pt = atof(str); // this silliness is so we can load 1.06 save files, which have float skill values VFS_GETS(f, str, sizeof(str)); current_skill = (int)(atof(str) + 0.1); Cvar_Set (&skill, va("%i", current_skill)); VFS_GETS(f, str, sizeof(str)); Cvar_SetValue (&deathmatch, atof(str)); VFS_GETS(f, str, sizeof(str)); Cvar_SetValue (&coop, atof(str)); VFS_GETS(f, str, sizeof(str)); Cvar_SetValue (&teamplay, atof(str)); VFS_GETS(f, mapname, sizeof(mapname)); VFS_GETS(f, str, sizeof(str)); time = atof(str); SV_SpawnServer (mapname, startspot, false, false); sv.time = time; if (svs.gametype != gametype) { Con_Printf("Incorrect gamecode type. Cannot load game.\n"); return false; } if (sv.state != ss_active) { VFS_CLOSE (f); Con_TPrintf (STL_LOADFAILED); return false; } // sv.paused = true; // pause until all clients connect // sv.loadgame = true; // load the light styles VFS_GETS(f, str, sizeof(str)); if (atoi(str) != MAX_LIGHTSTYLES) { VFS_CLOSE (f); Con_Printf ("load failed - invalid number of lightstyles\n"); return false; } for (i=0 ; iload_ents(svprogfuncs, file, 0); BZ_Free(file); progstype = pt; PR_LoadGlabalStruct(); pr_global_struct->time = sv.time = sv.physicstime = time; sv.starttime = Sys_DoubleTime() - sv.time; VFS_SEEK(f, modelpos); LoadModelsAndSounds(f); VFS_CLOSE(f); PF_InitTempStrings(svprogfuncs); SV_ClearWorld (); for (i=0 ; ixv->playerclass; } if (!ignoreplayers) { eval = PR_FindGlobal(svprogfuncs, "startspot", 0); if (eval) eval->_int = (int)PR_NewString(svprogfuncs, startspot, 0); eval = PR_FindGlobal(svprogfuncs, "ClientReEnter", 0); if (eval) for (i=0 ; irestoreent(svprogfuncs, svs.clients[i].spawninfo, &j, ent); e2 = svprogfuncs->GetEdictFieldValue(svprogfuncs, ent, "stats_restored", NULL); if (e2) e2->_float = 1; for (j=0 ; j< NUM_SPAWN_PARMS ; j++) { if (spawnparamglobals[j]) *spawnparamglobals[j] = host_client->spawn_parms[j]; } pr_global_struct->time = sv.physicstime; pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, ent); ent->area.next = ent->area.prev = NULL; G_FLOAT(OFS_PARM0) = sv.time-host_client->spawninfotime; PR_ExecuteProgram(svprogfuncs, eval->function); } } } for (i=0 ; iisfree) continue; SV_LinkEdict (ent, false); // force retouch even for stationary } return true; //yay } void SV_SaveLevelCache(qboolean dontharmgame) { int len; char *s; client_t *cl; int clnum; char name[256]; vfsfile_t *f; int i; char comment[SAVEGAME_COMMENT_LENGTH+1]; levelcache_t *cache; if (!sv.state) return; cache = svs.levcache; while(cache) { if (!strcmp(cache->mapname, sv.name)) break; cache = cache->next; } if (!cache) //not visited yet. Let us know that we went there. { cache = Z_Malloc(sizeof(levelcache_t)+strlen(sv.name)+1); cache->mapname = (char *)(cache+1); strcpy(cache->mapname, sv.name); cache->gametype = svs.gametype; cache->next = svs.levcache; svs.levcache = cache; } sprintf (name, "saves/%s", cache->mapname); COM_DefaultExtension (name, ".lvc", sizeof(name)); FS_CreatePath(name, FS_GAMEONLY); if (!dontharmgame) //save game in progress Con_TPrintf (STL_SAVEGAMETO, name); #ifdef Q2SERVER if (ge) { char syspath[256]; if (!FS_NativePath(name, FS_GAMEONLY, syspath, sizeof(syspath))) return; ge->WriteLevel(syspath); return; } #endif #ifdef HLSERVER if (svs.gametype == GT_HALFLIFE) { SVHL_SaveLevelCache(name); return; } #endif f = FS_OpenVFS (name, "wb", FS_GAME); if (!f) { Con_TPrintf (STL_ERRORCOULDNTOPEN); return; } VFS_PRINTF (f, "%i\n", CACHEGAME_VERSION); SV_SavegameComment (comment); VFS_PRINTF (f, "%s\n", comment); for (cl = svs.clients, clnum=0; clnum < MAX_CLIENTS; cl++,clnum++)//fake dropping { if ((cl->state < cs_spawned && !cl->istobeloaded) || dontharmgame) //don't drop if they are still connecting { continue; } else if (!cl->spectator) { // call the prog function for removing a client // this will set the body to a dead frame, among other things pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict); PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect); } else if (SpectatorDisconnect) { // call the prog function for removing a client // this will set the body to a dead frame, among other things pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict); PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect); } } VFS_PRINTF (f, "%d\n", progstype); VFS_PRINTF (f, "%f\n", skill.value); VFS_PRINTF (f, "%f\n", deathmatch.value); VFS_PRINTF (f, "%f\n", coop.value); VFS_PRINTF (f, "%f\n", teamplay.value); VFS_PRINTF (f, "%s\n", sv.name); VFS_PRINTF (f, "%f\n",sv.time); // write the light styles VFS_PRINTF (f, "%i\n",MAX_LIGHTSTYLES); for (i=0 ; iparms->memfree(s); VFS_CLOSE (f); } #ifdef NEWSAVEFORMAT #define FTESAVEGAME_VERSION 25000 void SV_Savegame_f (void) { extern cvar_t nomonsters; extern cvar_t gamecfg; extern cvar_t scratch1; extern cvar_t scratch2; extern cvar_t scratch3; extern cvar_t scratch4; extern cvar_t savedgamecfg; extern cvar_t saved1; extern cvar_t saved2; extern cvar_t saved3; extern cvar_t saved4; extern cvar_t temp1; extern cvar_t noexit; extern cvar_t pr_maxedicts; extern cvar_t progs; client_t *cl; int clnum; char comment[SAVEGAME_COMMENT_LENGTH+1]; vfsfile_t *f; int len; char *savename; levelcache_t *cache; char str[MAX_LOCALINFO_STRING+1]; if (!sv.state) { Con_Printf("Server is not active - unable to save\n"); return; } savename = Cmd_Argv(1); if (!*savename || strstr(savename, "..")) savename = "quicksav"; f = FS_OpenVFS(va("saves/%s/info.fsv", savename), "wt", FS_GAMEONLY); if (!f) { Con_Printf("Couldn't open file saves/%s/info.fsv\n", savename); return; } SV_SavegameComment(comment); VFS_PRINTF (f, "%d\n", FTESAVEGAME_VERSION+svs.gametype); VFS_PRINTF (f, "%s\n", comment); VFS_PRINTF(f, "%i\n", sv.allocated_client_slots); for (cl = svs.clients, clnum=0; clnum < sv.allocated_client_slots; cl++,clnum++) { if (cl->state < cs_spawned && !cl->istobeloaded) //don't save if they are still connecting { VFS_PRINTF(f, "\n"); continue; } VFS_PRINTF(f, "%s\n", cl->name); if (*cl->name) for (len = 0; len < NUM_SPAWN_PARMS; len++) VFS_PRINTF(f, "%i (%f)\n", *(int*)&cl->spawn_parms[len], cl->spawn_parms[len]); //write ints as not everyone passes a float in the parms. //write floats too so you can use it to debug. } Q_strncpyz(str, svs.info, sizeof(str)); Info_RemovePrefixedKeys(str, '*'); VFS_PRINTF (f, "%s\"\n", str); Q_strncpyz(str, localinfo, sizeof(str)); Info_RemovePrefixedKeys(str, '*'); VFS_PRINTF (f, "%s\n", str); VFS_PRINTF (f, "{\n"); //all game vars. FIXME: Should save the ones that have been retrieved/set by progs. VFS_PRINTF (f, "skill \"%s\"\n", skill.string); VFS_PRINTF (f, "deathmatch \"%s\"\n", deathmatch.string); VFS_PRINTF (f, "coop \"%s\"\n", coop.string); VFS_PRINTF (f, "teamplay \"%s\"\n", teamplay.string); VFS_PRINTF (f, "nomonsters \"%s\"\n", nomonsters.string); VFS_PRINTF (f, "gamecfg\t \"%s\"\n", gamecfg.string); VFS_PRINTF (f, "scratch1 \"%s\"\n", scratch1.string); VFS_PRINTF (f, "scratch2 \"%s\"\n", scratch2.string); VFS_PRINTF (f, "scratch3 \"%s\"\n", scratch3.string); VFS_PRINTF (f, "scratch4 \"%s\"\n", scratch4.string); VFS_PRINTF (f, "savedgamecfg\t \"%s\"\n", savedgamecfg.string); VFS_PRINTF (f, "saved1 \"%s\"\n", saved1.string); VFS_PRINTF (f, "saved2 \"%s\"\n", saved2.string); VFS_PRINTF (f, "saved3 \"%s\"\n", saved3.string); VFS_PRINTF (f, "saved4 \"%s\"\n", saved4.string); VFS_PRINTF (f, "temp1 \"%s\"\n", temp1.string); VFS_PRINTF (f, "noexit \"%s\"\n", noexit.string); VFS_PRINTF (f, "pr_maxedicts\t \"%s\"\n", pr_maxedicts.string); VFS_PRINTF (f, "progs \"%s\"\n", progs.string); VFS_PRINTF (f, "set nextserver \"%s\"\n", Cvar_Get("nextserver", "", 0, "")->string); VFS_PRINTF (f, "}\n"); SV_SaveLevelCache(true); //add the current level. Note that this can cause reentry problems. cache = svs.levcache; //state from previous levels - just copy it all accross. VFS_PRINTF(f, "{\n"); while(cache) { VFS_PRINTF(f, "%s\n", cache->mapname); FS_Copy(va("saves/%s.lvc", cache->mapname), va("saves/%s/%s.lvc", savename, cache->mapname), FS_GAME, FS_GAME); cache = cache->next; } VFS_PRINTF(f, "}\n"); VFS_PRINTF (f, "%s\n", sv.name); VFS_CLOSE(f); } void SV_Loadgame_f (void) { levelcache_t *cache; char str[MAX_LOCALINFO_STRING+1], *trim; char savename[MAX_QPATH]; vfsfile_t *f; char filename[MAX_OSPATH]; int version; int clnum; int slots; client_t *cl; gametype_e gametype; int len; Q_strncpyz(savename, Cmd_Argv(1), sizeof(savename)); if (!*savename || strstr(savename, "..")) strcpy(savename, "quicksav"); sprintf (filename, "saves/%s/info.fsv", savename); f = FS_OpenVFS (filename, "rt", FS_GAME); if (!f) { Con_TPrintf (STL_ERRORCOULDNTOPEN); return; } VFS_GETS(f, str, sizeof(str)-1); version = atoi(str); if (version < FTESAVEGAME_VERSION || version >= FTESAVEGAME_VERSION+GT_MAX) { VFS_CLOSE (f); Con_TPrintf (STL_BADSAVEVERSION, version, FTESAVEGAME_VERSION); return; } gametype = version - FTESAVEGAME_VERSION; VFS_GETS(f, str, sizeof(str)-1); #ifndef SERVERONLY if (!cls.state) #endif Con_TPrintf (STL_LOADGAMEFROM, filename); for (clnum = 0; clnum < MAX_CLIENTS; clnum++) //clear the server for the level change. { cl = &svs.clients[clnum]; if (cl->state <= cs_zombie) continue; if (cl->protocol == SCP_QUAKE2) MSG_WriteByte (&cl->netchan.message, svcq2_stufftext); else MSG_WriteByte (&cl->netchan.message, svc_stufftext); MSG_WriteString (&cl->netchan.message, "echo Loading Game;disconnect;wait;wait;reconnect\n"); //kindly ask the client to come again. cl->istobeloaded = false; } SV_SendMessagesToAll(); VFS_GETS(f, str, sizeof(str)-1); slots = atoi(str); for (cl = svs.clients, clnum=0; clnum < slots; cl++,clnum++) { if (cl->state > cs_zombie) SV_DropClient(cl); VFS_GETS(f, str, sizeof(str)-1); str[sizeof(cl->namebuf)-1] = '\0'; for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; strcpy(cl->namebuf, str); cl->name = cl->namebuf; if (*str) { cl->state = cs_zombie; cl->connection_started = realtime+20; cl->istobeloaded = true; memset(&cl->netchan, 0, sizeof(cl->netchan)); for (len = 0; len < NUM_SPAWN_PARMS; len++) { VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) if (*str == '(') cl->spawn_parms[len] = atof(str); else { version = atoi(str); cl->spawn_parms[len] = *(float *)&version; } } } } VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; Info_RemovePrefixedKeys(str, '*'); //just in case Info_RemoveNonStarKeys(svs.info); len = strlen(svs.info); Q_strncpyz(svs.info+len, str, sizeof(svs.info)-len); VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; Info_RemovePrefixedKeys(str, '*'); //just in case Info_RemoveNonStarKeys(localinfo); len = strlen(localinfo); Q_strncpyz(localinfo+len, str, sizeof(localinfo)-len); VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; if (strcmp(str, "{")) SV_Error("Corrupt saved game\n"); while(1) { if (!VFS_GETS(f, str, sizeof(str)-1)) SV_Error("Corrupt saved game\n"); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; if (!strcmp(str, "}")) break; else if (*str) Cmd_ExecuteString(str, RESTRICT_RCON); } SV_FlushLevelCache(); VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; if (strcmp(str, "{")) SV_Error("Corrupt saved game\n"); while(1) { if (!VFS_GETS(f, str, sizeof(str)-1)) SV_Error("Corrupt saved game\n"); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; if (!strcmp(str, "}")) break; if (!*str) continue; cache = Z_Malloc(sizeof(levelcache_t)+strlen(str)+1); cache->mapname = (char *)(cache+1); strcpy(cache->mapname, str); cache->gametype = gametype; cache->next = svs.levcache; FS_Copy(va("saves/%s/%s.lvc", savename, cache->mapname), va("saves/%s.lvc", cache->mapname), FS_GAME, FS_GAMEONLY); svs.levcache = cache; } VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; VFS_CLOSE(f); SV_LoadLevelCache(str, "", true); sv.allocated_client_slots = slots; } #endif #endif