From 7650b61d27aa848d0f22f59c43d23787c04a65b7 Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Tue, 18 Nov 2008 16:31:55 +0000 Subject: Applied a slightly modified version of my patch #2307224 - "BS1: Save/load overhaul" svn-id: r35111 --- engines/sword1/control.cpp | 302 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 233 insertions(+), 69 deletions(-) (limited to 'engines/sword1/control.cpp') diff --git a/engines/sword1/control.cpp b/engines/sword1/control.cpp index 49cd2c4b34..78b09e5c82 100644 --- a/engines/sword1/control.cpp +++ b/engines/sword1/control.cpp @@ -23,6 +23,7 @@ * */ +#include // for extended infos #include "common/file.h" #include "common/util.h" @@ -31,6 +32,7 @@ #include "common/system.h" #include "common/config-manager.h" +#include "graphics/thumbnail.h" #include "gui/message.h" #include "sword1/control.h" @@ -509,6 +511,8 @@ void Control::setupMainPanel(void) { } void Control::setupSaveRestorePanel(bool saving) { + readSavegameDescriptions(); + FrameHeader *savePanel = _resMan->fetchFrame(_resMan->openFetchRes(SR_WINDOW), 0); uint16 panelX = (640 - _resMan->getUint16(savePanel->width)) / 2; uint16 panelY = (480 - _resMan->getUint16(savePanel->height)) / 2; @@ -690,22 +694,20 @@ bool Control::keyAccepted(uint16 ascii) { void Control::handleSaveKey(Common::KeyState kbd) { if (_selectedSavegame < 255) { - uint8 len = strlen((char*)_saveNames[_selectedSavegame]); + uint8 len = _saveNames[_selectedSavegame].size(); if ((kbd.keycode == Common::KEYCODE_BACKSPACE) && len) // backspace - _saveNames[_selectedSavegame][len - 1] = '\0'; + _saveNames[_selectedSavegame].deleteLastChar(); else if (keyAccepted(kbd.ascii) && (len < 31)) { - _saveNames[_selectedSavegame][len] = kbd.ascii; - _saveNames[_selectedSavegame][len + 1] = '\0'; + _saveNames[_selectedSavegame].insertChar(kbd.ascii, len); } showSavegameNames(); } } bool Control::saveToFile(void) { - if ((_selectedSavegame == 255) || !strlen((char*)_saveNames[_selectedSavegame])) + if ((_selectedSavegame == 255) || _saveNames[_selectedSavegame].size() == 0) return false; // no saveslot selected or no name entered saveGameToFile(_selectedSavegame); - writeSavegameDescriptions(); return true; } @@ -717,33 +719,40 @@ bool Control::restoreFromFile(void) { } void Control::readSavegameDescriptions(void) { - Common::InSaveFile *inf; - inf = _saveFileMan->openForLoading("SAVEGAME.INF"); - _saveScrollPos = _saveFiles = 0; - _selectedSavegame = 255; - for (uint8 cnt = 0; cnt < 64; cnt++) { - memset(_saveNames[cnt], 0, sizeof(_saveNames[cnt])); - } - if (inf) { - uint8 curFileNum = 0; - uint8 ch; - do { - uint8 pos = 0; - do { - ch = inf->readByte(); - if (pos < sizeof(_saveNames[curFileNum]) - 1) { - if ((ch == 10) || (ch == 255) || (inf->eos())) - _saveNames[curFileNum][pos++] = '\0'; - else if (ch >= 32) - _saveNames[curFileNum][pos++] = ch; - } - } while ((ch != 10) && (ch != 255) && (!inf->eos())); - curFileNum++; - } while ((ch != 255) && (!inf->eos())); - _saveFiles = curFileNum; - _numSaves = _saveFiles; + char saveName[40]; + Common::String pattern = ConfMan.get("gameid") + ".???"; + Common::StringList filenames = _saveFileMan->listSavefiles(pattern.c_str()); + sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + int num = 0; + int slotNum = 0; + for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + slotNum = atoi(file->c_str() + file->size() - 3); + + while (num < slotNum) { + _saveNames.push_back(""); + num++; + } + + if (slotNum >= 0 && slotNum <= 999) { + num++; + Common::InSaveFile *in = _saveFileMan->openForLoading(file->c_str()); + if (in) { + in->readUint32LE(); // header + in->read(saveName, 40); + _saveNames.push_back(saveName); + delete in; + } + } } - delete inf; + + for (int i = _saveNames.size(); i < 1000; i++) + _saveNames.push_back(""); + + _saveScrollPos = 0; + _selectedSavegame = 255; + _saveFiles = _numSaves = _saveNames.size(); } int Control::displayMessage(const char *altButton, const char *message, ...) { @@ -760,43 +769,59 @@ int Control::displayMessage(const char *altButton, const char *message, ...) { return result; } -void Control::writeSavegameDescriptions(void) { - Common::OutSaveFile *outf; - outf = _saveFileMan->openForSaving("SAVEGAME.INF"); +bool Control::savegamesExist(void) { + Common::String pattern = ConfMan.get("gameid") + ".???"; + Common::StringList saveNames = _saveFileMan->listSavefiles(pattern.c_str()); + return saveNames.size() > 0; +} - if (!outf) { - // Display an error message, and do nothing - displayMessage(0, "Can't create SAVEGAME.INF. (%s)", _saveFileMan->popErrorDesc().c_str()); +void Control::checkForOldSaveGames() { + Common::InSaveFile *inf = _saveFileMan->openForLoading("SAVEGAME.INF"); + + if (!inf) { + delete inf; return; } - // if the player accidently clicked the last slot and then deselected it again, - // we'd still have _saveFiles == 64, so get rid of the empty end. - while (strlen((char*)_saveNames[_saveFiles - 1]) == 0) - _saveFiles--; - for (uint8 cnt = 0; cnt < _saveFiles; cnt++) { - int len = strlen((char*)_saveNames[cnt]); - if (len > 0) - outf->write(_saveNames[cnt], len); - if (cnt < _saveFiles - 1) - outf->writeByte(10); - else - outf->writeByte(255); + GUI::MessageDialog dialog0( + "ScummVM found that you have old savefiles for Broken Sword 1 that should be converted.\n" + "The old save game format is no longer supported, so you will not be able to load your games if you don't convert them.\n\n" + "Press OK to convert them now, otherwise you will be asked again the next time you start the game.\n", "OK", "Cancel"); + + int choice = dialog0.runModal(); + if (choice == GUI::kMessageCancel) { + // user pressed cancel + return; } - outf->finalize(); - if (outf->ioFailed()) - displayMessage(0, "Can't write to SAVEGAME.INF. Device full? (%s)", _saveFileMan->popErrorDesc().c_str()); - delete outf; -} -bool Control::savegamesExist(void) { - bool retVal = false; - Common::InSaveFile *inf; - inf = _saveFileMan->openForLoading("SAVEGAME.INF"); - if (inf) - retVal = true; - delete inf; - return retVal; + // Convert every save slot we find in the index file to the new format + uint8 saveName[32]; + uint8 slot = 0; + uint8 ch; + + memset(saveName, 0, sizeof(saveName)); + + do { + uint8 pos = 0; + do { + ch = inf->readByte(); + if (pos < sizeof(saveName) - 1) { + if ((ch == 10) || (ch == 255) || (inf->eos())) + saveName[pos++] = '\0'; + else if (ch >= 32) + saveName[pos++] = ch; + } + } while ((ch != 10) && (ch != 255) && (!inf->eos())); + + if (pos > 1) // if the slot has a description + convertSaveGame(slot, (char*)saveName); + slot++; + } while ((ch != 255) && (!inf->eos())); + + delete inf; + + // Delete index file + _saveFileMan->removeSavefile("SAVEGAME.INF"); } void Control::showSavegameNames(void) { @@ -805,7 +830,7 @@ void Control::showSavegameNames(void) { uint8 textMode = TEXT_LEFT_ALIGN; uint16 ycoord = _saveButtons[cnt].y + 2; uint8 str[40]; - sprintf((char*)str, "%d. %s", cnt + _saveScrollPos + 1, _saveNames[cnt + _saveScrollPos]); + sprintf((char*)str, "%d. %s", cnt + _saveScrollPos + 1, _saveNames[cnt + _saveScrollPos].c_str()); if (cnt + _saveScrollPos == _selectedSavegame) { textMode |= TEXT_RED_FONT; ycoord += 2; @@ -821,10 +846,10 @@ void Control::saveNameSelect(uint8 id, bool saving) { _buttons[id - BUTTON_SAVE_SELECT1]->setSelected(1); uint8 num = (id - BUTTON_SAVE_SELECT1) + _saveScrollPos; if (saving && (_selectedSavegame != 255)) // the player may have entered something, clear it again - strcpy((char*)_saveNames[_selectedSavegame], (char*)_oldName); + _saveNames[_selectedSavegame] = _oldName; if (num < _saveFiles) { _selectedSavegame = num; - strcpy((char*)_oldName, (char*)_saveNames[num]); // save for later + _oldName = _saveNames[num]; // save for later } else { if (!saving) _buttons[id - BUTTON_SAVE_SELECT1]->setSelected(0); // no save in slot, deselect it @@ -832,7 +857,7 @@ void Control::saveNameSelect(uint8 id, bool saving) { if (_saveFiles <= num) _saveFiles = num + 1; _selectedSavegame = num; - _oldName[0] = '\0'; + _oldName.clear(); } } if (_selectedSavegame < 255) @@ -950,7 +975,7 @@ void Control::renderVolumeBar(uint8 id, uint8 volL, uint8 volR) { void Control::saveGameToFile(uint8 slot) { char fName[15]; uint16 cnt; - sprintf(fName, "SAVEGAME.%03d", slot); + sprintf(fName, "%s.%03d", ConfMan.get("gameid").c_str(), slot); uint16 liveBuf[TOTAL_SECTIONS]; Common::OutSaveFile *outf; outf = _saveFileMan->openForSaving(fName); @@ -960,6 +985,31 @@ void Control::saveGameToFile(uint8 slot) { return; } + outf->writeUint32LE(SAVEGAME_HEADER); + outf->write(_saveNames[slot].c_str(), 40); + outf->writeByte(SAVEGAME_VERSION); + + // FIXME: at this point, we can't save a thumbnail of the game screen, as the save menu is shown +#if 0 + outf->writeByte(HAS_THUMBNAIL); + + // Thumbnail + Graphics::saveThumbnail(*outf); +#else + outf->writeByte(NO_THUMBNAIL); +#endif + + // Date / time + tm curTime; + _system->getTimeAndDate(curTime); + + uint32 saveDate = (curTime.tm_mday & 0xFF) << 24 | ((curTime.tm_mon + 1) & 0xFF) << 16 | (curTime.tm_year + 1900) & 0xFFFF; + uint16 saveTime = (curTime.tm_hour & 0xFF) << 8 | (curTime.tm_min) & 0xFF; + + outf->writeUint32BE(saveDate); + outf->writeUint16BE(saveTime); + // TODO: played time + _objMan->saveLiveList(liveBuf); for (cnt = 0; cnt < TOTAL_SECTIONS; cnt++) outf->writeUint16LE(liveBuf[cnt]); @@ -987,7 +1037,7 @@ void Control::saveGameToFile(uint8 slot) { bool Control::restoreGameFromFile(uint8 slot) { char fName[15]; uint16 cnt; - sprintf(fName, "SAVEGAME.%03d", slot); + sprintf(fName, "%s.%03d", ConfMan.get("gameid").c_str(), slot); Common::InSaveFile *inf; inf = _saveFileMan->openForLoading(fName); if (!inf) { @@ -996,6 +1046,36 @@ bool Control::restoreGameFromFile(uint8 slot) { return false; } + uint saveHeader = inf->readUint32LE(); + if (saveHeader != SAVEGAME_HEADER) { + // Display an error message, and do nothing + displayMessage(0, "Save game '%s' is corrupt", fName); + return false; + } + + inf->skip(40); // skip description + uint8 saveVersion = inf->readByte(); + + if (saveVersion != SAVEGAME_VERSION) { + warning("Different save game version"); + return false; + } + + bool hasThumbnail = inf->readByte(); + + if (hasThumbnail) { + // We don't need the thumbnail here, so just read it and discard it + Graphics::Surface *thumbnail = new Graphics::Surface(); + assert(thumbnail); + Graphics::loadThumbnail(*inf, *thumbnail); + delete thumbnail; + thumbnail = 0; + } + + inf->readUint32BE(); // save date + inf->readUint16BE(); // save time + // TODO: played time + _restoreBuf = (uint8*)malloc( TOTAL_SECTIONS * 2 + NUM_SCRIPT_VARS * 4 + @@ -1026,6 +1106,90 @@ bool Control::restoreGameFromFile(uint8 slot) { return true; } +bool Control::convertSaveGame(uint8 slot, char* desc) { + char oldFileName[15]; + char newFileName[40]; + sprintf(oldFileName, "SAVEGAME.%03d", slot); + sprintf(newFileName, "%s.%03d", ConfMan.get("gameid").c_str(), slot); + uint8 *saveData; + int dataSize; + + // Check if the new file already exists + Common::InSaveFile *testSave = _saveFileMan->openForLoading(newFileName); + + if (testSave) { + delete testSave; + + char msg[200]; + sprintf(msg, "Target new save game already exists!\n" + "Would you like to keep the old save game (%s) or the new one (%s)?\n", + oldFileName, newFileName); + GUI::MessageDialog dialog0(msg, "Keep the old one", "Keep the new one"); + + int choice = dialog0.runModal(); + if (choice == GUI::kMessageCancel) { + // User chose to keep the new game, so delete the old one + _saveFileMan->removeSavefile(oldFileName); + return true; + } + } + + Common::InSaveFile *oldSave = _saveFileMan->openForLoading(oldFileName); + if (!oldSave) { + // Display a warning message and do nothing + warning("Can't open file '%s'", oldFileName); + return false; + } + + // Read data from old type of save game + dataSize = oldSave->size(); + saveData = new uint8[dataSize]; + oldSave->read(saveData, dataSize); + delete oldSave; + + // Now write the save data to a new type of save game + Common::OutSaveFile *newSave; + newSave = _saveFileMan->openForSaving(newFileName); + if (!newSave) { + // Display a warning message and do nothing + warning("Unable to create file '%s'. (%s)", newFileName, _saveFileMan->popErrorDesc().c_str()); + free(saveData); + saveData = NULL; + return false; + } + + newSave->writeUint32LE(SAVEGAME_HEADER); + newSave->write(desc, 40); + newSave->writeByte(SAVEGAME_VERSION); + newSave->writeByte(NO_THUMBNAIL); + + // Date / time + tm curTime; + _system->getTimeAndDate(curTime); + + uint32 saveDate = (curTime.tm_mday & 0xFF) << 24 | ((curTime.tm_mon + 1) & 0xFF) << 16 | (curTime.tm_year + 1900) & 0xFFFF; + uint16 saveTime = (curTime.tm_hour & 0xFF) << 8 | (curTime.tm_min) & 0xFF; + + newSave->writeUint32BE(saveDate); + newSave->writeUint16BE(saveTime); + // TODO: played time + + newSave->write(saveData, dataSize); + + newSave->finalize(); + if (newSave->ioFailed()) + warning("Couldn't write to file '%s'. Device full?", newFileName); + delete newSave; + + // Delete old save + _saveFileMan->removeSavefile(oldFileName); + + // Cleanup + free(saveData); + saveData = NULL; + return true; +} + void Control::doRestore(void) { uint8 *bufPos = _restoreBuf; _objMan->loadLiveList((uint16*)bufPos); -- cgit v1.2.3