bool PersistenceService::saveGame(uint slotID, const Common::String &screenshotFilename) {
// FIXME: This code is a hack which bypasses the savefile API,
// and should eventually be removed.
// Überprüfen, ob die Slot-ID zulässig ist.
if (slotID >= SLOT_COUNT) {
error("Tried to save to an invalid slot (%d). Only slot ids form 0 to %d are allowed.", slotID, SLOT_COUNT - 1);
return false;
}
// Dateinamen erzeugen.
Common::String filename = generateSavegameFilename(slotID);
// Spielstanddatei öffnen und die Headerdaten schreiben.
Common::SaveFileManager *sfm = g_system->getSavefileManager();
Common::OutSaveFile *file = sfm->openForSaving(filename);
file->writeString(FILE_MARKER);
file->writeByte(0);
file->writeString(VERSIONID);
file->writeByte(0);
char buf[20];
snprintf(buf, 20, "%d", VERSIONNUM);
file->writeString(buf);
file->writeByte(0);
TimeDate dt;
g_system->getTimeAndDate(dt);
file->writeString(formatTimestamp(dt));
file->writeByte(0);
if (file->err()) {
error("Unable to write header data to savegame file \"%s\".", filename.c_str());
}
// Alle notwendigen Module persistieren.
OutputPersistenceBlock writer;
bool success = true;
success &= Kernel::getInstance()->getScript()->persist(writer);
success &= RegionRegistry::instance().persist(writer);
success &= Kernel::getInstance()->getGfx()->persist(writer);
success &= Kernel::getInstance()->getSfx()->persist(writer);
success &= Kernel::getInstance()->getInput()->persist(writer);
if (!success) {
error("Unable to persist modules for savegame file \"%s\".", filename.c_str());
}
// Write the save game data uncompressed, since the final saved game will be
// compressed anyway.
char sBuffer[10];
snprintf(sBuffer, 10, "%u", writer.getDataSize());
file->writeString(sBuffer);
file->writeByte(0);
snprintf(sBuffer, 10, "%u", writer.getDataSize());
file->writeString(sBuffer);
file->writeByte(0);
file->write(writer.getData(), writer.getDataSize());
// Get the screenshot
Common::SeekableReadStream *thumbnail = Kernel::getInstance()->getGfx()->getThumbnail();
if (thumbnail) {
byte *buffer = new byte[FILE_COPY_BUFFER_SIZE];
thumbnail->seek(0, SEEK_SET);
while (!thumbnail->eos()) {
int bytesRead = thumbnail->read(&buffer[0], FILE_COPY_BUFFER_SIZE);
file->write(&buffer[0], bytesRead);
}
delete[] buffer;
} else {
warning("The screenshot file \"%s\" does not exist. Savegame is written without a screenshot.", filename.c_str());
}
file->finalize();
delete file;
// Savegameinformationen für diesen Slot aktualisieren.
_impl->readSlotSavegameInformation(slotID);
// Empty the cache, to remove old thumbnails
Kernel::getInstance()->getResourceManager()->emptyThumbnailCache();
// Erfolg signalisieren.
return true;
}
int TattooMap::show() {
Debugger &debugger = *_vm->_debugger;
Events &events = *_vm->_events;
Music &music = *_vm->_music;
Resources &res = *_vm->_res;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Screen &screen = *_vm->_screen;
int result = 0;
// Check if we need to keep track of how many times player has been to the map
for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) {
SceneTripEntry &entry = scene._sceneTripCounters[idx];
if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) {
if (--entry._numTimes == 0) {
_vm->setFlagsDirect(entry._flag);
scene._sceneTripCounters.remove_at(idx);
}
}
}
if (music._midiOption) {
// See if Holmes or Watson is the active character
Common::String song;
if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES))
// Player is Holmes
song = "Cue9";
else if (_vm->readFlags(FLAG_ALT_MAP_MUSIC))
song = "Cue8";
else
song = "Cue7";
if (music.loadSong(song)) {
music.setMIDIVolume(music._musicVolume);
if (music._musicOn)
music.startSong();
}
}
screen.initPaletteFade(1364485);
// Load the custom mouse cursors for the map
ImageFile cursors("omouse.vgs");
events.setCursor(cursors[0]._frame);
events.warpMouse(Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2));
// Load the data for the map
_iconImages = new ImageFile("mapicons.vgs");
loadData();
// Load the palette
Common::SeekableReadStream *stream = res.load("map.pal");
stream->read(screen._cMap, PALETTE_SIZE);
screen.translatePalette(screen._cMap);
delete stream;
// Load the map image and draw it to the back buffer
ImageFile *map = new ImageFile("map.vgs");
screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
screen._backBuffer1.blitFrom((*map)[0], Common::Point(0, 0));
delete map;
screen.clear();
screen.setPalette(screen._cMap);
drawMapIcons();
// Copy the map drawn in the back buffer to the secondary back buffer
screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
screen._backBuffer2.blitFrom(screen._backBuffer1);
// Display the built map to the screen
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
// Set initial scroll position
_targetScroll = _bigPos;
screen._currentScroll = Common::Point(-1, -1);
do {
// Allow for event processing and get the current mouse position
events.pollEventsAndWait();
events.setButtonState();
Common::Point mousePos = events.screenMousePos();
if (debugger._showAllLocations == LOC_REFRESH) {
drawMapIcons();
screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
}
checkMapNames(true);
if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6))
_targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6);
if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6))
_targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6);
if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6))
_targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6);
if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6))
_targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6);
if (_targetScroll.x < 0)
//.........这里部分代码省略.........
/**
* Savegame format detector
* @param fHandle Savefile to check
* @return Savegame format on success, ANIMSIZE_UNKNOWN on failure
*
* This function seeks through the savefile and tries to determine the
* savegame format it uses. There's a miniscule chance that the detection
* algorithm could get confused and think that the file uses both the older
* and the newer format but that is such a remote possibility that I wouldn't
* worry about it at all.
*
* Also detects the temporary Operation Stealth savegame format now.
*/
enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) {
const uint32 prevStreamPos = fHandle.pos();
// First check for the temporary Operation Stealth savegame format.
fHandle.seek(0);
ChunkHeader hdr;
loadChunkHeader(fHandle, hdr);
fHandle.seek(prevStreamPos);
if (hdr.id == TEMP_OS_FORMAT_ID) {
return TEMP_OS_FORMAT;
}
// Ok, so the savegame isn't using the temporary Operation Stealth savegame format.
// Let's check for the plain Future Wars savegame format and its different versions then.
// The animDataTable begins at savefile position 0x2315.
// Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443)
// and 30 bytes in the save format after that (Revision 31444 and onwards).
// There are 255 entries in the animDataTable in both of the savefile formats.
static const uint animDataTableStart = 0x2315;
static const uint animEntriesCount = 255;
static const uint oldAnimEntrySize = 23;
static const uint newAnimEntrySize = 30;
static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize};
Common::Array<uint> animEntrySizeMatches;
// Try to walk through the savefile using different animDataTable entry sizes
// and make a list of all the successful entry sizes.
for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) {
// 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries)
// 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries)
static const uint sizeofScreenParams = 2 * 6;
static const uint globalScriptEntrySize = 206;
static const uint objectScriptEntrySize = 206;
static const uint overlayEntrySize = 20;
static const uint bgIncrustEntrySize = 20;
static const uint chainEntrySizes[] = {
globalScriptEntrySize,
objectScriptEntrySize,
overlayEntrySize,
bgIncrustEntrySize
};
uint animEntrySize = animEntrySizeChoices[i];
// Jump over the animDataTable entries and the screen parameters
int32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams;
// Check that there's data left after the point we're going to jump to
if (newPos >= fHandle.size()) {
continue;
}
fHandle.seek(newPos);
// Jump over the remaining items in the savegame file
// (i.e. the global scripts, object scripts, overlays and background incrusts).
bool chainWalkSuccess = true;
for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) {
// Read entry count and jump over the entries
int entryCount = fHandle.readSint16BE();
newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount;
// Check that we didn't go past the end of file.
// Note that getting exactly to the end of file is acceptable.
if (newPos > fHandle.size()) {
chainWalkSuccess = false;
break;
}
fHandle.seek(newPos);
}
// If we could walk the chain successfully and
// got exactly to the end of file then we've got a match.
if (chainWalkSuccess && fHandle.pos() == fHandle.size()) {
// We found a match, let's save it
animEntrySizeMatches.push_back(animEntrySize);
}
}
// Check that we got only one entry size match.
// If we didn't, then return an error.
enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN;
if (animEntrySizeMatches.size() == 1) {
const uint animEntrySize = animEntrySizeMatches[0];
assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize);
if (animEntrySize == oldAnimEntrySize) {
result = ANIMSIZE_23;
} else { // animEntrySize == newAnimEntrySize
// Check data and mask pointers in all of the animDataTable entries
// to see whether we've got the version with the broken data and mask pointers or not.
// In the broken format all data and mask pointers were always zero.
//.........这里部分代码省略.........
/**
* Load animDataTable from save
* @param fHandle Savefile open for reading
* @param saveGameFormat The used savegame format
* @todo Add Operation Stealth savefile support
*
* Unlike the old code, this one actually rebuilds the table one frame
* at a time.
*/
void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) {
int16 foundFileIdx;
char *animName, part[256], name[10];
strcpy(part, currentPartName);
// We only support these variations of the savegame format at the moment.
assert(saveGameFormat == ANIMSIZE_23 || saveGameFormat == ANIMSIZE_30_PTRS_INTACT);
const int entrySize = ((saveGameFormat == ANIMSIZE_23) ? 23 : 30);
const int fileStartPos = fHandle.pos();
for(int resourceIndex=0; resourceIndex<NUM_MAX_ANIMDATA; resourceIndex++) {
// Seek to the start of the current animation's entry
fHandle.seek(fileStartPos + resourceIndex * entrySize);
// Read in the current animation entry
fHandle.readUint16BE(); // width
fHandle.readUint16BE();
fHandle.readUint16BE(); // bpp
fHandle.readUint16BE(); // height
bool validPtr = false;
// Handle variables only present in animation entries of size 30
if (entrySize == 30) {
validPtr = (fHandle.readUint32BE() != 0); // Read data pointer
fHandle.readUint32BE(); // Discard mask pointer
}
foundFileIdx = fHandle.readSint16BE();
int16 frameIndex = fHandle.readSint16BE(); // frame
fHandle.read(name, 10);
// Handle variables only present in animation entries of size 23
if (entrySize == 23) {
validPtr = (fHandle.readByte() != 0);
}
// Don't try to load invalid entries.
if (foundFileIdx < 0 || !validPtr) {
//resourceIndex++; // Jump over the invalid entry
continue;
}
// Alright, the animation entry looks to be valid so let's start handling it...
if (strcmp(currentPartName, name) != 0) {
closePart();
loadPart(name);
}
animName = g_cine->_partBuffer[foundFileIdx].partName;
loadRelatedPalette(animName); // Is this for Future Wars only?
loadResource(animName, resourceIndex, frameIndex);
}
loadPart(part);
// Make sure we jump over all the animation entries
fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize);
}
请发表评论