scummvm/engines/hugo/file.cpp
Paul Gilbert 9f175c4053 ENGINES: Cleanup of savegame filenames generation
This removes filename methods when it matched the Engine method.
Secondly, ensuring there was an overriden getSaveStateName method
for engines that didn't do the standard target.00x save filenames
2020-02-16 15:44:28 -08:00

610 lines
17 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.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "graphics/surface.h"
#include "graphics/thumbnail.h"
#include "gui/saveload.h"
#include "image/pcx.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/util.h"
#include "hugo/object.h"
#include "hugo/text.h"
#include "hugo/mouse.h"
namespace Hugo {
namespace {
static const char s_bootCypher[] = "Copyright 1992, David P Gray, Gray Design Associates";
static const int s_bootCypherLen = sizeof(s_bootCypher) - 1;
}
FileManager::FileManager(HugoEngine *vm) : _vm(vm) {
_hasReadHeader = false;
_firstUIFFl = true;
_UIFHeader->_size = 0;
_UIFHeader->_offset = 0;
_soundHdr->_size = 0;
_soundHdr->_offset = 0;
}
FileManager::~FileManager() {
}
/**
* Name scenery and objects picture databases
*/
const char *FileManager::getBootFilename() const {
return "HUGO.BSF";
}
const char *FileManager::getObjectFilename() const {
return "objects.dat";
}
const char *FileManager::getSceneryFilename() const {
return "scenery.dat";
}
const char *FileManager::getSoundFilename() const {
return "sounds.dat";
}
const char *FileManager::getStringFilename() const {
return "strings.dat";
}
const char *FileManager::getUifFilename() const {
return "uif.dat";
}
/**
* Read a pcx file of length len. Use supplied seqPtr and image_p or
* allocate space if NULL. Name used for errors. Returns address of seqPtr
* Set first TRUE to initialize b_index (i.e. not reading a sequential image in file).
*/
Seq *FileManager::readPCX(Common::SeekableReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name) {
debugC(1, kDebugFile, "readPCX(..., %s)", name);
// Allocate memory for Seq if 0
if (seqPtr == 0) {
if ((seqPtr = (Seq *)malloc(sizeof(Seq))) == 0)
error("Insufficient memory to run game.");
}
Image::PCXDecoder pcx;
if (!pcx.loadStream(f))
error("Error while reading PCX image");
const Graphics::Surface *pcxSurface = pcx.getSurface();
if (pcxSurface->format.bytesPerPixel != 1)
error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel);
// Find size of image data in 8-bit DIB format
// Note save of x2 - marks end of valid data before garbage
seqPtr->_lines = pcxSurface->h;
seqPtr->_x2 = seqPtr->_bytesPerLine8 = pcxSurface->w;
// Size of the image
uint16 size = pcxSurface->w * pcxSurface->h;
// Allocate memory for image data if NULL
if (imagePtr == 0)
imagePtr = (byte *)malloc((size_t) size);
assert(imagePtr);
seqPtr->_imagePtr = imagePtr;
for (uint16 y = 0; y < pcxSurface->h; y++)
memcpy(imagePtr + y * pcxSurface->w, pcxSurface->getBasePtr(0, y), pcxSurface->w);
return seqPtr;
}
/**
* Read object file of PCC images into object supplied
*/
void FileManager::readImage(const int objNum, Object *objPtr) {
debugC(1, kDebugFile, "readImage(%d, Object *objPtr)", objNum);
/**
* Structure of object file lookup entry
*/
struct objBlock_t {
uint32 objOffset;
uint32 objLength;
};
if (!objPtr->_seqNumb) // This object has no images
return;
if (_vm->isPacked()) {
_objectsArchive.seek((uint32)objNum * sizeof(objBlock_t), SEEK_SET);
objBlock_t objBlock; // Info on file within database
objBlock.objOffset = _objectsArchive.readUint32LE();
objBlock.objLength = _objectsArchive.readUint32LE();
_objectsArchive.seek(objBlock.objOffset, SEEK_SET);
} else {
Common::String buf;
buf = _vm->_picDir + Common::String(_vm->_text->getNoun(objPtr->_nounIndex, 0)) + ".PIX";
if (!_objectsArchive.open(buf)) {
buf = Common::String(_vm->_text->getNoun(objPtr->_nounIndex, 0)) + ".PIX";
if (!_objectsArchive.open(buf))
error("File not found: %s", buf.c_str());
}
}
bool firstImgFl = true; // Initializes pcx read function
Seq *seqPtr = 0; // Ptr to sequence structure
// Now read the images into an images list
for (int j = 0; j < objPtr->_seqNumb; j++) { // for each sequence
for (int k = 0; k < objPtr->_seqList[j]._imageNbr; k++) { // each image
if (k == 0) { // First image
// Read this image - allocate both seq and image memory
seqPtr = readPCX(_objectsArchive, 0, 0, firstImgFl, _vm->_text->getNoun(objPtr->_nounIndex, 0));
objPtr->_seqList[j]._seqPtr = seqPtr;
firstImgFl = false;
} else { // Subsequent image
// Read this image - allocate both seq and image memory
seqPtr->_nextSeqPtr = readPCX(_objectsArchive, 0, 0, firstImgFl, _vm->_text->getNoun(objPtr->_nounIndex, 0));
seqPtr = seqPtr->_nextSeqPtr;
}
// Compute the bounding box - x1, x2, y1, y2
// Note use of x2 - marks end of valid data in row
uint16 x2 = seqPtr->_x2;
seqPtr->_x1 = seqPtr->_x2;
seqPtr->_x2 = 0;
seqPtr->_y1 = seqPtr->_lines;
seqPtr->_y2 = 0;
ImagePtr dibPtr = seqPtr->_imagePtr;
for (int y = 0; y < seqPtr->_lines; y++, dibPtr += seqPtr->_bytesPerLine8 - x2) {
for (int x = 0; x < x2; x++) {
if (*dibPtr++) { // Some data found
if (x < seqPtr->_x1)
seqPtr->_x1 = x;
if (x > seqPtr->_x2)
seqPtr->_x2 = x;
if (y < seqPtr->_y1)
seqPtr->_y1 = y;
if (y > seqPtr->_y2)
seqPtr->_y2 = y;
}
}
}
}
assert(seqPtr);
seqPtr->_nextSeqPtr = objPtr->_seqList[j]._seqPtr; // loop linked list to head
}
// Set the current image sequence to first or last
switch (objPtr->_cycling) {
case kCycleInvisible: // (May become visible later)
case kCycleAlmostInvisible:
case kCycleNotCycling:
case kCycleForward:
objPtr->_currImagePtr = objPtr->_seqList[0]._seqPtr;
break;
case kCycleBackward:
objPtr->_currImagePtr = seqPtr;
break;
default:
warning("Unexpected cycling: %d", objPtr->_cycling);
}
if (!_vm->isPacked())
_objectsArchive.close();
}
/**
* Read sound (or music) file data. Call with SILENCE to free-up
* any allocated memory. Also returns size of data
*/
SoundPtr FileManager::getSound(const int16 sound, uint16 *size) {
debugC(1, kDebugFile, "getSound(%d)", sound);
// No more to do if SILENCE (called for cleanup purposes)
if (sound == _vm->_soundSilence)
return nullptr;
// Open sounds file
Common::File fp; // Handle to SOUND_FILE
if (!fp.open(getSoundFilename())) {
warning("Hugo Error: File not found %s", getSoundFilename());
return nullptr;
}
if (!_hasReadHeader) {
for (int i = 0; i < kMaxSounds; i++) {
_soundHdr[i]._size = fp.readUint16LE();
_soundHdr[i]._offset = fp.readUint32LE();
}
if (fp.err())
error("Wrong sound file format");
_hasReadHeader = true;
}
*size = _soundHdr[sound]._size;
if (*size == 0)
error("Wrong sound file format or missing sound %d", sound);
// Allocate memory for sound or music, if possible
SoundPtr soundPtr = (byte *)malloc(_soundHdr[sound]._size); // Ptr to sound data
assert(soundPtr);
// Seek to data and read it
fp.seek(_soundHdr[sound]._offset, SEEK_SET);
if (fp.read(soundPtr, _soundHdr[sound]._size) != _soundHdr[sound]._size)
error("Wrong sound file format");
fp.close();
return soundPtr;
}
/**
* Save game to supplied slot
*/
bool FileManager::saveGame(const int16 slot, const Common::String &descrip) {
debugC(1, kDebugFile, "saveGame(%d, %s)", slot, descrip.c_str());
int16 savegameId;
Common::String savegameDescription;
if (slot == -1) {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
savegameId = dialog->runModalWithCurrentTarget();
savegameDescription = dialog->getResultString();
delete dialog;
} else {
savegameId = slot;
if (!descrip.empty()) {
savegameDescription = descrip;
} else {
savegameDescription = Common::String::format("Quick save #%d", slot);
}
}
if (savegameId < 0) // dialog aborted
return false;
Common::String savegameFile = _vm->getSaveStateName(savegameId);
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
Common::OutSaveFile *out = saveMan->openForSaving(savegameFile);
if (!out) {
warning("Can't create file '%s', game not saved", savegameFile.c_str());
return false;
}
// Write version. We can't restore from obsolete versions
out->writeByte(kSavegameVersion);
if (savegameDescription == "") {
savegameDescription = "Untitled savegame";
}
out->writeSint16BE(savegameDescription.size() + 1);
out->write(savegameDescription.c_str(), savegameDescription.size() + 1);
Graphics::saveThumbnail(*out);
TimeDate curTime;
_vm->_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);
out->writeUint32BE(saveDate);
out->writeUint16BE(saveTime);
_vm->_object->saveObjects(out);
const Status &gameStatus = _vm->getGameStatus();
// Save whether hero image is swapped
out->writeByte(_vm->_heroImage);
// Save score
out->writeSint16BE(_vm->getScore());
// Save story mode
out->writeByte((gameStatus._storyModeFl) ? 1 : 0);
// Save jumpexit mode
out->writeByte((_vm->_mouse->getJumpExitFl()) ? 1 : 0);
// Save gameover status
out->writeByte((gameStatus._gameOverFl) ? 1 : 0);
// Save screen states
for (int i = 0; i < _vm->_numStates; i++)
out->writeByte(_vm->_screenStates[i]);
_vm->_scheduler->saveSchedulerData(out);
// Save palette table
_vm->_screen->savePal(out);
// Save maze status
out->writeByte((_vm->_maze._enabledFl) ? 1 : 0);
out->writeByte(_vm->_maze._size);
out->writeSint16BE(_vm->_maze._x1);
out->writeSint16BE(_vm->_maze._y1);
out->writeSint16BE(_vm->_maze._x2);
out->writeSint16BE(_vm->_maze._y2);
out->writeSint16BE(_vm->_maze._x3);
out->writeSint16BE(_vm->_maze._x4);
out->writeByte(_vm->_maze._firstScreenIndex);
out->writeByte((byte)_vm->getGameStatus()._viewState);
out->finalize();
delete out;
return true;
}
/**
* Restore game from supplied slot number
*/
bool FileManager::restoreGame(const int16 slot) {
debugC(1, kDebugFile, "restoreGame(%d)", slot);
int16 savegameId;
if (slot == -1) {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
savegameId = dialog->runModalWithCurrentTarget();
delete dialog;
} else {
savegameId = slot;
}
if (savegameId < 0) // dialog aborted
return false;
Common::String savegameFile = _vm->getSaveStateName(savegameId);
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
Common::InSaveFile *in = saveMan->openForLoading(savegameFile);
if (!in)
return false;
// Initialize new-game status
_vm->initStatus();
// Check version, can't restore from different versions
int saveVersion = in->readByte();
if (saveVersion != kSavegameVersion) {
warning("Savegame of incompatible version");
delete in;
return false;
}
// Skip over description
int32 saveGameNameSize = in->readSint16BE();
in->skip(saveGameNameSize);
Graphics::skipThumbnail(*in);
in->skip(6); // Skip date & time
// If hero image is currently swapped, swap it back before restore
if (_vm->_heroImage != kHeroIndex)
_vm->_object->swapImages(kHeroIndex, _vm->_heroImage);
_vm->_object->restoreObjects(in);
_vm->_heroImage = in->readByte();
// If hero swapped in saved game, swap it
byte heroImg = _vm->_heroImage;
if (heroImg != kHeroIndex)
_vm->_object->swapImages(kHeroIndex, _vm->_heroImage);
_vm->_heroImage = heroImg;
Status &gameStatus = _vm->getGameStatus();
int score = in->readSint16BE();
_vm->setScore(score);
gameStatus._storyModeFl = (in->readByte() == 1);
_vm->_mouse->setJumpExitFl(in->readByte() == 1);
gameStatus._gameOverFl = (in->readByte() == 1);
for (int i = 0; i < _vm->_numStates; i++)
_vm->_screenStates[i] = in->readByte();
_vm->_scheduler->restoreSchedulerData(in);
// Restore palette and change it if necessary
_vm->_screen->restorePal(in);
// Restore maze status
_vm->_maze._enabledFl = (in->readByte() == 1);
_vm->_maze._size = in->readByte();
_vm->_maze._x1 = in->readSint16BE();
_vm->_maze._y1 = in->readSint16BE();
_vm->_maze._x2 = in->readSint16BE();
_vm->_maze._y2 = in->readSint16BE();
_vm->_maze._x3 = in->readSint16BE();
_vm->_maze._x4 = in->readSint16BE();
_vm->_maze._firstScreenIndex = in->readByte();
_vm->_scheduler->restoreScreen(*_vm->_screenPtr);
if ((_vm->getGameStatus()._viewState = (Vstate) in->readByte()) != kViewPlay)
_vm->_screen->hideCursor();
delete in;
return true;
}
/**
* Reads boot file for program environment. Fatal error if not there or
* file checksum is bad. De-crypts structure while checking checksum
*/
void FileManager::readBootFile() {
debugC(1, kDebugFile, "readBootFile()");
Common::File ofp;
if (!ofp.open(getBootFilename())) {
if (_vm->_gameVariant == kGameVariantH1Dos) {
//TODO initialize properly _boot structure
warning("readBootFile - Skipping as H1 Dos may be a freeware");
memset(_vm->_boot._distrib, '\0', sizeof(_vm->_boot._distrib));
_vm->_boot._registered = kRegFreeware;
return;
} else if (_vm->getPlatform() == Common::kPlatformDOS) {
warning("readBootFile - Skipping as H2 and H3 Dos may be shareware");
memset(_vm->_boot._distrib, '\0', sizeof(_vm->_boot._distrib));
_vm->_boot._registered = kRegShareware;
return;
} else {
Utils::notifyBox(Common::String::format("Missing startup file '%s'", getBootFilename()));
_vm->getGameStatus()._doQuitFl = true;
return;
}
}
if (ofp.size() < (int32)sizeof(_vm->_boot)) {
Utils::notifyBox(Common::String::format("Corrupted startup file '%s'", getBootFilename()));
_vm->getGameStatus()._doQuitFl = true;
return;
}
_vm->_boot._checksum = ofp.readByte();
_vm->_boot._registered = ofp.readByte();
ofp.read(_vm->_boot._pbswitch, sizeof(_vm->_boot._pbswitch));
ofp.read(_vm->_boot._distrib, sizeof(_vm->_boot._distrib));
_vm->_boot._exitLen = ofp.readUint16LE();
ofp.close();
byte *p = (byte *)&_vm->_boot;
byte checksum = 0;
for (uint32 i = 0; i < sizeof(_vm->_boot); i++) {
checksum ^= p[i];
p[i] ^= s_bootCypher[i % s_bootCypherLen];
}
if (checksum) {
Utils::notifyBox(Common::String::format("Corrupted startup file '%s'", getBootFilename()));
_vm->getGameStatus()._doQuitFl = true;
}
}
/**
* Returns address of uif_hdr[id], reading it in if first call
* This file contains, between others, the bitmaps of the fonts used in the application
* UIF means User interface database (Windows Only)
*/
UifHdr *FileManager::getUIFHeader(const Uif id) {
debugC(1, kDebugFile, "getUIFHeader(%d)", id);
// Initialize offset lookup if not read yet
if (_firstUIFFl) {
_firstUIFFl = false;
// Open unbuffered to do far read
Common::File ip; // Image data file
if (!ip.open(getUifFilename()))
error("File not found: %s", getUifFilename());
if (ip.size() < (int32)sizeof(_UIFHeader))
error("Wrong UIF file format");
for (int i = 0; i < kMaxUifs; ++i) {
_UIFHeader[i]._size = ip.readUint16LE();
_UIFHeader[i]._offset = ip.readUint32LE();
}
ip.close();
}
return &_UIFHeader[id];
}
/**
* Read uif item into supplied buffer.
*/
void FileManager::readUIFItem(const int16 id, byte *buf) {
debugC(1, kDebugFile, "readUIFItem(%d, ...)", id);
// Open uif file to read data
Common::File ip; // UIF_FILE handle
if (!ip.open(getUifFilename()))
error("File not found: %s", getUifFilename());
// Seek to data
UifHdr *_UIFHeaderPtr = getUIFHeader((Uif)id);
ip.seek(_UIFHeaderPtr->_offset, SEEK_SET);
// We support pcx images and straight data
Seq *dummySeq; // Dummy Seq for image data
switch (id) {
case UIF_IMAGES: // Read uif images file
dummySeq = readPCX(ip, 0, buf, true, getUifFilename());
free(dummySeq);
break;
default: // Read file data into supplied array
if (ip.read(buf, _UIFHeaderPtr->_size) != _UIFHeaderPtr->_size)
error("Wrong UIF file format");
break;
}
ip.close();
}
/**
* Read the uif image file (inventory icons)
*/
void FileManager::readUIFImages() {
debugC(1, kDebugFile, "readUIFImages()");
readUIFItem(UIF_IMAGES, _vm->_screen->getGUIBuffer()); // Read all uif images
}
} // End of namespace Hugo