aboutsummaryrefslogtreecommitdiff
path: root/engines/drascula/saveload.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/drascula/saveload.cpp')
-rw-r--r--engines/drascula/saveload.cpp529
1 files changed, 352 insertions, 177 deletions
diff --git a/engines/drascula/saveload.cpp b/engines/drascula/saveload.cpp
index 35e3821dc4..ba4148fb76 100644
--- a/engines/drascula/saveload.cpp
+++ b/engines/drascula/saveload.cpp
@@ -21,218 +21,270 @@
*/
#include "common/textconsole.h"
+#include "common/translation.h"
+
+#include "engines/savestate.h"
+#include "graphics/thumbnail.h"
+#include "gui/message.h"
+#include "gui/saveload.h"
#include "drascula/drascula.h"
namespace Drascula {
-/**
- * Loads the save names from the EPA index file.
- *
- * TODO: We should move the save names in their respective save files and get
- * rid of this completely. A good example is the sword1 engine, which also used
- * to have an index file for its saves, that has been removed.
- * sword1 contains code that converts the old index-based saves into the new
- * format without the index file, so we could apply this idea to drascula as
- * well.
- */
-void DrasculaEngine::loadSaveNames() {
- Common::InSaveFile *sav;
- Common::String fileEpa = Common::String::format("%s.epa", _targetName.c_str());
-
- // Create and initialize the index file, if it doesn't exist
- if (!(sav = _saveFileMan->openForLoading(fileEpa))) {
- Common::OutSaveFile *epa;
- if (!(epa = _saveFileMan->openForSaving(fileEpa)))
- error("Can't open %s file", fileEpa.c_str());
- for (int n = 0; n < NUM_SAVES; n++)
- epa->writeString("*\n");
- epa->finalize();
- delete epa;
- if (!(sav = _saveFileMan->openForLoading(fileEpa))) {
- error("Can't open %s file", fileEpa.c_str());
- }
+#define MAGIC_HEADER 0xD6A55A57 // (D)rascula (GA)me (S)cummVM (SA)ve (ST)ate
+#define SAVEGAME_VERSION 1
+
+void DrasculaEngine::checkForOldSaveGames() {
+ Common::String indexFileName = Common::String::format("%s.epa", _targetName.c_str());
+ Common::InSaveFile *indexFile = _saveFileMan->openForLoading(indexFileName);
+
+ // Check for the existence of an old index file
+ if (!indexFile) {
+ delete indexFile;
+ return;
}
- // Load the index file
- for (int n = 0; n < NUM_SAVES; n++) {
- strncpy(_saveNames[n], sav->readLine().c_str(), 23);
- _saveNames[n][22] = '\0'; // make sure the savegame name is 0-terminated
+ GUI::MessageDialog dialog0(
+ _("ScummVM found that you have old savefiles for Drascula 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)
+ return;
+
+ // Convert every save slot we find in the index file to the new format
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::String pattern = Common::String::format("%s??", _targetName.c_str());
+
+ // Get list of savefiles for target game
+ Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
+ Common::Array<int> slots;
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the last 2 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 2);
+
+ // Ensure save slot is within valid range
+ if (slotNum >= 1 && slotNum <= 10) {
+ slots.push_back(slotNum);
+ }
}
- delete sav;
-}
-/**
- * Saves the save names into the EPA index file.
- *
- * TODO: We should move the save names in their respective save files and get
- * rid of this completely. A good example is the sword1 engine, which also used
- * to have an index file for its saves, that has been removed.
- * sword1 contains code that converts the old index-based saves into the new
- * format without the index file, so we could apply this idea to drascula as
- * well.
- */
-void DrasculaEngine::saveSaveNames() {
- Common::OutSaveFile *tsav;
- Common::String fileEpa = Common::String::format("%s.epa", _targetName.c_str());
+ // Sort save slot ids
+ Common::sort<int>(slots.begin(), slots.end());
- if (!(tsav = _saveFileMan->openForSaving(fileEpa))) {
- error("Can't open %s file", fileEpa.c_str());
- }
- for (int n = 0; n < NUM_SAVES; n++) {
- tsav->writeString(_saveNames[n]);
- tsav->writeString("\n");
+ // Get savegame names from index
+ Common::String saveDesc;
+
+ int line = 1;
+ for (uint i = 0; i < slots.size(); i++) {
+ // Ignore lines corresponding to unused saveslots
+ for (; line < slots[i]; line++)
+ indexFile->readLine();
+
+ // Copy the name in the line corresponding to the save slot
+ saveDesc = indexFile->readLine();
+
+ // Handle cases where the save directory and save index are detectably out of sync
+ if (saveDesc == "*")
+ saveDesc = "No name specified.";
+
+ // Increment line number to keep it in sync with slot number
+ line++;
+
+ // Convert savegame
+ convertSaveGame(slots[i], saveDesc);
}
- tsav->finalize();
- delete tsav;
-}
-bool DrasculaEngine::saveLoadScreen() {
- Common::String file;
- int n, n2, num_sav = 0, y = 27;
+ delete indexFile;
- clearRoom();
+ // Remove index file
+ _saveFileMan->removeSavefile(indexFileName);
+}
- loadSaveNames();
+SaveStateDescriptor loadMetaData(Common::ReadStream *s, int slot, bool setPlayTime) {
+ uint32 sig = s->readUint32BE();
+ byte version = s->readByte();
- loadPic("savescr.alg", bgSurface, HALF_PAL);
+ SaveStateDescriptor desc(-1, ""); // init to an invalid save slot
- color_abc(kColorLightGreen);
+ if (sig != MAGIC_HEADER || version > SAVEGAME_VERSION)
+ return desc;
- select[0] = 0;
+ // Save is valid, set its slot number
+ desc.setSaveSlot(slot);
- _system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
- setCursor(kCursorCrosshair);
+ Common::String name;
+ byte size = s->readByte();
+ for (int i = 0; i < size; ++i)
+ name += s->readByte();
+ desc.setDescription(name);
- while (!shouldQuit()) {
- y = 27;
- copyBackground();
- for (n = 0; n < NUM_SAVES; n++) {
- print_abc(_saveNames[n], 116, y);
- y = y + 9;
- }
- print_abc(select, 117, 15);
- updateScreen();
- y = 27;
+ uint32 saveDate = s->readUint32LE();
+ int day = (saveDate >> 24) & 0xFF;
+ int month = (saveDate >> 16) & 0xFF;
+ int year = saveDate & 0xFFFF;
+ desc.setSaveDate(year, month, day);
- updateEvents();
+ uint16 saveTime = s->readUint16LE();
+ int hour = (saveTime >> 8) & 0xFF;
+ int minutes = saveTime & 0xFF;
+ desc.setSaveTime(hour, minutes);
- if (leftMouseButton == 1) {
- delay(50);
- for (n = 0; n < NUM_SAVES; n++) {
- if (mouseX > 115 && mouseY > y + (9 * n) && mouseX < 115 + 175 && mouseY < y + 10 + (9 * n)) {
- strcpy(select, _saveNames[n]);
-
- if (strcmp(select, "*") != 0)
- selectionMade = 1;
- else {
- enterName();
- strcpy(_saveNames[n], select);
- if (selectionMade == 1) {
- file = Common::String::format("%s%02d", _targetName.c_str(), n + 1);
- saveGame(file.c_str());
- saveSaveNames();
- }
- }
+ uint32 playTime = s->readUint32LE();
+ desc.setPlayTime(playTime * 1000);
+ if (setPlayTime)
+ g_engine->setTotalPlayTime(playTime * 1000);
- print_abc(select, 117, 15);
- y = 27;
- for (n2 = 0; n2 < NUM_SAVES; n2++) {
- print_abc(_saveNames[n2], 116, y);
- y = y + 9;
- }
- if (selectionMade == 1) {
- file = Common::String::format("%s%02d", _targetName.c_str(), n + 1);
- }
- num_sav = n;
- }
- }
+ return desc;
+}
- if (mouseX > 117 && mouseY > 15 && mouseX < 295 && mouseY < 24 && selectionMade == 1) {
- enterName();
- strcpy(_saveNames[num_sav], select);
- print_abc(select, 117, 15);
- y = 27;
- for (n2 = 0; n2 < NUM_SAVES; n2++) {
- print_abc(_saveNames[n2], 116, y);
- y = y + 9;
- }
+void saveMetaData(Common::WriteStream *s, Common::String &desc) {
+ TimeDate curTime;
+ g_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);
+ uint32 playTime = g_engine->getTotalPlayTime() / 1000;
+
+ s->writeUint32BE(MAGIC_HEADER);
+ s->writeByte(SAVEGAME_VERSION);
+ s->writeByte(desc.size());
+ s->writeString(desc);
+ s->writeUint32LE(saveDate);
+ s->writeUint16LE(saveTime);
+ s->writeUint32LE(playTime);
+}
- if (selectionMade == 1) {
- file = Common::String::format("%s%02d", _targetName.c_str(), n + 1);
- saveGame(file.c_str());
- saveSaveNames();
- }
- }
+void DrasculaEngine::convertSaveGame(int slot, Common::String &desc) {
+ Common::String oldFileName = Common::String::format("%s%02d", _targetName.c_str(), slot);
+ Common::String newFileName = Common::String::format("%s.%03d", _targetName.c_str(), slot);
+ Common::InSaveFile *oldFile = _saveFileMan->openForLoading(oldFileName);
+ if (!oldFile)
+ error("Can't open %s", oldFileName.c_str());
+ Common::OutSaveFile *newFile = _saveFileMan->openForSaving(newFileName);
+ if (!newFile)
+ error("Can't open %s", newFileName.c_str());
+
+ // Read data from old file
+ int32 dataSize = oldFile->size();
+ byte *buffer = new byte[dataSize];
+ oldFile->read(buffer, dataSize);
+
+ // First, write the appropriate meta data in the new file
+ saveMetaData(newFile, desc);
+ Graphics::saveThumbnail(*newFile); // basically, at this point this will capture a black screen
+
+ // And then attach the actual save data
+ newFile->write(buffer, dataSize);
+ newFile->finalize();
+ if (newFile->err())
+ warning("Can't write file '%s'. (Disk full?)", newFileName.c_str());
+
+ delete[] buffer;
+ delete newFile;
+ delete oldFile;
+
+ // Remove old save file
+ _saveFileMan->removeSavefile(oldFileName);
+}
- if (mouseX > 125 && mouseY > 123 && mouseX < 199 && mouseY < 149 && selectionMade == 1) {
- if (!loadGame(file.c_str())) {
- _system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
- return false;
- }
- break;
- } else if (mouseX > 208 && mouseY > 123 && mouseX < 282 && mouseY < 149 && selectionMade == 1) {
- saveGame(file.c_str());
- saveSaveNames();
- } else if (mouseX > 168 && mouseY > 154 && mouseX < 242 && mouseY < 180)
- break;
- else if (selectionMade == 0) {
- print_abc("Please select a slot", 117, 15);
- }
- updateScreen();
- delay(200);
+/**
+ * Loads the first 10 save names, to be used in Drascula's save/load screen
+ */
+void DrasculaEngine::loadSaveNames() {
+ Common::String saveFileName;
+ Common::InSaveFile *in;
+
+ for (int n = 0; n < NUM_SAVES; n++) {
+ saveFileName = Common::String::format("%s.%03d", _targetName.c_str(), n + 1);
+ if ((in = _saveFileMan->openForLoading(saveFileName))) {
+ SaveStateDescriptor desc = loadMetaData(in, n + 1, false);
+ _saveNames[n] = desc.getDescription();
+ delete in;
}
- y = 26;
+ }
+}
+
+void DrasculaEngine::saveGame(int slot, Common::String &desc) {
+ Common::OutSaveFile *out;
+ int l;
- delay(5);
+ Common::String saveFileName = Common::String::format("%s.%03d", _targetName.c_str(), slot);
+ if (!(out = _saveFileMan->openForSaving(saveFileName))) {
+ error("Unable to open the file");
}
- selectVerb(kVerbNone);
+ saveMetaData(out, desc);
+ Graphics::saveThumbnail(*out);
- clearRoom();
- loadPic(roomNumber, bgSurface, HALF_PAL);
- selectionMade = 0;
+ // Actual save data follows
+ out->writeSint32LE(currentChapter);
+ out->write(currentData, 20);
+ out->writeSint32LE(curX);
+ out->writeSint32LE(curY);
+ out->writeSint32LE(trackProtagonist);
- _system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+ for (l = 1; l < ARRAYSIZE(inventoryObjects); l++) {
+ out->writeSint32LE(inventoryObjects[l]);
+ }
- return true;
+ for (l = 0; l < NUM_FLAGS; l++) {
+ out->writeSint32LE(flags[l]);
+ }
+
+ out->writeSint32LE(takeObject);
+ out->writeSint32LE(pickedObject);
+
+ out->finalize();
+ if (out->err())
+ warning("Can't write file '%s'. (Disk full?)", saveFileName.c_str());
+
+ delete out;
}
-bool DrasculaEngine::loadGame(const char *gameName) {
+bool DrasculaEngine::loadGame(int slot) {
int l, savedChapter, roomNum = 0;
- Common::InSaveFile *sav;
+ Common::InSaveFile *in;
previousMusic = roomMusic;
_menuScreen = false;
if (currentChapter != 1)
clearRoom();
- if (!(sav = _saveFileMan->openForLoading(gameName))) {
- error("missing savegame file");
+ Common::String saveFileName = Common::String::format("%s.%03d", _targetName.c_str(), slot);
+ if (!(in = _saveFileMan->openForLoading(saveFileName))) {
+ error("missing savegame file %s", saveFileName.c_str());
}
- savedChapter = sav->readSint32LE();
+ loadMetaData(in, slot, true);
+ Graphics::skipThumbnail(*in);
+
+ savedChapter = in->readSint32LE();
if (savedChapter != currentChapter) {
- strcpy(saveName, gameName);
+ _currentSaveSlot = slot;
currentChapter = savedChapter - 1;
loadedDifferentChapter = 1;
+ delete in;
return false;
}
- sav->read(currentData, 20);
- curX = sav->readSint32LE();
- curY = sav->readSint32LE();
- trackProtagonist = sav->readSint32LE();
+
+ in->read(currentData, 20);
+ curX = in->readSint32LE();
+ curY = in->readSint32LE();
+ trackProtagonist = in->readSint32LE();
for (l = 1; l < ARRAYSIZE(inventoryObjects); l++) {
- inventoryObjects[l] = sav->readSint32LE();
+ inventoryObjects[l] = in->readSint32LE();
}
for (l = 0; l < NUM_FLAGS; l++) {
- flags[l] = sav->readSint32LE();
+ flags[l] = in->readSint32LE();
}
- takeObject = sav->readSint32LE();
- pickedObject = sav->readSint32LE();
+ takeObject = in->readSint32LE();
+ pickedObject = in->readSint32LE();
loadedDifferentChapter = 0;
if (!sscanf(currentData, "%d.ald", &roomNum)) {
error("Bad save format");
@@ -243,35 +295,158 @@ bool DrasculaEngine::loadGame(const char *gameName) {
return true;
}
-void DrasculaEngine::saveGame(const char *gameName) {
- Common::OutSaveFile *out;
- int l;
+Common::String DrasculaEngine::enterName(Common::String &selectedName) {
+ Common::KeyCode key;
+ Common::String inputLine = selectedName;
- if (!(out = _saveFileMan->openForSaving(gameName))) {
- error("Unable to open the file");
+ flushKeyBuffer();
+ _system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+
+ while (!shouldQuit()) {
+ copyBackground(115, 14, 115, 14, 176, 9, bgSurface, screenSurface);
+ print_abc((inputLine + "-").c_str(), 117, 15);
+ updateScreen();
+
+ key = getScan();
+
+ if (key != 0) {
+ if (key >= 0 && key <= 0xFF && isAlpha(key)) {
+ inputLine += tolower(key);
+ } else if ((key >= Common::KEYCODE_0 && key <= Common::KEYCODE_9) || key == Common::KEYCODE_SPACE) {
+ inputLine += key;
+ } else if (key == Common::KEYCODE_ESCAPE) {
+ inputLine.clear();
+ break;
+ } else if (key == Common::KEYCODE_RETURN) {
+ break;
+ } else if (key == Common::KEYCODE_BACKSPACE) {
+ inputLine.deleteLastChar();
+ }
+ }
}
- out->writeSint32LE(currentChapter);
- out->write(currentData, 20);
- out->writeSint32LE(curX);
- out->writeSint32LE(curY);
- out->writeSint32LE(trackProtagonist);
- for (l = 1; l < ARRAYSIZE(inventoryObjects); l++) {
- out->writeSint32LE(inventoryObjects[l]);
+ _system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+ return inputLine;
+}
+
+bool DrasculaEngine::scummVMSaveLoadDialog(bool isSave) {
+ GUI::SaveLoadChooser *dialog;
+ Common::String desc;
+ int slot;
+
+ if (isSave) {
+ dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
+
+ slot = dialog->runModalWithCurrentTarget();
+ desc = dialog->getResultString();
+
+ if (desc.empty()) {
+ // create our own description for the saved game, the user didnt enter it
+ desc = dialog->createDefaultSaveDescription(slot);
+ }
+
+ if (desc.size() > 28)
+ desc = Common::String(desc.c_str(), 28);
+ } else {
+ dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
+ slot = dialog->runModalWithCurrentTarget();
}
- for (l = 0; l < NUM_FLAGS; l++) {
- out->writeSint32LE(flags[l]);
+ delete dialog;
+
+ if (slot < 0)
+ return true;
+
+ if (isSave) {
+ saveGame(slot, desc);
+ return true;
+ } else {
+ return loadGame(slot);
}
+}
- out->writeSint32LE(takeObject);
- out->writeSint32LE(pickedObject);
+bool DrasculaEngine::saveLoadScreen() {
+ int n, selectedSlot = 0;
+ Common::String selectedName;
- out->finalize();
- if (out->err())
- warning("Can't write file '%s'. (Disk full?)", gameName);
+ clearRoom();
+ loadPic("savescr.alg", bgSurface, HALF_PAL);
+ color_abc(kColorLightGreen);
+ setCursor(kCursorCrosshair);
+ loadSaveNames();
- delete out;
+ while (!shouldQuit()) {
+ copyBackground();
+ for (n = 0; n < NUM_SAVES; n++) {
+ print_abc(_saveNames[n].c_str(), 116, 27 + 9 * n);
+ }
+ print_abc(selectedName.c_str(), 117, 15);
+
+ updateScreen();
+ updateEvents();
+
+ if (leftMouseButton == 1) {
+ // Check if the user has clicked on a save slot
+ for (n = 0; n < NUM_SAVES; n++) {
+ if (mouseX > 115 && mouseY > 27 + (9 * n) && mouseX < 115 + 175 && mouseY < 27 + 10 + (9 * n)) {
+ selectedSlot = n;
+ selectedName = _saveNames[selectedSlot];
+ if (selectedName.empty()) {
+ selectedName = enterName(selectedName);
+ if (!selectedName.empty())
+ _saveNames[selectedSlot] = selectedName; // update save name
+ }
+ break;
+ }
+ }
+
+ // Check if the user has clicked in the text area above the save slots
+ if (mouseX > 117 && mouseY > 15 && mouseX < 295 && mouseY < 24 && !selectedName.empty()) {
+ selectedName = enterName(selectedName);
+ if (!selectedName.empty())
+ _saveNames[selectedSlot] = selectedName; // update save name
+ }
+
+ // Check if the user has clicked a button
+ if (mouseX > 208 && mouseY > 123 && mouseX < 282 && mouseY < 149) {
+ // "Save" button
+ if (selectedName.empty()) {
+ print_abc("Please select a slot", 117, 15);
+ updateScreen();
+ delay(200);
+ } else {
+ selectVerb(kVerbNone);
+ clearRoom();
+ loadPic(roomNumber, bgSurface, HALF_PAL);
+ updateRoom();
+ updateScreen();
+
+ saveGame(selectedSlot + 1, _saveNames[selectedSlot]);
+ return true;
+ }
+ } else if (mouseX > 125 && mouseY > 123 && mouseX < 199 && mouseY < 149) {
+ // "Load" button
+ if (selectedName.empty()) {
+ print_abc("Please select a slot", 117, 15);
+ updateScreen();
+ delay(200);
+ } else {
+ return loadGame(selectedSlot + 1);
+ }
+ } else if (mouseX > 168 && mouseY > 154 && mouseX < 242 && mouseY < 180) {
+ // "Play" button
+ break;
+ }
+ } // if (leftMouseButton == 1)
+
+ leftMouseButton = 0;
+ delay(10);
+ }
+
+ selectVerb(kVerbNone);
+ clearRoom();
+ loadPic(roomNumber, bgSurface, HALF_PAL);
+ return true;
}
} // End of namespace Drascula