// // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2010 James Haley, Samuel Villarreal // // 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 2 // 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. // // DESCRIPTION: // // [STRIFE] New Module // // Strife Hub Saving Code // // For GNU C and POSIX targets, dirent.h should be available. Otherwise, for // Visual C++, we need to include the win_opendir module. #if defined(_MSC_VER) #include #elif defined(__GNUC__) || defined(POSIX) #include #else #error Need an include for dirent.h! #endif #include #include #include #include "z_zone.h" #include "i_system.h" #include "d_player.h" #include "deh_str.h" #include "doomstat.h" #include "m_misc.h" #include "m_saves.h" #include "p_dialog.h" // // File Paths // // Strife maintains multiple file paths related to savegames. // char *savepath; // The actual path of the selected saveslot char *savepathtemp; // The path of the temporary saveslot (strfsav6.ssg) char *loadpath; // Path used while loading the game char character_name[CHARACTER_NAME_LEN]; // Name of "character" for saveslot // // ClearTmp // // Clear the temporary save directory // void ClearTmp(void) { DIR *sp2dir = NULL; struct dirent *f = NULL; if(savepathtemp == NULL) I_Error("you fucked up savedir man!"); if(!(sp2dir = opendir(savepathtemp))) I_Error("ClearTmp: Couldn't open dir %s", savepathtemp); while((f = readdir(sp2dir))) { char *filepath = NULL; // haleyjd: skip "." and ".." without assuming they're the // first two entries like the original code did. if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) continue; // haleyjd: use M_SafeFilePath, not sprintf filepath = M_SafeFilePath(savepathtemp, f->d_name); remove(filepath); Z_Free(filepath); } closedir(sp2dir); } // // ClearSlot // // Clear a single save slot folder // void ClearSlot(void) { DIR *spdir = NULL; struct dirent *f = NULL; if(savepath == NULL) I_Error("userdir is fucked up man!"); if(!(spdir = opendir(savepath))) I_Error("ClearSlot: Couldn't open dir %s", savepath); while((f = readdir(spdir))) { char *filepath = NULL; if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) continue; // haleyjd: use M_SafeFilePath, not sprintf filepath = M_SafeFilePath(savepath, f->d_name); remove(filepath); Z_Free(filepath); } closedir(spdir); } // // FromCurr // // Copying files from savepathtemp to savepath // void FromCurr(void) { DIR *sp2dir = NULL; struct dirent *f = NULL; if(!(sp2dir = opendir(savepathtemp))) I_Error("FromCurr: Couldn't open dir %s", savepathtemp); while((f = readdir(sp2dir))) { byte *filebuffer = NULL; int filelen = 0; char *srcfilename = NULL; char *dstfilename = NULL; // haleyjd: skip "." and ".." without assuming they're the // first two entries like the original code did. if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) continue; // haleyjd: use M_SafeFilePath, NOT sprintf. srcfilename = M_SafeFilePath(savepathtemp, f->d_name); dstfilename = M_SafeFilePath(savepath, f->d_name); filelen = M_ReadFile(srcfilename, &filebuffer); M_WriteFile(dstfilename, filebuffer, filelen); Z_Free(filebuffer); Z_Free(srcfilename); Z_Free(dstfilename); } closedir(sp2dir); } // // ToCurr // // Copying files from savepath to savepathtemp // void ToCurr(void) { DIR *spdir = NULL; struct dirent *f = NULL; ClearTmp(); // BUG: Rogue copypasta'd this error message, which is why we don't know // the real original name of this function. if(!(spdir = opendir(savepath))) I_Error("ClearSlot: Couldn't open dir %s", savepath); while((f = readdir(spdir))) { byte *filebuffer = NULL; int filelen = 0; char *srcfilename = NULL; char *dstfilename = NULL; if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) continue; // haleyjd: use M_SafeFilePath, NOT sprintf. srcfilename = M_SafeFilePath(savepath, f->d_name); dstfilename = M_SafeFilePath(savepathtemp, f->d_name); filelen = M_ReadFile(srcfilename, &filebuffer); M_WriteFile(dstfilename, filebuffer, filelen); Z_Free(filebuffer); Z_Free(srcfilename); Z_Free(dstfilename); } closedir(spdir); } // // M_SaveMoveMapToHere // // Moves a map to the "HERE" save. // void M_SaveMoveMapToHere(void) { char *mapsave = NULL; char *heresave = NULL; char tmpnum[33]; // haleyjd: no itoa available... M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap); // haleyjd: use M_SafeFilePath, not sprintf mapsave = M_SafeFilePath(savepath, tmpnum); heresave = M_SafeFilePath(savepath, "here"); // haleyjd: use M_FileExists, not access if(M_FileExists(mapsave)) { remove(heresave); rename(mapsave, heresave); } Z_Free(mapsave); Z_Free(heresave); } // // M_SaveMoveHereToMap // // Moves the "HERE" save to a map. // void M_SaveMoveHereToMap(void) { char *mapsave = NULL; char *heresave = NULL; char tmpnum[33]; // haleyjd: no itoa available... M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap); mapsave = M_SafeFilePath(savepathtemp, tmpnum); heresave = M_SafeFilePath(savepathtemp, "here"); if(M_FileExists(heresave)) { remove(mapsave); rename(heresave, mapsave); } Z_Free(mapsave); Z_Free(heresave); } // // M_SaveMisObj // // Writes the mission objective into the MIS_OBJ file. // boolean M_SaveMisObj(const char *path) { boolean result; char *destpath = NULL; // haleyjd 20110210: use M_SafeFilePath, not sprintf destpath = M_SafeFilePath(path, "mis_obj"); result = M_WriteFile(destpath, mission_objective, OBJECTIVE_LEN); Z_Free(destpath); return result; } // // M_ReadMisObj // // Reads the mission objective from the MIS_OBJ file. // void M_ReadMisObj(void) { FILE *f = NULL; char *srcpath = NULL; // haleyjd: use M_SafeFilePath, not sprintf srcpath = M_SafeFilePath(savepathtemp, "mis_obj"); if((f = fopen(srcpath, "rb"))) { fread(mission_objective, 1, OBJECTIVE_LEN, f); fclose(f); } Z_Free(srcpath); } //============================================================================= // // Original Routines // // haleyjd - None of the below code is derived from Strife itself, but has been // adapted or created in order to provide secure, portable filepath handling // for the purposes of savegame support. This is partially needed to allow for // differences in Choco due to it being multiplatform. The rest exists because // I cannot stand programming in an impoverished ANSI C environment that // calls sprintf on fixed-size buffers. :P // // // M_Calloc // // haleyjd 20110210 - original routine // Because Choco doesn't have Z_Calloc O_o // void *M_Calloc(size_t n1, size_t n2) { return (n1 *= n2) ? memset(Z_Malloc(n1, PU_STATIC, NULL), 0, n1) : NULL; } // // M_StringAlloc // // haleyjd: This routine takes any number of strings and a number of extra // characters, calculates their combined length, and calls Z_Alloca to create // a temporary buffer of that size. This is extremely useful for allocation of // file paths, and is used extensively in d_main.c. The pointer returned is // to a temporary Z_Alloca buffer, which lives until the next main loop // iteration, so don't cache it. Note that this idiom is not possible with the // normal non-standard alloca function, which allocates stack space. // // [STRIFE] - haleyjd 20110210 // This routine is taken from the Eternity Engine and adapted to do without // Z_Alloca. I need secure string concatenation for filepath handling. The // only difference from use in EE is that the pointer returned in *str must // be manually freed. // int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...) { va_list args; size_t len = extra; if(numstrs < 1) I_Error("M_StringAlloc: invalid input\n"); len += strlen(str1); --numstrs; if(numstrs != 0) { va_start(args, str1); while(numstrs != 0) { const char *argstr = va_arg(args, const char *); len += strlen(argstr); --numstrs; } va_end(args); } ++len; *str = (char *)(M_Calloc(1, len)); return len; } // // M_NormalizeSlashes // // Remove trailing slashes, translate backslashes to slashes // The string to normalize is passed and returned in str // // killough 11/98: rewritten // // [STRIFE] - haleyjd 20110210: Borrowed from Eternity and adapted to respect // the DIR_SEPARATOR define used by Choco Doom. This routine originated in // BOOM. // void M_NormalizeSlashes(char *str) { char *p; // Convert all slashes/backslashes to DIR_SEPARATOR for(p = str; *p; p++) { if((*p == '/' || *p == '\\') && *p != DIR_SEPARATOR) *p = DIR_SEPARATOR; } // Remove trailing slashes while(p > str && *--p == DIR_SEPARATOR) *p = 0; // Collapse multiple slashes for(p = str; (*str++ = *p); ) if(*p++ == DIR_SEPARATOR) while(*p == DIR_SEPARATOR) p++; } // // M_SafeFilePath // // haleyjd 20110210 - original routine. // This routine performs safe, portable concatenation of a base file path // with another path component or file name. The returned string is Z_Malloc'd // and should be freed when it has exhausted its usefulness. // char *M_SafeFilePath(const char *basepath, const char *newcomponent) { int newstrlen = 0; char *newstr = NULL; if (!strcmp(basepath, "")) { basepath = "."; } // Always throw in a slash. M_NormalizeSlashes will remove it in the case // that either basepath or newcomponent includes a redundant slash at the // end or beginning respectively. newstrlen = M_StringAlloc(&newstr, 3, 1, basepath, "/", newcomponent); M_snprintf(newstr, newstrlen, "%s/%s", basepath, newcomponent); M_NormalizeSlashes(newstr); return newstr; } // // M_CreateSaveDirs // // haleyjd 20110210: Vanilla Strife went tits-up if it didn't have the full set // of save folders which were created externally by the installer. fraggle says // that's no good for Choco purposes, and I agree, so this routine will create // the full set of folders under the configured savegamedir. // void M_CreateSaveDirs(const char *savedir) { int i; for(i = 0; i < 7; i++) { char *compositedir; // compose the full path by concatenating with savedir compositedir = M_SafeFilePath(savedir, M_MakeStrifeSaveDir(i, "")); M_MakeDirectory(compositedir); Z_Free(compositedir); } } // // M_MakeStrifeSaveDir // // haleyjd 20110211: Convenience routine // char *M_MakeStrifeSaveDir(int slotnum, const char *extra) { static char tmpbuffer[32]; M_snprintf(tmpbuffer, sizeof(tmpbuffer), "strfsav%d.ssg%s", slotnum, extra); return tmpbuffer; } // // M_GetFilePath // // haleyjd: STRIFE-FIXME: Temporary? // Code borrowed from Eternity, and modified to return separator char // char M_GetFilePath(const char *fn, char *dest, size_t len) { boolean found_slash = false; char *p; char sepchar = '\0'; memset(dest, 0, len); p = dest + len - 1; M_StringCopy(dest, fn, len); while(p >= dest) { if(*p == '/' || *p == '\\') { sepchar = *p; found_slash = true; // mark that the path ended with a slash *p = '\0'; break; } *p = '\0'; p--; } // haleyjd: in the case that no slash was ever found, yet the // path string is empty, we are dealing with a file local to the // working directory. The proper path to return for such a string is // not "", but ".", since the format strings add a slash now. When // the string is empty but a slash WAS found, we really do want to // return the empty string, since the path is relative to the root. if(!found_slash && *dest == '\0') *dest = '.'; // if a separator is not found, default to forward, because Windows // supports that too. if(sepchar == '\0') sepchar = '/'; return sepchar; } // EOF