/* * Copyright (c) ZeniMax Media Inc. * Licensed under the GNU General Public License 2.0. */ /* * ======================================================================= * * The savegame system. * * ======================================================================= */ /* * This is the Quake 2 savegame system, fixed by Yamagi * based on an idea by Knightmare of kmquake2. This major * rewrite of the original g_save.c is much more robust * and portable since it doesn't use any function pointers. * * Inner workings: * When the game is saved all function pointers are * translated into human readable function definition strings. * The same way all mmove_t pointers are translated. This * human readable strings are then written into the file. * At game load the human readable strings are retranslated * into the actual function pointers and struct pointers. The * pointers are generated at each compilation / start of the * client, thus the pointers are always correct. * * Limitations: * While savegames survive recompilations of the game source * and bigger changes in the source, there are some limitation * which a nearly impossible to fix without a object orientated * rewrite of the game. * - If functions or mmove_t structs that a referencenced * inside savegames are added or removed (e.g. the files * in tables/ are altered) the load functions cannot * reconnect all pointers and thus not restore the game. * - If the operating system is changed internal structures * may change in an unrepairable way. * - If the architecture is changed pointer length and * other internal datastructures change in an * incompatible way. * - If the edict_t struct is changed, savegames * will break. * This is not so bad as it looks since functions and * struct won't be added and edict_t won't be changed * if no big, sweeping changes are done. The operating * system and architecture are in the hands of the user. */ #include "../header/local.h" /* * When ever the savegame version is changed, q2 will refuse to * load older savegames. This should be bumped if the files * in tables/ are changed, otherwise strange things may happen. */ #define SAVEGAMEVER "YQ2-4" /* * This macros are used to prohibit loading of savegames * created on other systems or architectures. This will * crash q2 in spectacular ways */ #ifndef YQ2OSTYPE #error YQ2OSTYPE should be defined by the build system #endif #ifndef YQ2ARCH #error YQ2ARCH should be defined by the build system #endif /* * Older operating system and architecture detection * macros, implemented by savegame version YQ2-1. */ #if defined(__APPLE__) #define YQ2OSTYPE_1 "MacOS X" #elif defined(__FreeBSD__) #define YQ2OSTYPE_1 "FreeBSD" #elif defined(__OpenBSD__) #define YQ2OSTYPE_1 "OpenBSD" #elif defined(__linux__) #define YQ2OSTYPE_1 "Linux" #elif defined(_WIN32) #define YQ2OSTYPE_1 "Windows" #else #define YQ2OSTYPE_1 "Unknown" #endif #if defined(__i386__) #define YQ2ARCH_1 "i386" #elif defined(__x86_64__) #define YQ2ARCH_1 "amd64" #elif defined(__sparc__) #define YQ2ARCH_1 "sparc64" #elif defined(__ia64__) #define YQ2ARCH_1 "ia64" #else #define YQ2ARCH_1 "unknown" #endif /* * Connects a human readable * function signature with * the corresponding pointer */ typedef struct { char *funcStr; byte *funcPtr; } functionList_t; /* * Connects a human readable * mmove_t string with the * correspondig pointer * */ typedef struct { char *mmoveStr; mmove_t *mmovePtr; } mmoveList_t; typedef struct { char ver[32]; char game[32]; char os[32]; char arch[32]; } savegameHeader_t; /* ========================================================= */ /* * Prototypes for forward * declaration for all game * functions. */ #include "tables/gamefunc_decs.h" /* * List with function pointer * to each of the functions * prototyped above. */ functionList_t functionList[] = { #include "tables/gamefunc_list.h" }; /* * Prtotypes for forward * declaration for all game * mmove_t functions. */ #include "tables/gamemmove_decs.h" /* * List with pointers to * each of the mmove_t * functions prototyped * above. */ mmoveList_t mmoveList[] = { #include "tables/gamemmove_list.h" }; /* * Fields to be saved */ field_t fields[] = { #include "tables/fields.h" }; /* * Level fields to * be saved */ field_t levelfields[] = { #include "tables/levelfields.h" }; /* * Client fields to * be saved */ field_t clientfields[] = { #include "tables/clientfields.h" }; /* ========================================================= */ /* * This will be called when the dll is first loaded, * which only happens when a new game is started or * a save game is loaded. */ void InitGame(void) { gi.dprintf("Game is starting up.\n"); gi.dprintf("Game is %s built on %s.\n", GAMEVERSION, __DATE__); gun_x = gi.cvar ("gun_x", "0", 0); gun_y = gi.cvar ("gun_y", "0", 0); gun_z = gi.cvar ("gun_z", "0", 0); sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); sv_gravity = gi.cvar ("sv_gravity", "800", 0); /* noset vars */ dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); /* latched vars */ sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO); deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); coop = gi.cvar ("coop", "0", CVAR_LATCH); coop_elevator_delay = gi.cvar("coop_elevator_delay", "1.0", CVAR_ARCHIVE); coop_pickup_weapons = gi.cvar("coop_pickup_weapons", "0", CVAR_ARCHIVE); skill = gi.cvar ("skill", "1", CVAR_LATCH); maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); g_footsteps = gi.cvar ("g_footsteps", "1", CVAR_ARCHIVE); g_fix_triggered = gi.cvar ("g_fix_triggered", "0", 0); /* change anytime vars */ dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); password = gi.cvar ("password", "", CVAR_USERINFO); spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO); needpass = gi.cvar ("needpass", "0", CVAR_SERVERINFO); filterban = gi.cvar ("filterban", "1", 0); g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); run_pitch = gi.cvar ("run_pitch", "0.002", 0); run_roll = gi.cvar ("run_roll", "0.005", 0); bob_up = gi.cvar ("bob_up", "0.005", 0); bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); bob_roll = gi.cvar ("bob_roll", "0.002", 0); /* flood control */ flood_msgs = gi.cvar ("flood_msgs", "4", 0); flood_persecond = gi.cvar ("flood_persecond", "4", 0); flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); /* dm map list */ sv_maplist = gi.cvar ("sv_maplist", "", 0); /* others */ aimfix = gi.cvar("aimfix", "0", CVAR_ARCHIVE); g_machinegun_norecoil = gi.cvar("g_machinegun_norecoil", "0", CVAR_ARCHIVE); /* items */ InitItems (); game.helpmessage1[0] = 0; game.helpmessage2[0] = 0; /* initialize all entities for this game */ game.maxentities = maxentities->value; g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); globals.edicts = g_edicts; globals.max_edicts = game.maxentities; /* initialize all clients for this game */ game.maxclients = maxclients->value; game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); globals.num_edicts = game.maxclients+1; } /* ========================================================= */ /* * Helper function to get * the human readable function * definition by an address. * Called by WriteField1 and * WriteField2. */ functionList_t * GetFunctionByAddress(byte *adr) { int i; for (i = 0; functionList[i].funcStr; i++) { if (functionList[i].funcPtr == adr) { return &functionList[i]; } } return NULL; } /* * Helper function to get the * pointer to a function by * it's human readable name. * Called by WriteField1 and * WriteField2. */ byte * FindFunctionByName(char *name) { int i; for (i = 0; functionList[i].funcStr; i++) { if (!strcmp(name, functionList[i].funcStr)) { return functionList[i].funcPtr; } } return NULL; } /* * Helper function to get the * human readable definition of * a mmove_t struct by a pointer. */ mmoveList_t * GetMmoveByAddress(mmove_t *adr) { int i; for (i = 0; mmoveList[i].mmoveStr; i++) { if (mmoveList[i].mmovePtr == adr) { return &mmoveList[i]; } } return NULL; } /* * Helper function to get the * pointer to a mmove_t struct * by a human readable definition. */ mmove_t * FindMmoveByName(char *name) { int i; for (i = 0; mmoveList[i].mmoveStr; i++) { if (!strcmp(name, mmoveList[i].mmoveStr)) { return mmoveList[i].mmovePtr; } } return NULL; } /* ========================================================= */ /* * The following two functions are * doing the dirty work to write the * data generated by the functions * below this block into files. */ void WriteField1(FILE *f, field_t *field, byte *base) { void *p; int len; int index; functionList_t *func; mmoveList_t *mmove; if (field->flags & FFL_SPAWNTEMP) { return; } p = (void *)(base + field->ofs); switch (field->type) { case F_INT: case F_FLOAT: case F_ANGLEHACK: case F_VECTOR: case F_IGNORE: break; case F_LSTRING: case F_GSTRING: if (*(char **)p) { len = strlen(*(char **)p) + 1; } else { len = 0; } *(int *)p = len; break; case F_EDICT: if (*(edict_t **)p == NULL) { index = -1; } else { index = *(edict_t **)p - g_edicts; } *(int *)p = index; break; case F_CLIENT: if (*(gclient_t **)p == NULL) { index = -1; } else { index = *(gclient_t **)p - game.clients; } *(int *)p = index; break; case F_ITEM: if (*(edict_t **)p == NULL) { index = -1; } else { index = *(gitem_t **)p - itemlist; } *(int *)p = index; break; case F_FUNCTION: if (*(byte **)p == NULL) { len = 0; } else { func = GetFunctionByAddress (*(byte **)p); if (!func) { gi.error ("WriteField1: function not in list, can't save game"); } len = strlen(func->funcStr)+1; } *(int *)p = len; break; case F_MMOVE: if (*(byte **)p == NULL) { len = 0; } else { mmove = GetMmoveByAddress (*(mmove_t **)p); if (!mmove) { gi.error ("WriteField1: mmove not in list, can't save game"); } len = strlen(mmove->mmoveStr)+1; } *(int *)p = len; break; default: gi.error("WriteEdict: unknown field type"); } } void WriteField2(FILE *f, field_t *field, byte *base) { int len; void *p; functionList_t *func; mmoveList_t *mmove; if (field->flags & FFL_SPAWNTEMP) { return; } p = (void *)(base + field->ofs); switch (field->type) { case F_LSTRING: if (*(char **)p) { len = strlen(*(char **)p) + 1; fwrite(*(char **)p, len, 1, f); } break; case F_FUNCTION: if (*(byte **)p) { func = GetFunctionByAddress (*(byte **)p); if (!func) { gi.error ("WriteField2: function not in list, can't save game"); } len = strlen(func->funcStr)+1; fwrite (func->funcStr, len, 1, f); } break; case F_MMOVE: if (*(byte **)p) { mmove = GetMmoveByAddress (*(mmove_t **)p); if (!mmove) { gi.error ("WriteField2: mmove not in list, can't save game"); } len = strlen(mmove->mmoveStr)+1; fwrite (mmove->mmoveStr, len, 1, f); } break; default: break; } } /* ========================================================= */ /* * This function does the dirty * work to read the data from a * file. The processing of the * data is done in the functions * below */ void ReadField(FILE *f, field_t *field, byte *base) { void *p; int len; int index; char funcStr[2048]; if (field->flags & FFL_SPAWNTEMP) { return; } p = (void *)(base + field->ofs); switch (field->type) { case F_INT: case F_FLOAT: case F_ANGLEHACK: case F_VECTOR: case F_IGNORE: break; case F_LSTRING: len = *(int *)p; if (!len) { *(char **)p = NULL; } else { *(char **)p = gi.TagMalloc(32 + len, TAG_LEVEL); fread(*(char **)p, len, 1, f); } break; case F_EDICT: index = *(int *)p; if (index == -1) { *(edict_t **)p = NULL; } else { *(edict_t **)p = &g_edicts[index]; } break; case F_CLIENT: index = *(int *)p; if (index == -1) { *(gclient_t **)p = NULL; } else { *(gclient_t **)p = &game.clients[index]; } break; case F_ITEM: index = *(int *)p; if (index == -1) { *(gitem_t **)p = NULL; } else { *(gitem_t **)p = &itemlist[index]; } break; case F_FUNCTION: len = *(int *)p; if (!len) { *(byte **)p = NULL; } else { if (len > sizeof(funcStr)) { gi.error ("ReadField: function name is longer than buffer (%i chars)", (int)sizeof(funcStr)); } fread (funcStr, len, 1, f); if ( !(*(byte **)p = FindFunctionByName (funcStr)) ) { gi.error ("ReadField: function %s not found in table, can't load game", funcStr); } } break; case F_MMOVE: len = *(int *)p; if (!len) { *(byte **)p = NULL; } else { if (len > sizeof(funcStr)) { gi.error ("ReadField: mmove name is longer than buffer (%i chars)", (int)sizeof(funcStr)); } fread (funcStr, len, 1, f); if ( !(*(mmove_t **)p = FindMmoveByName (funcStr)) ) { gi.error ("ReadField: mmove %s not found in table, can't load game", funcStr); } } break; default: gi.error("ReadEdict: unknown field type"); } } /* ========================================================= */ /* * Write the client struct into a file. */ void WriteClient(FILE *f, gclient_t *client) { field_t *field; gclient_t temp; /* all of the ints, floats, and vectors stay as they are */ temp = *client; /* change the pointers to indexes */ for (field = clientfields; field->name; field++) { WriteField1(f, field, (byte *)&temp); } /* write the block */ fwrite(&temp, sizeof(temp), 1, f); /* now write any allocated data following the edict */ for (field = clientfields; field->name; field++) { WriteField2(f, field, (byte *)client); } } /* * Read the client struct from a file */ void ReadClient(FILE *f, gclient_t *client, short save_ver) { field_t *field; fread(client, sizeof(*client), 1, f); for (field = clientfields; field->name; field++) { if (field->save_ver <= save_ver) { ReadField(f, field, (byte *)client); } } if (save_ver < 3) { InitClientResp(client); } } /* ========================================================= */ /* * Writes the game struct into * a file. This is called when * ever the games goes to e new * level or the user saves the * game. Saved informations are: * - cross level data * - client states * - help computer info */ void WriteGame(const char *filename, qboolean autosave) { savegameHeader_t sv; FILE *f; int i; if (!autosave) { SaveClientData(); } f = fopen(filename, "wb"); if (!f) { gi.error("Couldn't open %s", filename); } /* Savegame identification */ memset(&sv, 0, sizeof(sv)); Q_strlcpy(sv.ver, SAVEGAMEVER, sizeof(sv.ver) - 1); Q_strlcpy(sv.game, GAMEVERSION, sizeof(sv.game) - 1); Q_strlcpy(sv.os, YQ2OSTYPE, sizeof(sv.os) - 1); Q_strlcpy(sv.arch, YQ2ARCH, sizeof(sv.arch) - 1); fwrite(&sv, sizeof(sv), 1, f); game.autosaved = autosave; fwrite(&game, sizeof(game), 1, f); game.autosaved = false; for (i = 0; i < game.maxclients; i++) { WriteClient(f, &game.clients[i]); } fclose(f); } /* * Read the game structs from * a file. Called when ever a * savegames is loaded. */ void ReadGame(const char *filename) { savegameHeader_t sv; FILE *f; int i; short save_ver = 0; gi.FreeTags(TAG_GAME); f = fopen(filename, "rb"); if (!f) { gi.error("Couldn't open %s", filename); } /* Sanity checks */ fread(&sv, sizeof(sv), 1, f); static const struct { const char* verstr; int vernum; } version_mappings[] = { {"YQ2-1", 1}, {"YQ2-2", 2}, {"YQ2-3", 3}, {"YQ2-4", 4}, }; for (i=0; i < sizeof(version_mappings)/sizeof(version_mappings[0]); ++i) { if (strcmp(version_mappings[i].verstr, sv.ver) == 0) { save_ver = version_mappings[i].vernum; break; } } if (save_ver == 0) // not found in mappings table { fclose(f); gi.error("Savegame from an incompatible version.\n"); } else if (save_ver == 1) { if (strcmp(sv.game, GAMEVERSION) != 0) { fclose(f); gi.error("Savegame from an other game.so.\n"); } else if (strcmp(sv.os, YQ2OSTYPE_1) != 0) { fclose(f); gi.error("Savegame from an other os.\n"); } #ifdef _WIN32 /* Windows was forced to i386 */ if (strcmp(sv.arch, "i386") != 0) { fclose(f); gi.error("Savegame from another architecture.\n"); } #else if (strcmp(sv.arch, YQ2ARCH_1) != 0) { fclose(f); gi.error("Savegame from another architecture.\n"); } #endif } else // all newer savegame versions { if (strcmp(sv.game, GAMEVERSION) != 0) { fclose(f); gi.error("Savegame from another game.so.\n"); } else if (strcmp(sv.os, YQ2OSTYPE) != 0) { fclose(f); gi.error("Savegame from another os.\n"); } else if (strcmp(sv.arch, YQ2ARCH) != 0) { #if defined(_WIN32) && (defined(__i386__) || defined(_M_IX86)) // before savegame version "YQ2-4" (and after version 1), // the official Win32 binaries accidentally had the YQ2ARCH "AMD64" // instead of "i386" set due to a bug in the Makefile. // This quirk allows loading those savegames anyway if (save_ver >= 4 || strcmp(sv.arch, "AMD64") != 0) #endif { fclose(f); gi.error("Savegame from another architecture.\n"); } } } g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME); globals.edicts = g_edicts; fread(&game, sizeof(game), 1, f); game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); for (i = 0; i < game.maxclients; i++) { ReadClient(f, &game.clients[i], save_ver); } fclose(f); } /* ========================================================== */ /* * Helper function to write the * edict into a file. Called by * WriteLevel. */ void WriteEdict(FILE *f, edict_t *ent) { field_t *field; edict_t temp; /* all of the ints, floats, and vectors stay as they are */ temp = *ent; /* change the pointers to lengths or indexes */ for (field = fields; field->name; field++) { WriteField1(f, field, (byte *)&temp); } /* write the block */ fwrite(&temp, sizeof(temp), 1, f); /* now write any allocated data following the edict */ for (field = fields; field->name; field++) { WriteField2(f, field, (byte *)ent); } } /* * Helper fcuntion to write the * level local data into a file. * Called by WriteLevel. */ void WriteLevelLocals(FILE *f) { field_t *field; level_locals_t temp; /* all of the ints, floats, and vectors stay as they are */ temp = level; /* change the pointers to lengths or indexes */ for (field = levelfields; field->name; field++) { WriteField1(f, field, (byte *)&temp); } /* write the block */ fwrite(&temp, sizeof(temp), 1, f); /* now write any allocated data following the edict */ for (field = levelfields; field->name; field++) { WriteField2(f, field, (byte *)&level); } } /* * Writes the current level * into a file. */ void WriteLevel(const char *filename) { int i; edict_t *ent; FILE *f; f = fopen(filename, "wb"); if (!f) { gi.error("Couldn't open %s", filename); } /* write out edict size for checking */ i = sizeof(edict_t); fwrite(&i, sizeof(i), 1, f); /* write out level_locals_t */ WriteLevelLocals(f); /* write out all the entities */ for (i = 0; i < globals.num_edicts; i++) { ent = &g_edicts[i]; if (!ent->inuse) { continue; } fwrite(&i, sizeof(i), 1, f); WriteEdict(f, ent); } i = -1; fwrite(&i, sizeof(i), 1, f); fclose(f); } /* ========================================================== */ /* * A helper function to * read the edict back * into the memory. Called * by ReadLevel. */ void ReadEdict(FILE *f, edict_t *ent) { field_t *field; fread(ent, sizeof(*ent), 1, f); for (field = fields; field->name; field++) { ReadField(f, field, (byte *)ent); } } /* * A helper function to * read the level local * data from a file. * Called by ReadLevel. */ void ReadLevelLocals(FILE *f) { field_t *field; fread(&level, sizeof(level), 1, f); for (field = levelfields; field->name; field++) { ReadField(f, field, (byte *)&level); } } /* * Reads a level back into the memory. * SpawnEntities were allready called * in the same way when the level was * saved. All world links were cleared * befor this function was called. When * this function is called, no clients * are connected to the server. */ void ReadLevel(const char *filename) { int entnum; FILE *f; int i; edict_t *ent; f = fopen(filename, "rb"); if (!f) { gi.error("Couldn't open %s", filename); } /* free any dynamic memory allocated by loading the level base state */ gi.FreeTags(TAG_LEVEL); /* wipe all the entities */ memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); globals.num_edicts = maxclients->value + 1; /* check edict size */ fread(&i, sizeof(i), 1, f); if (i != sizeof(edict_t)) { fclose(f); gi.error("ReadLevel: mismatched edict size"); } /* load the level locals */ ReadLevelLocals(f); /* load all the entities */ while (1) { if (fread(&entnum, sizeof(entnum), 1, f) != 1) { fclose(f); gi.error("ReadLevel: failed to read entnum"); } if (entnum == -1) { break; } if (entnum >= globals.num_edicts) { globals.num_edicts = entnum + 1; } ent = &g_edicts[entnum]; ReadEdict(f, ent); /* let the server rebuild world links for this ent */ memset(&ent->area, 0, sizeof(ent->area)); gi.linkentity(ent); } fclose(f); /* mark all clients as unconnected */ for (i = 0; i < maxclients->value; i++) { ent = &g_edicts[i + 1]; ent->client = game.clients + i; ent->client->pers.connected = false; } /* do any load time things at this point */ for (i = 0; i < globals.num_edicts; i++) { ent = &g_edicts[i]; if (!ent->inuse) { continue; } /* fire any cross-level triggers */ if (ent->classname) { if (strcmp(ent->classname, "target_crosslevel_target") == 0) { ent->nextthink = level.time + ent->delay; } } } }