gcsx_save.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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;
  247. getPathname(newFilename->c_str(), tempPath);
  248. tempFilenameChar = tempnam(tempPath.c_str(), NULL);
  249. try {
  250. newFile = new File(tempFilenameChar, File::FILE_MODE_OVERWRITE);
  251. }
  252. catch (...) {
  253. free(tempFilenameChar);
  254. tempFilenameChar = NULL;
  255. throw;
  256. }
  257. }
  258. else {
  259. // New file- write directly
  260. newFile = new File(newFilename->c_str(), File::FILE_MODE_OVERWRITE);
  261. }
  262. Uint32 actualBlockCount = 0;
  263. try {
  264. // Count actual blocks used
  265. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  266. if ((blocks[pos].type != BLOCKTYPE_UNUSED) &&
  267. (blocks[pos].object != NULL)) {
  268. ++actualBlockCount;
  269. }
  270. }
  271. // Empty blocks
  272. actualBlockCount += NUM_EMPTY_BLOCK;
  273. // Header
  274. newFile->write(correctCookie, 4);
  275. newFile->writeInt(currentVersion[0]);
  276. newFile->writeInt(currentVersion[1]);
  277. newFile->writeInt(currentVersion[2]);
  278. newFile->writeInt(currentVersion[3]);
  279. newFile->writeBlanks(8); // Reserved
  280. newFile->writeInt(actualBlockCount);
  281. // Block infos- Where they'll go; reserve space
  282. int infoOffset = newFile->tell();
  283. newFile->writeBlanks(actualBlockCount * 16);
  284. // Write each block, and remember new details
  285. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  286. // Unused block? Will delete on final pass
  287. if ((blocks[pos].type == BLOCKTYPE_UNUSED) ||
  288. (!blocks[pos].object)) {
  289. blocks[pos].newOffset = 0;
  290. blocks[pos].newSize = 0;
  291. }
  292. else {
  293. blocks[pos].newOffset = newFile->tell();
  294. // Block header
  295. newFile->writeBlanks(16);
  296. // Tell block to write header
  297. FileWrite* write = new FileWrite(newFile, 1);
  298. blocks[pos].newHeader.version = blockSaves[pos]->saveHeader(write);
  299. write->flush();
  300. blocks[pos].newHeader.headerSize = write->getSize();
  301. delete write;
  302. // Is content modified?
  303. if (blockSaves[pos]->isContentModified()) {
  304. assert(!blockSaves[pos]->isContentCached());
  305. FileWrite* write = new FileWrite(newFile);
  306. blockSaves[pos]->saveContent(write);
  307. write->flush();
  308. blocks[pos].newHeader.contentSize = write->getSize();
  309. blocks[pos].newHeader.compression = write->getCompression();
  310. delete write;
  311. }
  312. else {
  313. // Copy from previous version
  314. // (assert header was read at some point)
  315. assert(blocks[pos].header.version);
  316. filePtr->seek(blocks[pos].offset + 16 + blocks[pos].header.headerSize);
  317. newFile->copy(filePtr, blocks[pos].header.contentSize);
  318. blocks[pos].newHeader.contentSize = blocks[pos].header.contentSize;
  319. blocks[pos].newHeader.compression = blocks[pos].header.compression;
  320. }
  321. blocks[pos].newSize = 16 + blocks[pos].newHeader.headerSize + blocks[pos].newHeader.contentSize;
  322. // Seek back to fill in header
  323. int prevPos = newFile->tell();
  324. newFile->seek(blocks[pos].newOffset);
  325. newFile->writeInt(blocks[pos].newHeader.version);
  326. newFile->writeInt(blocks[pos].newHeader.headerSize);
  327. newFile->writeInt(blocks[pos].newHeader.contentSize);
  328. newFile->writeInt(blocks[pos].newHeader.compression);
  329. newFile->seek(prevPos);
  330. }
  331. }
  332. // Seek back to fill in info blocks
  333. newFile->seek(infoOffset);
  334. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  335. // Make sure a used block
  336. if ((blocks[pos].type != BLOCKTYPE_UNUSED) &&
  337. (blocks[pos].object != NULL)) {
  338. newFile->writeInt(blocks[pos].newOffset);
  339. newFile->writeInt(blocks[pos].newSize);
  340. newFile->writeInt(blocks[pos].type);
  341. newFile->writeInt(0); // Reserved
  342. }
  343. }
  344. // Done!
  345. newFile->flush();
  346. // From this point forward, there should hopefully be no exceptions,
  347. // because we could potentially lose our saved file, and our blocks
  348. // point to invalid locations
  349. // This shouldn't happen unless writing to a temp file AND somehow the
  350. // deletion of the original file or renaming of the temp file fails,
  351. // which shouldn't, because we obviously had access to write to
  352. // the original file in the first place
  353. if (writingToTemp) {
  354. // Close old file, but don't delete; close temp file also
  355. delete filePtr;
  356. delete newFile;
  357. newFile = NULL;
  358. filePtr = NULL;
  359. // Attempt rename, delete first though- rename doesn't overwrite
  360. // on all systems
  361. remove(newFilename->c_str());
  362. if (rename(tempFilenameChar, newFilename->c_str())) {
  363. // Failed- Warn user, but we can't recover
  364. fatalCrash(0, "Unable to save to %s- your file was saved as %s- error %s", newFilename->c_str(), tempFilenameChar, strerror(errno));
  365. }
  366. // Rename/overwrite successful- open it
  367. try {
  368. filePtr = new File(newFilename->c_str(), File::FILE_MODE_READWRITE);
  369. }
  370. catch (...) {
  371. // File saved, but for some reason, we can't reopen it
  372. // This is now a fatal exception that we can't recover from
  373. fatalCrash(0, "Your file was saved, but unable to reopen %s: %s", newFilename->c_str(), filename->c_str(), strerror(errno));
  374. }
  375. filename = newFilename;
  376. }
  377. else {
  378. // Not temp- just move file "pointer" over
  379. delete filePtr;
  380. filePtr = newFile;
  381. filename = newFilename;
  382. newFile = NULL;
  383. }
  384. // Delete temp filename
  385. if (tempFilenameChar) {
  386. free(tempFilenameChar);
  387. tempFilenameChar = NULL;
  388. }
  389. }
  390. // If an exception, we unmark inSaving, we'll be allowed to resave later
  391. catch (BaseException& e) {
  392. delete newFile;
  393. if (tempFilenameChar) {
  394. free(tempFilenameChar);
  395. tempFilenameChar = NULL;
  396. }
  397. inSaving = 0;
  398. throw;
  399. }
  400. // Tell all blocks of their new cache location and modified
  401. // status; if any of this throws, we're still "insaving".
  402. Uint32 pos;
  403. for (pos = 0; pos < numBlocks; ) {
  404. // Unused block? Delete
  405. if ((blocks[pos].type == BLOCKTYPE_UNUSED) ||
  406. (!blocks[pos].object)) {
  407. for (Uint32 subpos = pos; subpos < (numBlocks - 1); ++subpos) {
  408. blocks[subpos] = blocks[subpos + 1];
  409. blockSaves[subpos] = blockSaves[subpos + 1];
  410. }
  411. blocks.pop_back();
  412. blockSaves.pop_back();
  413. --numBlocks;
  414. }
  415. else {
  416. blocks[pos].offset = blocks[pos].newOffset;
  417. blocks[pos].size = blocks[pos].newSize;
  418. blocks[pos].header = blocks[pos].newHeader;
  419. // If THIS throws a fileexception, it becomes fatal, as
  420. // we just saved it and that means there's something wrong
  421. try {
  422. // @TODO: memory leak if cachedContent excepts and doesn't free contentFile
  423. // (would be a good place for auto_ptr)
  424. FileRead* contentFile = new FileRead(filePtr,
  425. blocks[pos].offset + blocks[pos].header.headerSize + 16,
  426. blocks[pos].header.contentSize,
  427. blocks[pos].header.version,
  428. blocks[pos].header.compression);
  429. blockSaves[pos]->cachedContent(contentFile, 0);
  430. blockSaves[pos]->saveSuccess();
  431. }
  432. catch (FileException& e) {
  433. fatalCrash(0, "Your file was saved, but error rereading file- %s", e.details);
  434. }
  435. ++pos;
  436. }
  437. }
  438. isModifiedAddDel = 0;
  439. for (; pos < actualBlockCount; ++pos) {
  440. BlockInfo header;
  441. header.offset = 0;
  442. header.size = 0;
  443. header.type = BLOCKTYPE_UNUSED;
  444. header.object = NULL;
  445. header.header.version = 0;
  446. header.header.headerSize = 0;
  447. header.header.contentSize = 0;
  448. header.header.compression = BLOCKCOMPRESSION_NONE;
  449. blocks.push_back(header);
  450. // Size of blocks, blockSaves, and numBlocks must be kept syncronized
  451. blockSaves.push_back(NULL);
  452. ++numBlocks;
  453. }
  454. inSaving = 0;
  455. }
  456. void WorldFile::fullSave() throw_File { start_func
  457. if (inSaving) return;
  458. assert(filePtr);
  459. assert(!isNew());
  460. assert(filename);
  461. // Just a save-as the current filename
  462. saveAs(filename);
  463. }
  464. int WorldFile::isModified() { start_func
  465. for (Uint32 pos = 0; pos < numBlocks; ++pos) {
  466. if (blockSaves[pos]) {
  467. if (blockSaves[pos]->isHeaderModified()) return 1;
  468. if (blockSaves[pos]->isContentModified()) return 1;
  469. }
  470. }
  471. return isModifiedAddDel;
  472. }