gcsx_save.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. /* GCSx
  2. ** SAVE.CPP
  3. **
  4. ** File saving and loading
  5. */
  6. /*****************************************************************************
  7. ** Copyright (C) 2003-2006 Janson
  8. **
  9. ** This program is free software; you can redistribute it and/or modify
  10. ** it under the terms of the GNU General Public License as published by
  11. ** the Free Software Foundation; either version 2 of the License, or
  12. ** (at your option) any later version.
  13. **
  14. ** This program is distributed in the hope that it will be useful,
  15. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ** GNU General Public License for more details.
  18. **
  19. ** You should have received a copy of the GNU General Public License
  20. ** along with this program; if not, write to the Free Software
  21. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  22. *****************************************************************************/
  23. #include "all.h"
  24. // Write interface
  25. FileWrite::FileWrite(File* myFilePtr, int noCompression) throw_File { start_func
  26. filePtr = myFilePtr;
  27. zlibBufferOut = NULL;
  28. zlibStream = NULL;
  29. bytesWritten = 0;
  30. zlibStreamInit = 0;
  31. try {
  32. // @TODO: At some point we may intelligently determine whether to actually
  33. // use compression (and what level/type?) based on hints as to content
  34. // and size
  35. if (noCompression) {
  36. compression = WorldFileLoad::BLOCKCOMPRESSION_NONE;
  37. }
  38. else {
  39. compression = WorldFileLoad::BLOCKCOMPRESSION_ZLIB;
  40. // Save room to store original size
  41. seekBegin = filePtr->tell();
  42. filePtr->skip(4); // Exception point
  43. zlibBufferOut = new Byte[ZLIB_BUFFER_SIZE];
  44. zlibStream = new z_stream;
  45. // Use default alloc, free, and compression level for now
  46. zlibStream->zalloc = NULL;
  47. zlibStream->zfree = NULL;
  48. zlibStream->opaque = NULL;
  49. if (deflateInit(zlibStream, Z_DEFAULT_COMPRESSION) != Z_OK) {
  50. fatalCrash(0, "ZLIB Memory/compression error: %s", zlibStream->msg);
  51. }
  52. zlibStreamInit = 1;
  53. zlibStream->next_out = zlibBufferOut;
  54. zlibStream->avail_out = ZLIB_BUFFER_SIZE;
  55. bytesWritten = 4;
  56. }
  57. }
  58. catch (...) {
  59. if (zlibStreamInit) deflateEnd(zlibStream);
  60. delete zlibStream;
  61. delete[] zlibBufferOut;
  62. throw;
  63. }
  64. }
  65. FileWrite::~FileWrite() { start_func
  66. if (zlibStreamInit) deflateEnd(zlibStream);
  67. delete zlibStream;
  68. delete[] zlibBufferOut;
  69. }
  70. void FileWrite::flush() throw_File { start_func
  71. if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
  72. zlibStream->next_in = NULL;
  73. zlibStream->avail_in = 0;
  74. int result;
  75. while ((result = deflate(zlibStream, Z_FINISH)) != Z_STREAM_END) {
  76. if (result == Z_STREAM_ERROR) {
  77. throw FileException("Compression error: %s", zlibStream->msg);
  78. }
  79. // Flush buffer
  80. assert(zlibStream->avail_out == 0);
  81. filePtr->write(zlibBufferOut, ZLIB_BUFFER_SIZE);
  82. zlibStream->next_out = zlibBufferOut;
  83. zlibStream->avail_out = ZLIB_BUFFER_SIZE;
  84. }
  85. // Final flush
  86. filePtr->write(zlibBufferOut, ZLIB_BUFFER_SIZE - zlibStream->avail_out);
  87. bytesWritten = 4 + zlibStream->total_out;
  88. zlibStreamInit = 0;
  89. if (deflateEnd(zlibStream) != Z_OK) {
  90. throw FileException("Compression error: %s", zlibStream->msg);
  91. }
  92. int totalWrote = zlibStream->total_in;
  93. delete zlibStream;
  94. zlibStream = NULL;
  95. // Return to beginning and write original size
  96. int position = filePtr->tell();
  97. filePtr->seek(seekBegin);
  98. filePtr->writeInt(totalWrote);
  99. filePtr->seek(position);
  100. }
  101. }
  102. void FileWrite::write(const void* ptr, int size) throw_File { start_func
  103. if (size) {
  104. if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
  105. zlibStream->next_in = (Byte*)ptr;
  106. zlibStream->avail_in = size;
  107. // Still some to compress?
  108. while (zlibStream->avail_in) {
  109. // Need to flush?
  110. if (zlibStream->avail_out == 0) {
  111. // Flush buffer
  112. filePtr->write(zlibBufferOut, ZLIB_BUFFER_SIZE);
  113. zlibStream->next_out = zlibBufferOut;
  114. zlibStream->avail_out = ZLIB_BUFFER_SIZE;
  115. }
  116. if (deflate(zlibStream, 0) == Z_STREAM_ERROR) {
  117. throw FileException("Compression error: %s", zlibStream->msg);
  118. }
  119. }
  120. bytesWritten = 4 + zlibStream->total_out;
  121. }
  122. else {
  123. filePtr->write(ptr, size);
  124. bytesWritten += size;
  125. }
  126. }
  127. }
  128. void FileWrite::writeIntBulk(const Uint32* ptr, int count) throw_File { start_func
  129. #if SDL_BYTEORDER == SDL_LIL_ENDIAN
  130. write(ptr, count * 4);
  131. #else
  132. for (int pos = 0; pos < count; ++pos) {
  133. writeInt(ptr[pos]);
  134. }
  135. #endif
  136. }
  137. void FileWrite::writeInt(Uint32 value) throw_File { start_func
  138. #if SDL_BYTEORDER == SDL_LIL_ENDIAN
  139. write(&value, 4);
  140. #else
  141. Uint8 bytes[4];
  142. bytes[0] = value & 0xFF;
  143. bytes[1] = (value >> 8) & 0xFF;
  144. bytes[2] = (value >> 16) & 0xFF;
  145. bytes[3] = (value >> 24) & 0xFF;
  146. write(bytes, 4);
  147. #endif
  148. }
  149. void FileWrite::writeInt16(Uint16 value) throw_File { start_func
  150. #if SDL_BYTEORDER == SDL_LIL_ENDIAN
  151. write(&value, 2);
  152. #else
  153. Uint8 bytes[2];
  154. bytes[0] = value & 0xFF;
  155. bytes[1] = (value >> 8) & 0xFF;
  156. write(bytes, 2);
  157. #endif
  158. }
  159. void FileWrite::writeInt8(Uint8 value) throw_File { start_func
  160. write(&value, 1);
  161. }
  162. void FileWrite::writeStr(const string& str) throw_File { start_func
  163. writeInt16(str.size());
  164. write(str.c_str(), str.size());
  165. }
  166. // World file class
  167. // (exception comes from WorldFileLoad)
  168. WorldFile::WorldFile() throw_File : WorldFileLoad(), blockSaves() { start_func
  169. inSaving = 0;
  170. isModifiedAddDel = 0;
  171. }
  172. WorldFile::WorldFile(const string* openFile) throw_File : WorldFileLoad(openFile, File::FILE_MODE_READWRITE), blockSaves() { start_func
  173. emergencySave = NULL;
  174. inSaving = 0;
  175. isModifiedAddDel = 0;
  176. blockSaves.resize(numBlocks, NULL);
  177. }
  178. WorldFile::~WorldFile() { start_func
  179. }
  180. int WorldFile::isNew() { start_func
  181. return filename == NULL;
  182. }
  183. void WorldFile::claimBlock(Uint32 id, SaveLoad* claimingObject) { start_func
  184. WorldFileLoad::claimBlock(id, dynamic_cast<LoadOnly*>(claimingObject));
  185. blockSaves[id] = claimingObject;
  186. }
  187. void WorldFile::discardBlock(SaveLoad* claimingObject) { start_func
  188. assert(claimingObject);
  189. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  190. if (blockSaves[pos] == claimingObject) {
  191. blockSaves[pos] = NULL;
  192. blocks[pos].type = BLOCKTYPE_UNUSED;
  193. isModifiedAddDel = 1;
  194. }
  195. }
  196. }
  197. void WorldFile::newBlock(Uint32 blockType, SaveLoad* claimingObject) { start_func
  198. BlockInfo header;
  199. assert(blockType != BLOCKTYPE_UNUSED);
  200. // Setup header
  201. header.offset = 0;
  202. header.size = 0;
  203. header.type = blockType;
  204. header.object = claimingObject;
  205. header.header.version = 0;
  206. header.header.headerSize = 0;
  207. header.header.contentSize = 0;
  208. header.header.compression = BLOCKCOMPRESSION_NONE;
  209. blocks.push_back(header);
  210. blockSaves.push_back(claimingObject);
  211. ++numBlocks;
  212. isModifiedAddDel = 1;
  213. }
  214. void WorldFile::save() throw_File { start_func
  215. if (inSaving) return;
  216. assert(filePtr);
  217. assert(!isNew());
  218. // @TODO: determine if full save is more efficient or necessary
  219. // If not, only save blocks that are changed (requires blank spots to be
  220. // written in the headers)
  221. fullSave();
  222. }
  223. // Note that the uses of 'new File' are the main thing keeping us
  224. // from saving in a memory exception or other emergency
  225. void WorldFile::saveAs(const string* newFilename) throw_File { start_func
  226. if (inSaving) return;
  227. assert(newFilename);
  228. // Should mark ourselves as "in a save" so if we crash,
  229. // any safety "save all" doesn't try to save us, again
  230. inSaving = 1;
  231. int writingToTemp = 0;
  232. File* newFile = NULL;
  233. char* tempFilenameChar = NULL;
  234. // If we don't write to a tempfile, there'd be an error
  235. // if newfilename matches current filename
  236. // We only do this route if we appear to be writing to the
  237. // same filename and never if we don't already have a file open.
  238. if (newFilename == filename) writingToTemp = 1;
  239. #if FILESYSTEM_CASE_SENSITIVE
  240. if ((filename) && (strcmp(newFilename->c_str(), filename->c_str()) == 0)) writingToTemp = 1;
  241. #else
  242. if ((filename) && (myStricmp(newFilename->c_str(), filename->c_str()) == 0)) writingToTemp = 1;
  243. #endif
  244. if (writingToTemp) {
  245. // New file- temp
  246. string tempPath, tempPathName;
  247. getPathname(newFilename->c_str(), tempPath);
  248. createDirname(tempPath.c_str(), "GCSxXXXXXX", tempPathName);
  249. tempFilenameChar = (char *)malloc(tempPathName.size()+1);
  250. strcpy(tempFilenameChar, tempPathName.c_str());
  251. int fd = mkstemp(tempFilenameChar);
  252. if (fd != -1)
  253. close(fd);
  254. // at this point, the file is created
  255. try {
  256. newFile = new File(tempFilenameChar, File::FILE_MODE_OVERWRITE);
  257. }
  258. catch (...) {
  259. free(tempFilenameChar);
  260. tempFilenameChar = NULL;
  261. throw;
  262. }
  263. }
  264. else {
  265. // New file- write directly
  266. newFile = new File(newFilename->c_str(), File::FILE_MODE_OVERWRITE);
  267. }
  268. Uint32 actualBlockCount = 0;
  269. try {
  270. // Count actual blocks used
  271. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  272. if ((blocks[pos].type != BLOCKTYPE_UNUSED) &&
  273. (blocks[pos].object != NULL)) {
  274. ++actualBlockCount;
  275. }
  276. }
  277. // Empty blocks
  278. actualBlockCount += NUM_EMPTY_BLOCK;
  279. // Header
  280. newFile->write(correctCookie, 4);
  281. newFile->writeInt(currentVersion[0]);
  282. newFile->writeInt(currentVersion[1]);
  283. newFile->writeInt(currentVersion[2]);
  284. newFile->writeInt(currentVersion[3]);
  285. newFile->writeBlanks(8); // Reserved
  286. newFile->writeInt(actualBlockCount);
  287. // Block infos- Where they'll go; reserve space
  288. int infoOffset = newFile->tell();
  289. newFile->writeBlanks(actualBlockCount * 16);
  290. // Write each block, and remember new details
  291. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  292. // Unused block? Will delete on final pass
  293. if ((blocks[pos].type == BLOCKTYPE_UNUSED) ||
  294. (!blocks[pos].object)) {
  295. blocks[pos].newOffset = 0;
  296. blocks[pos].newSize = 0;
  297. }
  298. else {
  299. blocks[pos].newOffset = newFile->tell();
  300. // Block header
  301. newFile->writeBlanks(16);
  302. // Tell block to write header
  303. FileWrite* write = new FileWrite(newFile, 1);
  304. blocks[pos].newHeader.version = blockSaves[pos]->saveHeader(write);
  305. write->flush();
  306. blocks[pos].newHeader.headerSize = write->getSize();
  307. delete write;
  308. // Is content modified?
  309. if (blockSaves[pos]->isContentModified()) {
  310. assert(!blockSaves[pos]->isContentCached());
  311. FileWrite* write = new FileWrite(newFile);
  312. blockSaves[pos]->saveContent(write);
  313. write->flush();
  314. blocks[pos].newHeader.contentSize = write->getSize();
  315. blocks[pos].newHeader.compression = write->getCompression();
  316. delete write;
  317. }
  318. else {
  319. // Copy from previous version
  320. // (assert header was read at some point)
  321. assert(blocks[pos].header.version);
  322. filePtr->seek(blocks[pos].offset + 16 + blocks[pos].header.headerSize);
  323. newFile->copy(filePtr, blocks[pos].header.contentSize);
  324. blocks[pos].newHeader.contentSize = blocks[pos].header.contentSize;
  325. blocks[pos].newHeader.compression = blocks[pos].header.compression;
  326. }
  327. blocks[pos].newSize = 16 + blocks[pos].newHeader.headerSize + blocks[pos].newHeader.contentSize;
  328. // Seek back to fill in header
  329. int prevPos = newFile->tell();
  330. newFile->seek(blocks[pos].newOffset);
  331. newFile->writeInt(blocks[pos].newHeader.version);
  332. newFile->writeInt(blocks[pos].newHeader.headerSize);
  333. newFile->writeInt(blocks[pos].newHeader.contentSize);
  334. newFile->writeInt(blocks[pos].newHeader.compression);
  335. newFile->seek(prevPos);
  336. }
  337. }
  338. // Seek back to fill in info blocks
  339. newFile->seek(infoOffset);
  340. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  341. // Make sure a used block
  342. if ((blocks[pos].type != BLOCKTYPE_UNUSED) &&
  343. (blocks[pos].object != NULL)) {
  344. newFile->writeInt(blocks[pos].newOffset);
  345. newFile->writeInt(blocks[pos].newSize);
  346. newFile->writeInt(blocks[pos].type);
  347. newFile->writeInt(0); // Reserved
  348. }
  349. }
  350. // Done!
  351. newFile->flush();
  352. // From this point forward, there should hopefully be no exceptions,
  353. // because we could potentially lose our saved file, and our blocks
  354. // point to invalid locations
  355. // This shouldn't happen unless writing to a temp file AND somehow the
  356. // deletion of the original file or renaming of the temp file fails,
  357. // which shouldn't, because we obviously had access to write to
  358. // the original file in the first place
  359. if (writingToTemp) {
  360. // Close old file, but don't delete; close temp file also
  361. delete filePtr;
  362. delete newFile;
  363. newFile = NULL;
  364. filePtr = NULL;
  365. // Attempt rename, delete first though- rename doesn't overwrite
  366. // on all systems
  367. remove(newFilename->c_str());
  368. if (rename(tempFilenameChar, newFilename->c_str())) {
  369. // Failed- Warn user, but we can't recover
  370. fatalCrash(0, "Unable to save to %s- your file was saved as %s- error %s", newFilename->c_str(), tempFilenameChar, strerror(errno));
  371. }
  372. // Rename/overwrite successful- open it
  373. try {
  374. filePtr = new File(newFilename->c_str(), File::FILE_MODE_READWRITE);
  375. }
  376. catch (...) {
  377. // File saved, but for some reason, we can't reopen it
  378. // This is now a fatal exception that we can't recover from
  379. fatalCrash(0, "Your file was saved, but unable to reopen %s: %s", newFilename->c_str(), filename->c_str(), strerror(errno));
  380. }
  381. filename = newFilename;
  382. }
  383. else {
  384. // Not temp- just move file "pointer" over
  385. delete filePtr;
  386. filePtr = newFile;
  387. filename = newFilename;
  388. newFile = NULL;
  389. }
  390. // Delete temp filename
  391. if (tempFilenameChar) {
  392. free(tempFilenameChar);
  393. tempFilenameChar = NULL;
  394. }
  395. }
  396. // If an exception, we unmark inSaving, we'll be allowed to resave later
  397. catch (BaseException& e) {
  398. delete newFile;
  399. if (tempFilenameChar) {
  400. free(tempFilenameChar);
  401. tempFilenameChar = NULL;
  402. }
  403. inSaving = 0;
  404. throw;
  405. }
  406. // Tell all blocks of their new cache location and modified
  407. // status; if any of this throws, we're still "insaving".
  408. Uint32 pos;
  409. for (pos = 0; pos < numBlocks; ) {
  410. // Unused block? Delete
  411. if ((blocks[pos].type == BLOCKTYPE_UNUSED) ||
  412. (!blocks[pos].object)) {
  413. for (Uint32 subpos = pos; subpos < (numBlocks - 1); ++subpos) {
  414. blocks[subpos] = blocks[subpos + 1];
  415. blockSaves[subpos] = blockSaves[subpos + 1];
  416. }
  417. blocks.pop_back();
  418. blockSaves.pop_back();
  419. --numBlocks;
  420. }
  421. else {
  422. blocks[pos].offset = blocks[pos].newOffset;
  423. blocks[pos].size = blocks[pos].newSize;
  424. blocks[pos].header = blocks[pos].newHeader;
  425. // If THIS throws a fileexception, it becomes fatal, as
  426. // we just saved it and that means there's something wrong
  427. try {
  428. // @TODO: memory leak if cachedContent excepts and doesn't free contentFile
  429. // (would be a good place for auto_ptr)
  430. FileRead* contentFile = new FileRead(filePtr,
  431. blocks[pos].offset + blocks[pos].header.headerSize + 16,
  432. blocks[pos].header.contentSize,
  433. blocks[pos].header.version,
  434. blocks[pos].header.compression);
  435. blockSaves[pos]->cachedContent(contentFile, 0);
  436. blockSaves[pos]->saveSuccess();
  437. }
  438. catch (FileException& e) {
  439. fatalCrash(0, "Your file was saved, but error rereading file- %s", e.details);
  440. }
  441. ++pos;
  442. }
  443. }
  444. isModifiedAddDel = 0;
  445. for (; pos < actualBlockCount; ++pos) {
  446. BlockInfo header;
  447. header.offset = 0;
  448. header.size = 0;
  449. header.type = BLOCKTYPE_UNUSED;
  450. header.object = NULL;
  451. header.header.version = 0;
  452. header.header.headerSize = 0;
  453. header.header.contentSize = 0;
  454. header.header.compression = BLOCKCOMPRESSION_NONE;
  455. blocks.push_back(header);
  456. // Size of blocks, blockSaves, and numBlocks must be kept syncronized
  457. blockSaves.push_back(NULL);
  458. ++numBlocks;
  459. }
  460. inSaving = 0;
  461. }
  462. void WorldFile::fullSave() throw_File { start_func
  463. if (inSaving) return;
  464. assert(filePtr);
  465. assert(!isNew());
  466. assert(filename);
  467. // Just a save-as the current filename
  468. saveAs(filename);
  469. }
  470. int WorldFile::isModified() { start_func
  471. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  472. if (blockSaves[pos]) {
  473. if (blockSaves[pos]->isHeaderModified()) return 1;
  474. if (blockSaves[pos]->isContentModified()) return 1;
  475. }
  476. }
  477. return isModifiedAddDel;
  478. }