mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-01 23:18:44 +00:00
437 lines
8.9 KiB
C++
437 lines
8.9 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "common/endian.h"
|
|
#include "common/memstream.h"
|
|
#include "common/savefile.h"
|
|
|
|
#include "gob/gob.h"
|
|
#include "gob/save/saveconverter.h"
|
|
#include "gob/save/savefile.h"
|
|
#include "gob/save/savehandler.h"
|
|
|
|
namespace Gob {
|
|
|
|
SaveConverter::SaveConverter(GobEngine *vm, const Common::String &fileName)
|
|
: _vm(vm), _fileName(fileName) {
|
|
|
|
_data = 0;
|
|
_stream = 0;
|
|
}
|
|
|
|
SaveConverter::~SaveConverter() {
|
|
delete _stream;
|
|
delete[] _data;
|
|
}
|
|
|
|
void SaveConverter::clear() {
|
|
delete[] _data;
|
|
delete _stream;
|
|
|
|
_data = 0;
|
|
_stream = 0;
|
|
}
|
|
|
|
void SaveConverter::setFileName(const Common::String &fileName) {
|
|
clear();
|
|
_fileName = fileName;
|
|
}
|
|
|
|
Common::InSaveFile *SaveConverter::openSave() const {
|
|
if (_fileName.empty())
|
|
return 0;
|
|
|
|
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
|
|
return saveMan->openForLoading(_fileName);
|
|
}
|
|
|
|
void SaveConverter::displayWarning() const {
|
|
warning("Old save format detected, trying to convert. If this does not work, your "
|
|
"save is broken and can't be used anymore. Sorry for the inconvenience");
|
|
}
|
|
|
|
char *SaveConverter::getDescription(const Common::String &fileName) {
|
|
setFileName(fileName);
|
|
return getDescription();
|
|
}
|
|
|
|
char *SaveConverter::getDescription() const {
|
|
Common::InSaveFile *save;
|
|
|
|
// Test if it's an old savd
|
|
if (!isOldSave(&save) || !save)
|
|
return 0;
|
|
|
|
char *desc = getDescription(*save);
|
|
|
|
delete save;
|
|
return desc;
|
|
}
|
|
|
|
uint32 SaveConverter::getActualSize(Common::InSaveFile **save) const {
|
|
Common::InSaveFile *saveFile = openSave();
|
|
|
|
if (!saveFile)
|
|
return false;
|
|
|
|
// Is it a valid new save?
|
|
if (SaveContainer::isSave(*saveFile)) {
|
|
delete saveFile;
|
|
return false;
|
|
}
|
|
|
|
int32 saveSize = saveFile->size();
|
|
|
|
if (saveSize <= 0) {
|
|
delete saveFile;
|
|
return 0;
|
|
}
|
|
|
|
if (save)
|
|
*save = saveFile;
|
|
else
|
|
delete saveFile;
|
|
|
|
return saveSize;
|
|
}
|
|
|
|
bool SaveConverter::swapDataEndian(byte *data, const byte *sizes, uint32 count) {
|
|
if (!data || !sizes || (count == 0))
|
|
return false;
|
|
|
|
while (count-- > 0) {
|
|
if (*sizes == 3) // 32bit value (3 additional bytes)
|
|
WRITE_UINT32(data, SWAP_BYTES_32(READ_UINT32(data)));
|
|
else if (*sizes == 1) // 16bit value (1 additional byte)
|
|
WRITE_UINT16(data, SWAP_BYTES_16(READ_UINT16(data)));
|
|
else if (*sizes != 0) // else, it has to be an 8bit value
|
|
return false;
|
|
|
|
count -= *sizes;
|
|
data += *sizes + 1;
|
|
sizes += *sizes + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
SavePartInfo *SaveConverter::readInfo(Common::SeekableReadStream &stream,
|
|
uint32 descLength, bool hasSizes) const {
|
|
|
|
uint32 varSize = SaveHandler::getVarSize(_vm);
|
|
if (varSize == 0)
|
|
return 0;
|
|
|
|
char *desc = getDescription(stream);
|
|
if (!desc)
|
|
return 0;
|
|
|
|
// If it has sizes, skip them
|
|
if (hasSizes)
|
|
if (!stream.skip(descLength)) {
|
|
delete[] desc;
|
|
return 0;
|
|
}
|
|
|
|
SavePartInfo *info = new SavePartInfo(descLength, (uint32) _vm->getGameType(),
|
|
0, _vm->getEndianness(), varSize);
|
|
|
|
info->setDesc(desc);
|
|
|
|
delete[] desc;
|
|
|
|
return info;
|
|
}
|
|
|
|
byte *SaveConverter::readData(Common::SeekableReadStream &stream,
|
|
uint32 count, bool endian) const {
|
|
|
|
byte *data = new byte[count];
|
|
|
|
// Read variable data
|
|
if (stream.read(data, count) != count) {
|
|
delete[] data;
|
|
return 0;
|
|
}
|
|
|
|
/* Check the endianness. The old save data was always written
|
|
* as little endian, so we might need to swap the bytes. */
|
|
|
|
if (endian && (_vm->getEndianness() == kEndiannessBE)) {
|
|
// Big endian => swapping needed
|
|
|
|
// Read variable sizes
|
|
byte *sizes = new byte[count];
|
|
if (stream.read(sizes, count) != count) {
|
|
delete[] data;
|
|
delete[] sizes;
|
|
return 0;
|
|
}
|
|
|
|
// Swap bytes
|
|
if (!swapDataEndian(data, sizes, count)) {
|
|
delete[] data;
|
|
delete[] sizes;
|
|
return 0;
|
|
}
|
|
|
|
delete[] sizes;
|
|
|
|
} else {
|
|
// Little endian => just skip the sizes part
|
|
|
|
if (!stream.skip(count)) {
|
|
delete[] data;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
SavePartVars *SaveConverter::readVars(Common::SeekableReadStream &stream,
|
|
uint32 count, bool endian) const {
|
|
|
|
byte *data = readData(stream, count, endian);
|
|
if (!data)
|
|
return 0;
|
|
|
|
SavePartVars *vars = new SavePartVars(_vm, count);
|
|
|
|
// Read variables into part
|
|
if (!vars->readFromRaw(data, count)) {
|
|
delete[] data;
|
|
delete vars;
|
|
return 0;
|
|
}
|
|
|
|
delete[] data;
|
|
return vars;
|
|
}
|
|
|
|
SavePartMem *SaveConverter::readMem(Common::SeekableReadStream &stream,
|
|
uint32 count, bool endian) const {
|
|
|
|
byte *data = readData(stream, count, endian);
|
|
if (!data)
|
|
return 0;
|
|
|
|
SavePartMem *mem = new SavePartMem(count);
|
|
|
|
// Read mem into part
|
|
if (!mem->readFrom(data, 0, count)) {
|
|
delete[] data;
|
|
delete mem;
|
|
return 0;
|
|
}
|
|
|
|
delete[] data;
|
|
return mem;
|
|
}
|
|
|
|
SavePartSprite *SaveConverter::readSprite(Common::SeekableReadStream &stream,
|
|
uint32 width, uint32 height, bool palette) const {
|
|
|
|
assert((width > 0) && (height > 0));
|
|
|
|
uint32 spriteSize = width * height;
|
|
|
|
byte pal[768];
|
|
if (palette)
|
|
if (stream.read(pal, 768) != 768)
|
|
return 0;
|
|
|
|
byte *data = new byte[spriteSize];
|
|
|
|
// Read variable data
|
|
if (stream.read(data, spriteSize) != spriteSize) {
|
|
delete[] data;
|
|
return 0;
|
|
}
|
|
|
|
SavePartSprite *sprite = new SavePartSprite(width, height);
|
|
|
|
if (!sprite->readSpriteRaw(data, spriteSize)) {
|
|
delete[] data;
|
|
delete sprite;
|
|
return 0;
|
|
}
|
|
|
|
delete[] data;
|
|
|
|
if (palette)
|
|
if (!sprite->readPalette(pal))
|
|
return 0;
|
|
|
|
return sprite;
|
|
}
|
|
|
|
bool SaveConverter::createStream(SaveWriter &writer) {
|
|
// Allocate memory for the internal new save data
|
|
uint32 contSize = writer.getSize();
|
|
_data = new byte[contSize];
|
|
|
|
// Save the newly created new save data
|
|
Common::MemoryWriteStream writeStream(_data, contSize);
|
|
if (!writer.save(writeStream))
|
|
return false;
|
|
|
|
// Create a reading stream upon that new save data
|
|
_stream = new Common::MemoryReadStream(_data, contSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Stream functions. If the new save data stream is available, redirect the stream
|
|
* operations to that stream. Normal stream error behavior if not. */
|
|
|
|
bool SaveConverter::err() const {
|
|
if (!_data || !_stream)
|
|
return true;
|
|
|
|
return _stream->err();
|
|
}
|
|
|
|
void SaveConverter::clearErr() {
|
|
if (!_data || !_stream)
|
|
return;
|
|
|
|
_stream->clearErr();
|
|
}
|
|
|
|
bool SaveConverter::eos() const {
|
|
if (!_data || !_stream)
|
|
return true;
|
|
|
|
return _stream->eos();
|
|
}
|
|
|
|
uint32 SaveConverter::read(void *dataPtr, uint32 dataSize) {
|
|
if (!_data || !_stream)
|
|
return 0;
|
|
|
|
return _stream->read(dataPtr, dataSize);
|
|
}
|
|
|
|
int32 SaveConverter::pos() const {
|
|
if (!_data || !_stream)
|
|
return -1;
|
|
|
|
return _stream->pos();
|
|
}
|
|
|
|
int32 SaveConverter::size() const {
|
|
if (!_data || !_stream)
|
|
return -1;
|
|
|
|
return _stream->size();
|
|
}
|
|
|
|
bool SaveConverter::seek(int32 offset, int whence) {
|
|
if (!_data || !_stream)
|
|
return false;
|
|
|
|
return _stream->seek(offset, whence);
|
|
}
|
|
|
|
|
|
SaveConverter_Notes::SaveConverter_Notes(GobEngine *vm, uint32 notesSize,
|
|
const Common::String &fileName) : SaveConverter(vm, fileName) {
|
|
|
|
_size = notesSize;
|
|
}
|
|
|
|
SaveConverter_Notes::~SaveConverter_Notes() {
|
|
}
|
|
|
|
int SaveConverter_Notes::isOldSave(Common::InSaveFile **save) const {
|
|
if (_size == 0)
|
|
return 0;
|
|
|
|
uint32 saveSize = getActualSize(save);
|
|
if (saveSize == 0)
|
|
return 0;
|
|
|
|
// The size of the old save always follows that rule
|
|
if (saveSize == (_size * 2))
|
|
return 1;
|
|
|
|
// Not an old save, clean up
|
|
if (save) {
|
|
delete *save;
|
|
*save = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *SaveConverter_Notes::getDescription(Common::SeekableReadStream &save) const {
|
|
return 0;
|
|
}
|
|
|
|
bool SaveConverter_Notes::loadFail(SavePartVars *vars, Common::InSaveFile *save) {
|
|
delete vars;
|
|
delete save;
|
|
|
|
clear();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Loads the old save by constructing a new save containing the old save's data
|
|
bool SaveConverter_Notes::load() {
|
|
if (_size == 0)
|
|
return false;
|
|
|
|
Common::InSaveFile *save;
|
|
|
|
// Test if it's an old savd
|
|
if (!isOldSave(&save) || !save)
|
|
return false;
|
|
|
|
displayWarning();
|
|
|
|
SaveWriter writer(1, 0);
|
|
|
|
SavePartVars *vars = readVars(*save, _size, false);
|
|
if (!vars)
|
|
return loadFail(0, save);
|
|
|
|
// We don't need the save anymore
|
|
delete save;
|
|
|
|
// Write all parts
|
|
if (!writer.writePart(0, vars))
|
|
return loadFail(0, 0);
|
|
|
|
// We don't need this anymore
|
|
delete vars;
|
|
|
|
// Create the final read stream
|
|
if (!createStream(writer))
|
|
return loadFail(0, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
} // End of namespace Gob
|