123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- /* GCSx
- ** SAVE.CPP
- **
- ** File saving and loading
- */
- /*****************************************************************************
- ** Copyright (C) 2003-2006 Janson
- **
- ** 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.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program; if not, write to the Free Software
- ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
- *****************************************************************************/
- #include "all.h"
- // Write interface
- FileWrite::FileWrite(File* myFilePtr, int noCompression) throw_File { start_func
- filePtr = myFilePtr;
- zlibBufferOut = NULL;
- zlibStream = NULL;
- bytesWritten = 0;
- zlibStreamInit = 0;
-
- try {
- // @TODO: At some point we may intelligently determine whether to actually
- // use compression (and what level/type?) based on hints as to content
- // and size
- if (noCompression) {
- compression = WorldFileLoad::BLOCKCOMPRESSION_NONE;
- }
- else {
- compression = WorldFileLoad::BLOCKCOMPRESSION_ZLIB;
- // Save room to store original size
- seekBegin = filePtr->tell();
- filePtr->skip(4); // Exception point
- zlibBufferOut = new Byte[ZLIB_BUFFER_SIZE];
- zlibStream = new z_stream;
- // Use default alloc, free, and compression level for now
- zlibStream->zalloc = NULL;
- zlibStream->zfree = NULL;
- zlibStream->opaque = NULL;
- if (deflateInit(zlibStream, Z_DEFAULT_COMPRESSION) != Z_OK) {
- fatalCrash(0, "ZLIB Memory/compression error: %s", zlibStream->msg);
- }
- zlibStreamInit = 1;
- zlibStream->next_out = zlibBufferOut;
- zlibStream->avail_out = ZLIB_BUFFER_SIZE;
- bytesWritten = 4;
- }
- }
- catch (...) {
- if (zlibStreamInit) deflateEnd(zlibStream);
- delete zlibStream;
- delete[] zlibBufferOut;
- throw;
- }
- }
- FileWrite::~FileWrite() { start_func
- if (zlibStreamInit) deflateEnd(zlibStream);
- delete zlibStream;
- delete[] zlibBufferOut;
- }
- void FileWrite::flush() throw_File { start_func
- if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
- zlibStream->next_in = NULL;
- zlibStream->avail_in = 0;
- int result;
- while ((result = deflate(zlibStream, Z_FINISH)) != Z_STREAM_END) {
- if (result == Z_STREAM_ERROR) {
- throw FileException("Compression error: %s", zlibStream->msg);
- }
- // Flush buffer
- assert(zlibStream->avail_out == 0);
- filePtr->write(zlibBufferOut, ZLIB_BUFFER_SIZE);
- zlibStream->next_out = zlibBufferOut;
- zlibStream->avail_out = ZLIB_BUFFER_SIZE;
- }
- // Final flush
- filePtr->write(zlibBufferOut, ZLIB_BUFFER_SIZE - zlibStream->avail_out);
- bytesWritten = 4 + zlibStream->total_out;
- zlibStreamInit = 0;
- if (deflateEnd(zlibStream) != Z_OK) {
- throw FileException("Compression error: %s", zlibStream->msg);
- }
- int totalWrote = zlibStream->total_in;
- delete zlibStream;
- zlibStream = NULL;
- // Return to beginning and write original size
- int position = filePtr->tell();
- filePtr->seek(seekBegin);
- filePtr->writeInt(totalWrote);
- filePtr->seek(position);
- }
- }
- void FileWrite::write(const void* ptr, int size) throw_File { start_func
- if (size) {
- if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
- zlibStream->next_in = (Byte*)ptr;
- zlibStream->avail_in = size;
- // Still some to compress?
- while (zlibStream->avail_in) {
- // Need to flush?
- if (zlibStream->avail_out == 0) {
- // Flush buffer
- filePtr->write(zlibBufferOut, ZLIB_BUFFER_SIZE);
- zlibStream->next_out = zlibBufferOut;
- zlibStream->avail_out = ZLIB_BUFFER_SIZE;
- }
- if (deflate(zlibStream, 0) == Z_STREAM_ERROR) {
- throw FileException("Compression error: %s", zlibStream->msg);
- }
- }
- bytesWritten = 4 + zlibStream->total_out;
- }
- else {
- filePtr->write(ptr, size);
- bytesWritten += size;
- }
- }
- }
- void FileWrite::writeIntBulk(const Uint32* ptr, int count) throw_File { start_func
- #if SDL_BYTEORDER == SDL_LIL_ENDIAN
- write(ptr, count * 4);
- #else
- for (int pos = 0; pos < count; ++pos) {
- writeInt(ptr[pos]);
- }
- #endif
- }
- void FileWrite::writeInt(Uint32 value) throw_File { start_func
- #if SDL_BYTEORDER == SDL_LIL_ENDIAN
- write(&value, 4);
- #else
- Uint8 bytes[4];
- bytes[0] = value & 0xFF;
- bytes[1] = (value >> 8) & 0xFF;
- bytes[2] = (value >> 16) & 0xFF;
- bytes[3] = (value >> 24) & 0xFF;
- write(bytes, 4);
- #endif
- }
-
- void FileWrite::writeInt16(Uint16 value) throw_File { start_func
- #if SDL_BYTEORDER == SDL_LIL_ENDIAN
- write(&value, 2);
- #else
- Uint8 bytes[2];
- bytes[0] = value & 0xFF;
- bytes[1] = (value >> 8) & 0xFF;
- write(bytes, 2);
- #endif
- }
-
- void FileWrite::writeInt8(Uint8 value) throw_File { start_func
- write(&value, 1);
- }
-
- void FileWrite::writeStr(const string& str) throw_File { start_func
- writeInt16(str.size());
- write(str.c_str(), str.size());
- }
- // World file class
- // (exception comes from WorldFileLoad)
- WorldFile::WorldFile() throw_File : WorldFileLoad(), blockSaves() { start_func
- inSaving = 0;
- isModifiedAddDel = 0;
- }
- WorldFile::WorldFile(const string* openFile) throw_File : WorldFileLoad(openFile, File::FILE_MODE_READWRITE), blockSaves() { start_func
- emergencySave = NULL;
- inSaving = 0;
- isModifiedAddDel = 0;
- blockSaves.resize(numBlocks, NULL);
- }
- WorldFile::~WorldFile() { start_func
- }
-
- int WorldFile::isNew() { start_func
- return filename == NULL;
- }
-
- void WorldFile::claimBlock(Uint32 id, SaveLoad* claimingObject) { start_func
- WorldFileLoad::claimBlock(id, dynamic_cast<LoadOnly*>(claimingObject));
- blockSaves[id] = claimingObject;
- }
-
- void WorldFile::discardBlock(SaveLoad* claimingObject) { start_func
- assert(claimingObject);
- for (Uint32 pos = 0; pos < numBlocks; ++pos) {
- if (blockSaves[pos] == claimingObject) {
- blockSaves[pos] = NULL;
- blocks[pos].type = BLOCKTYPE_UNUSED;
- isModifiedAddDel = 1;
- }
- }
- }
- void WorldFile::newBlock(Uint32 blockType, SaveLoad* claimingObject) { start_func
- BlockInfo header;
- assert(blockType != BLOCKTYPE_UNUSED);
-
- // Setup header
- header.offset = 0;
- header.size = 0;
- header.type = blockType;
- header.object = claimingObject;
- header.header.version = 0;
- header.header.headerSize = 0;
- header.header.contentSize = 0;
- header.header.compression = BLOCKCOMPRESSION_NONE;
- blocks.push_back(header);
-
- blockSaves.push_back(claimingObject);
-
- ++numBlocks;
- isModifiedAddDel = 1;
- }
-
- void WorldFile::save() throw_File { start_func
- if (inSaving) return;
- assert(filePtr);
- assert(!isNew());
- // @TODO: determine if full save is more efficient or necessary
- // If not, only save blocks that are changed (requires blank spots to be
- // written in the headers)
- fullSave();
- }
- // Note that the uses of 'new File' are the main thing keeping us
- // from saving in a memory exception or other emergency
- void WorldFile::saveAs(const string* newFilename) throw_File { start_func
- if (inSaving) return;
- assert(newFilename);
-
- // Should mark ourselves as "in a save" so if we crash,
- // any safety "save all" doesn't try to save us, again
- inSaving = 1;
- int writingToTemp = 0;
- File* newFile = NULL;
- char* tempFilenameChar = NULL;
- // If we don't write to a tempfile, there'd be an error
- // if newfilename matches current filename
- // We only do this route if we appear to be writing to the
- // same filename and never if we don't already have a file open.
- if (newFilename == filename) writingToTemp = 1;
- #if FILESYSTEM_CASE_SENSITIVE
- if ((filename) && (strcmp(newFilename->c_str(), filename->c_str()) == 0)) writingToTemp = 1;
- #else
- if ((filename) && (myStricmp(newFilename->c_str(), filename->c_str()) == 0)) writingToTemp = 1;
- #endif
-
- if (writingToTemp) {
- // New file- temp
- string tempPath, tempPathName;
- getPathname(newFilename->c_str(), tempPath);
- createDirname(tempPath.c_str(), "GCSxXXXXXX", tempPathName);
- tempFilenameChar = (char *)malloc(tempPathName.size()+1);
- strcpy(tempFilenameChar, tempPathName.c_str());
- int fd = mkstemp(tempFilenameChar);
- if (fd != -1)
- close(fd);
- // at this point, the file is created
- try {
- newFile = new File(tempFilenameChar, File::FILE_MODE_OVERWRITE);
- }
- catch (...) {
- free(tempFilenameChar);
- tempFilenameChar = NULL;
- throw;
- }
- }
- else {
- // New file- write directly
- newFile = new File(newFilename->c_str(), File::FILE_MODE_OVERWRITE);
- }
-
- Uint32 actualBlockCount = 0;
- try {
- // Count actual blocks used
- for (Uint32 pos = 0; pos < numBlocks; ++pos) {
- if ((blocks[pos].type != BLOCKTYPE_UNUSED) &&
- (blocks[pos].object != NULL)) {
- ++actualBlockCount;
- }
- }
-
- // Empty blocks
- actualBlockCount += NUM_EMPTY_BLOCK;
-
- // Header
- newFile->write(correctCookie, 4);
- newFile->writeInt(currentVersion[0]);
- newFile->writeInt(currentVersion[1]);
- newFile->writeInt(currentVersion[2]);
- newFile->writeInt(currentVersion[3]);
- newFile->writeBlanks(8); // Reserved
- newFile->writeInt(actualBlockCount);
-
- // Block infos- Where they'll go; reserve space
- int infoOffset = newFile->tell();
- newFile->writeBlanks(actualBlockCount * 16);
-
- // Write each block, and remember new details
- for (Uint32 pos = 0; pos < numBlocks; ++pos) {
- // Unused block? Will delete on final pass
- if ((blocks[pos].type == BLOCKTYPE_UNUSED) ||
- (!blocks[pos].object)) {
- blocks[pos].newOffset = 0;
- blocks[pos].newSize = 0;
- }
- else {
- blocks[pos].newOffset = newFile->tell();
- // Block header
- newFile->writeBlanks(16);
-
- // Tell block to write header
- FileWrite* write = new FileWrite(newFile, 1);
- blocks[pos].newHeader.version = blockSaves[pos]->saveHeader(write);
- write->flush();
- blocks[pos].newHeader.headerSize = write->getSize();
- delete write;
-
- // Is content modified?
- if (blockSaves[pos]->isContentModified()) {
- assert(!blockSaves[pos]->isContentCached());
-
- FileWrite* write = new FileWrite(newFile);
- blockSaves[pos]->saveContent(write);
- write->flush();
- blocks[pos].newHeader.contentSize = write->getSize();
- blocks[pos].newHeader.compression = write->getCompression();
- delete write;
- }
- else {
- // Copy from previous version
- // (assert header was read at some point)
- assert(blocks[pos].header.version);
-
- filePtr->seek(blocks[pos].offset + 16 + blocks[pos].header.headerSize);
- newFile->copy(filePtr, blocks[pos].header.contentSize);
- blocks[pos].newHeader.contentSize = blocks[pos].header.contentSize;
- blocks[pos].newHeader.compression = blocks[pos].header.compression;
- }
- blocks[pos].newSize = 16 + blocks[pos].newHeader.headerSize + blocks[pos].newHeader.contentSize;
-
- // Seek back to fill in header
- int prevPos = newFile->tell();
- newFile->seek(blocks[pos].newOffset);
- newFile->writeInt(blocks[pos].newHeader.version);
- newFile->writeInt(blocks[pos].newHeader.headerSize);
- newFile->writeInt(blocks[pos].newHeader.contentSize);
- newFile->writeInt(blocks[pos].newHeader.compression);
- newFile->seek(prevPos);
- }
- }
-
- // Seek back to fill in info blocks
- newFile->seek(infoOffset);
- for (Uint32 pos = 0; pos < numBlocks; ++pos) {
- // Make sure a used block
- if ((blocks[pos].type != BLOCKTYPE_UNUSED) &&
- (blocks[pos].object != NULL)) {
- newFile->writeInt(blocks[pos].newOffset);
- newFile->writeInt(blocks[pos].newSize);
- newFile->writeInt(blocks[pos].type);
- newFile->writeInt(0); // Reserved
- }
- }
-
- // Done!
- newFile->flush();
-
- // From this point forward, there should hopefully be no exceptions,
- // because we could potentially lose our saved file, and our blocks
- // point to invalid locations
-
- // This shouldn't happen unless writing to a temp file AND somehow the
- // deletion of the original file or renaming of the temp file fails,
- // which shouldn't, because we obviously had access to write to
- // the original file in the first place
-
- if (writingToTemp) {
- // Close old file, but don't delete; close temp file also
- delete filePtr;
- delete newFile;
- newFile = NULL;
- filePtr = NULL;
- // Attempt rename, delete first though- rename doesn't overwrite
- // on all systems
- remove(newFilename->c_str());
- if (rename(tempFilenameChar, newFilename->c_str())) {
- // Failed- Warn user, but we can't recover
- fatalCrash(0, "Unable to save to %s- your file was saved as %s- error %s", newFilename->c_str(), tempFilenameChar, strerror(errno));
- }
- // Rename/overwrite successful- open it
- try {
- filePtr = new File(newFilename->c_str(), File::FILE_MODE_READWRITE);
- }
- catch (...) {
- // File saved, but for some reason, we can't reopen it
- // This is now a fatal exception that we can't recover from
- fatalCrash(0, "Your file was saved, but unable to reopen %s: %s", newFilename->c_str(), filename->c_str(), strerror(errno));
- }
- filename = newFilename;
- }
- else {
- // Not temp- just move file "pointer" over
- delete filePtr;
- filePtr = newFile;
- filename = newFilename;
- newFile = NULL;
- }
-
- // Delete temp filename
- if (tempFilenameChar) {
- free(tempFilenameChar);
- tempFilenameChar = NULL;
- }
- }
- // If an exception, we unmark inSaving, we'll be allowed to resave later
- catch (BaseException& e) {
- delete newFile;
- if (tempFilenameChar) {
- free(tempFilenameChar);
- tempFilenameChar = NULL;
- }
- inSaving = 0;
- throw;
- }
-
- // Tell all blocks of their new cache location and modified
- // status; if any of this throws, we're still "insaving".
- Uint32 pos;
- for (pos = 0; pos < numBlocks; ) {
- // Unused block? Delete
- if ((blocks[pos].type == BLOCKTYPE_UNUSED) ||
- (!blocks[pos].object)) {
- for (Uint32 subpos = pos; subpos < (numBlocks - 1); ++subpos) {
- blocks[subpos] = blocks[subpos + 1];
- blockSaves[subpos] = blockSaves[subpos + 1];
- }
- blocks.pop_back();
- blockSaves.pop_back();
- --numBlocks;
- }
- else {
- blocks[pos].offset = blocks[pos].newOffset;
- blocks[pos].size = blocks[pos].newSize;
- blocks[pos].header = blocks[pos].newHeader;
- // If THIS throws a fileexception, it becomes fatal, as
- // we just saved it and that means there's something wrong
- try {
- // @TODO: memory leak if cachedContent excepts and doesn't free contentFile
- // (would be a good place for auto_ptr)
- FileRead* contentFile = new FileRead(filePtr,
- blocks[pos].offset + blocks[pos].header.headerSize + 16,
- blocks[pos].header.contentSize,
- blocks[pos].header.version,
- blocks[pos].header.compression);
- blockSaves[pos]->cachedContent(contentFile, 0);
- blockSaves[pos]->saveSuccess();
- }
- catch (FileException& e) {
- fatalCrash(0, "Your file was saved, but error rereading file- %s", e.details);
- }
- ++pos;
- }
- }
- isModifiedAddDel = 0;
-
- for (; pos < actualBlockCount; ++pos) {
- BlockInfo header;
- header.offset = 0;
- header.size = 0;
- header.type = BLOCKTYPE_UNUSED;
- header.object = NULL;
- header.header.version = 0;
- header.header.headerSize = 0;
- header.header.contentSize = 0;
- header.header.compression = BLOCKCOMPRESSION_NONE;
- blocks.push_back(header);
- // Size of blocks, blockSaves, and numBlocks must be kept syncronized
- blockSaves.push_back(NULL);
- ++numBlocks;
- }
- inSaving = 0;
- }
-
- void WorldFile::fullSave() throw_File { start_func
- if (inSaving) return;
- assert(filePtr);
- assert(!isNew());
- assert(filename);
-
- // Just a save-as the current filename
- saveAs(filename);
- }
-
- int WorldFile::isModified() { start_func
- for (Uint32 pos = 0; pos < numBlocks; ++pos) {
- if (blockSaves[pos]) {
- if (blockSaves[pos]->isHeaderModified()) return 1;
- if (blockSaves[pos]->isContentModified()) return 1;
- }
- }
-
- return isModifiedAddDel;
- }
|