diff options
author | Christopher Page | 2008-08-04 22:34:07 +0000 |
---|---|---|
committer | Christopher Page | 2008-08-04 22:34:07 +0000 |
commit | 4198ee962399305a4a158b1f43224c00e2e04a1b (patch) | |
tree | eea375b5cb471509df2e2c5d9123d963a62403c6 /engines/cine | |
parent | a51f45407659bba43254b466d20b6af2e8f17ffd (diff) | |
parent | 4f5479ee744ac6b419cdf7ec1e96fbf7c83d36ef (diff) | |
download | scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.gz scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.bz2 scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.zip |
Merged revisions 33188-33189,33191-33193,33196,33198,33202-33203,33206,33210,33212,33218-33220,33222,33224-33226,33229-33243,33246,33248-33250,33252,33258-33261,33263,33266,33270,33272-33283,33285,33287-33290,33295-33298,33321,33325-33330,33332-33335,33337-33340,33342,33345,33347,33349-33350,33352-33357,33359-33367,33369-33371,33373,33375-33377,33379-33380,33383-33385,33387-33389,33392-33394,33400-33402,33404-33405,33407-33410,33412-33416,33418-33419,33425-33427,33432,33436-33438,33444,33446,33452-33453,33455-33459,33463-33464,33466-33471,33473-33474,33478,33490,33492,33495-33496,33509-33512,33518-33519,33522-33527,33529-33530,33537,33541,33544,33546,33550,33552-33554,33556,33558,33561-33562,33565,33568,33570,33574,33576,33578-33581,33584-33587,33590,33596,33604-33611,33614-33615,33617-33618,33620-33621 via svnmerge from
https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk
svn-id: r33624
Diffstat (limited to 'engines/cine')
-rw-r--r-- | engines/cine/anim.cpp | 210 | ||||
-rw-r--r-- | engines/cine/anim.h | 60 | ||||
-rw-r--r-- | engines/cine/bg.cpp | 2 | ||||
-rw-r--r-- | engines/cine/bg.h | 3 | ||||
-rw-r--r-- | engines/cine/bg_list.cpp | 2 | ||||
-rw-r--r-- | engines/cine/bg_list.h | 2 | ||||
-rw-r--r-- | engines/cine/cine.h | 6 | ||||
-rw-r--r-- | engines/cine/gfx.cpp | 66 | ||||
-rw-r--r-- | engines/cine/gfx.h | 12 | ||||
-rw-r--r-- | engines/cine/main_loop.cpp | 31 | ||||
-rw-r--r-- | engines/cine/object.h | 2 | ||||
-rw-r--r-- | engines/cine/part.cpp | 4 | ||||
-rw-r--r-- | engines/cine/script.h | 20 | ||||
-rw-r--r-- | engines/cine/script_fw.cpp | 54 | ||||
-rw-r--r-- | engines/cine/script_os.cpp | 6 | ||||
-rw-r--r-- | engines/cine/various.cpp | 1132 | ||||
-rw-r--r-- | engines/cine/various.h | 2 |
17 files changed, 1104 insertions, 510 deletions
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp index 10f4aa55b5..eb820804a6 100644 --- a/engines/cine/anim.cpp +++ b/engines/cine/anim.cpp @@ -511,14 +511,15 @@ int emptyAnimSpace(int start = 0) { /*! \brief Load SPL data into animDataTable * \param resourceName SPL filename - * \param idx Target index in animDataTable + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded SPL data (-1 if error) */ -void loadSpl(const char *resourceName, int16 idx) { +int loadSpl(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry; if (foundFileIdx < 0) { - return; + return -1; } byte *dataPtr = readBundleFile(foundFileIdx); @@ -528,12 +529,15 @@ void loadSpl(const char *resourceName, int16 idx) { animDataTable[entry].load(dataPtr, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName); free(dataPtr); + return entry + 1; } /*! \brief Load 1bpp mask * \param resourceName Mask filename + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded mask */ -void loadMsk(const char *resourceName) { +int loadMsk(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry = 0; byte *dataPtr = readBundleFile(foundFileIdx); @@ -544,20 +548,23 @@ void loadMsk(const char *resourceName) { loadAnimHeader(animHeader, readS); ptr = dataPtr + 0x16; + entry = idx < 0 ? emptyAnimSpace() : idx; + assert(entry >= 0); for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { - entry = emptyAnimSpace(entry); - assert(entry >= 0); animDataTable[entry].load(ptr, ANIM_MASK, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName); ptr += animHeader.frameWidth * animHeader.frameHeight; } free(dataPtr); + return entry; } /*! \brief Load animation * \param resourceName Animation filename + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded animation */ -void loadAni(const char *resourceName) { +int loadAni(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); int entry = 0; byte *dataPtr = readBundleFile(foundFileIdx); @@ -571,10 +578,10 @@ void loadAni(const char *resourceName) { transparentColor = getAnimTransparentColor(resourceName); - for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { - entry = emptyAnimSpace(entry); - assert(entry >= 0); + entry = idx < 0 ? emptyAnimSpace() : idx; + assert(entry >= 0); + for (int16 i = 0; i < animHeader.numFrames; i++, entry++) { // special case transparency handling if (!strcmp(resourceName, "L2202.ANI")) { transparentColor = i < 2 ? 0 : 7; @@ -587,6 +594,7 @@ void loadAni(const char *resourceName) { } free(dataPtr); + return entry; } /*! \brief Decode 16 color image with palette @@ -642,13 +650,14 @@ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) { /*! \brief Load image set * \param resourceName Image set filename - * \param idx Target index in animDataTable + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded image set */ -void loadSet(const char *resourceName, int16 idx) { +int loadSet(const char *resourceName, int16 idx) { AnimHeader2Struct header2; uint16 numSpriteInAnim; int16 foundFileIdx = findFileInBundle(resourceName); - int16 entry = idx >= 0 ? idx : 0; + int16 entry; byte *ptr, *startOfDataPtr, *dataPtr, *origDataPtr; int type; @@ -661,6 +670,9 @@ void loadSet(const char *resourceName, int16 idx) { startOfDataPtr = ptr + numSpriteInAnim * 0x10; + entry = idx < 0 ? emptyAnimSpace() : idx; + assert(entry >= 0); + for (int16 i = 0; i < numSpriteInAnim; i++, entry++) { Common::MemoryReadStream readS(ptr, 0x10); @@ -674,9 +686,6 @@ void loadSet(const char *resourceName, int16 idx) { ptr += 0x10; - entry = idx < 0 ? emptyAnimSpace(entry) : idx + i; - assert(entry >= 0); - dataPtr = startOfDataPtr + header2.field_0; if (header2.type == 1) { @@ -693,175 +702,126 @@ void loadSet(const char *resourceName, int16 idx) { } free(origDataPtr); + return entry; } /*! \brief Load SEQ data into animDataTable * \param resourceName SEQ data filename - * \param idx Target index in animDataTable + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded SEQ data */ -void loadSeq(const char *resourceName, int16 idx) { +int loadSeq(const char *resourceName, int16 idx) { int16 foundFileIdx = findFileInBundle(resourceName); byte *dataPtr = readBundleFile(foundFileIdx); int entry = idx < 0 ? emptyAnimSpace() : idx; animDataTable[entry].load(dataPtr+0x16, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize-0x16, 1, foundFileIdx, 0, currentPartName); free(dataPtr); + return entry + 1; } -void loadResource(const char *resourceName) { - /* byte isMask = 0; */ - /* byte isSpl = 0; */ - +/*! \brief Load a resource into animDataTable + * \param resourceName Resource's filename + * \param idx Target index in animDataTable (-1 if any empty space will do) + * \return The number of the animDataTable entry after the loaded resource (-1 if error) + * \todo Implement loading of all resource types + */ +int loadResource(const char *resourceName, int16 idx) { + int result = -1; // Return an error by default if (strstr(resourceName, ".SPL")) { - loadSpl(resourceName, -1); - return; + result = loadSpl(resourceName, idx); } else if (strstr(resourceName, ".MSK")) { - loadMsk(resourceName); - return; + result = loadMsk(resourceName, idx); } else if (strstr(resourceName, ".ANI")) { - loadAni(resourceName); - return; + result = loadAni(resourceName, idx); } else if (strstr(resourceName, ".ANM")) { - loadAni(resourceName); - return; + result = loadAni(resourceName, idx); } else if (strstr(resourceName, ".SET")) { - loadSet(resourceName, -1); - return; + result = loadSet(resourceName, idx); } else if (strstr(resourceName, ".SEQ")) { - loadSeq(resourceName, -1); - return; - } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure - g_cine->quitGame(); - return; - } - - error("loadResource: Cannot determine type for '%s'", resourceName); -} - -/*! \todo There seems to be some additional resource file that is not loaded - */ -void loadAbs(const char *resourceName, uint16 idx) { - /* byte isMask = 0; */ - /* byte isSpl = 0; */ - - if (strstr(resourceName, ".SET")) { - loadSet(resourceName, idx); - return; + result = loadSeq(resourceName, idx); } else if (strstr(resourceName, ".H32")) { - warning("Ignoring file %s (load at %d)", resourceName, idx); - return; - } else if (strstr(resourceName, ".SEQ")) { - loadSeq(resourceName, idx); - return; - } else if (strstr(resourceName, ".SPL")) { - loadSpl(resourceName, idx); - return; + warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx); } else if (strstr(resourceName, ".AMI")) { - warning("Ignoring file %s (load at %d)", resourceName, idx); - return; - } else if (strstr(resourceName, ".ANI")) { - warning("Ignoring file %s (load at %d)", resourceName, idx); - return; + warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx); + } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure + g_cine->quitGame(); + } else { + error("loadResource: Cannot determine type for '%s'", resourceName); } - error("loadAbs: Cannot determine type for '%s'", resourceName); + return result; } /*! \brief Load animDataTable from save * \param fHandle Savefile open for reading - * \param broken Broken/correct file format switch + * \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::InSaveFile &fHandle, bool broken) { - int16 currentAnim, foundFileIdx; - int8 isMask = 0, isSpl = 0; - byte *dataPtr, *ptr; - char *animName, part[256]; - byte transparentColor = 0; - AnimData *currentPtr; - AnimHeaderStruct animHeader; - +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) { + int16 currentAnim, foundFileIdx, frame; + char *animName, part[256], name[10]; uint16 width, height, bpp, var1; - int16 frame; - char name[10]; - int type; strcpy(part, currentPartName); - for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim++) { - currentPtr = &animDataTable[currentAnim]; + // 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(); + currentAnim = 0; + while (currentAnim < NUM_MAX_ANIMDATA) { + // Seek to the start of the current animation's entry + fHandle.seek(fileStartPos + currentAnim * entrySize); + // Read in the current animation entry width = fHandle.readUint16BE(); var1 = fHandle.readUint16BE(); bpp = fHandle.readUint16BE(); height = fHandle.readUint16BE(); - if (!broken) { - if (!fHandle.readUint32BE()) { - fHandle.skip(18); - continue; - } - fHandle.readUint32BE(); + 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(); frame = fHandle.readSint16BE(); fHandle.read(name, 10); - if (foundFileIdx < 0 || (broken && !fHandle.readByte())) { + // 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) { + currentAnim++; // Jump over the invalid entry continue; } + // Alright, the animation entry looks to be valid so let's start handling it... if (strcmp(currentPartName, name)) { closePart(); loadPart(name); } animName = partBuffer[foundFileIdx].partName; - ptr = dataPtr = readBundleFile(foundFileIdx); - - isSpl = (strstr(animName, ".SPL")) ? 1 : 0; - isMask = (strstr(animName, ".MSK")) ? 1 : 0; - - if (isSpl) { - width = (uint16) partBuffer[foundFileIdx].unpackedSize; - height = 1; - frame = 0; - type = ANIM_RAW; - } else { - Common::MemoryReadStream readS(ptr, 0x16); - loadAnimHeader(animHeader, readS); - ptr += 0x16; - - width = animHeader.frameWidth; - height = animHeader.frameHeight; - - if (isMask) { - type = ANIM_MASK; - } else { - type = ANIM_MASKSPRITE; - - loadRelatedPalette(animName); - transparentColor = getAnimTransparentColor(animName); - - // special case transparency handling - if (!strcmp(animName, "L2202.ANI")) { - transparentColor = (frame < 2) ? 0 : 7; - } else if (!strcmp(animName, "L4601.ANI")) { - transparentColor = (frame < 1) ? 0xE : 0; - } - } - } - - ptr += frame * width * height; - currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor); - free(dataPtr); + loadRelatedPalette(animName); // Is this for Future Wars only? + const int16 prevAnim = currentAnim; + currentAnim = loadResource(animName, currentAnim); + assert(currentAnim > prevAnim); // Make sure we advance forward } loadPart(part); + + // Make sure we jump over all the animation entries + fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize); } } // End of namespace Cine diff --git a/engines/cine/anim.h b/engines/cine/anim.h index d63033e670..317654064b 100644 --- a/engines/cine/anim.h +++ b/engines/cine/anim.h @@ -26,8 +26,63 @@ #ifndef CINE_ANIM_H #define CINE_ANIM_H +#include "common/endian.h" + namespace Cine { +/** + * Cine engine's save game formats. + * Enumeration entries (Excluding the one used as an error) + * are sorted according to age (i.e. top one is oldest, last one newest etc). + * + * ANIMSIZE_UNKNOWN: + * - Animation data entry size is unknown (Used as an error). + * + * ANIMSIZE_23: + * - Animation data entry size is 23 bytes. + * - Used at least by 0.11.0 and 0.11.1 releases of ScummVM. + * - Introduced in revision 21772, stopped using in revision 31444. + * + * ANIMSIZE_30_PTRS_BROKEN: + * - Animation data entry size is 30 bytes. + * - Data and mask pointers in the saved structs are always NULL. + * - Introduced in revision 31453, stopped using in revision 32073. + * + * ANIMSIZE_30_PTRS_INTACT: + * - Animation data entry size is 30 bytes. + * - Data and mask pointers in the saved structs are intact, + * so you can test them for equality or inequality with NULL + * but don't try using them for anything else, it won't work. + * - Introduced in revision 31444, got broken in revision 31453, + * got fixed in revision 32073 and used after that. + * + * TEMP_OS_FORMAT: + * - Temporary Operation Stealth savegame format. + * - NOT backward compatible and NOT to be supported in the future. + * This format should ONLY be used during development and abandoned + * later in favor of a better format! + */ +enum CineSaveGameFormat { + ANIMSIZE_UNKNOWN, + ANIMSIZE_23, + ANIMSIZE_30_PTRS_BROKEN, + ANIMSIZE_30_PTRS_INTACT, + TEMP_OS_FORMAT +}; + +/** Identifier for the temporary Operation Stealth savegame format. */ +static const uint32 TEMP_OS_FORMAT_ID = MKID_BE('TEMP'); + +/** The current version number of Operation Stealth's savegame format. */ +static const uint32 CURRENT_OS_SAVE_VER = 0; + +/** Chunk header used by the temporary Operation Stealth savegame format. */ +struct ChunkHeader { + uint32 id; ///< Identifier (e.g. MKID_BE('TEMP')) + uint32 version; ///< Version number + uint32 size; ///< Size of the chunk after this header in bytes +}; + struct AnimHeaderStruct { byte field_0; byte field_1; @@ -99,9 +154,8 @@ extern AnimData animDataTable[NUM_MAX_ANIMDATA]; void freeAnimDataTable(void); void freeAnimDataRange(byte startIdx, byte numIdx); -void loadResource(const char *resourceName); -void loadAbs(const char *resourceName, uint16 idx); -void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken); +int loadResource(const char *resourceName, int16 idx = -1); +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat); void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency); } // End of namespace Cine diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp index c5b7fb4e3d..2a4e7f0ab1 100644 --- a/engines/cine/bg.cpp +++ b/engines/cine/bg.cpp @@ -35,7 +35,7 @@ namespace Cine { uint16 bgVar0; byte *additionalBgTable[9]; -byte currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0; +int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0; byte loadCtFW(const char *ctName) { uint16 header[32]; diff --git a/engines/cine/bg.h b/engines/cine/bg.h index 5fa8209131..9f97bc467d 100644 --- a/engines/cine/bg.h +++ b/engines/cine/bg.h @@ -35,6 +35,9 @@ void addBackground(const char *bgName, uint16 bgIdx); extern uint16 bgVar0; +extern int16 currentAdditionalBgIdx; +extern int16 currentAdditionalBgIdx2; + } // End of namespace Cine #endif diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp index b10211282f..fddca078e5 100644 --- a/engines/cine/bg_list.cpp +++ b/engines/cine/bg_list.cpp @@ -83,7 +83,7 @@ void resetBgIncrustList(void) { /*! \brief Restore incrust list from savefile * \param fHandle Savefile open for reading */ -void loadBgIncrustFromSave(Common::InSaveFile &fHandle) { +void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) { BGIncrust tmp; int size = fHandle.readSint16BE(); diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h index 1849d6ec3d..9a402baee8 100644 --- a/engines/cine/bg_list.h +++ b/engines/cine/bg_list.h @@ -51,7 +51,7 @@ void addSpriteFilledToBGList(int16 idx); void createBgIncrustListElement(int16 objIdx, int16 param); void resetBgIncrustList(void); -void loadBgIncrustFromSave(Common::InSaveFile &fHandle); +void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle); } // End of namespace Cine diff --git a/engines/cine/cine.h b/engines/cine/cine.h index 06f2dfd982..eaae555812 100644 --- a/engines/cine/cine.h +++ b/engines/cine/cine.h @@ -98,7 +98,13 @@ public: private: void initialize(void); + void resetEngine(); + bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat); + bool loadTempSaveOS(Common::SeekableReadStream &in); bool makeLoad(char *saveName); + void makeSaveFW(Common::OutSaveFile &out); + void makeSaveOS(Common::OutSaveFile &out); + void makeSave(char *saveFileName); void mainLoop(int bootScriptIdx); void readVolCnf(); diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp index 1f868ccb75..cbddf0fc59 100644 --- a/engines/cine/gfx.cpp +++ b/engines/cine/gfx.cpp @@ -601,20 +601,26 @@ void FWRenderer::setScroll(unsigned int shift) { error("Future Wars renderer doesn't support multiple backgrounds"); } +/*! \brief Future Wars has no scrolling backgrounds so scroll value is always zero. + */ +uint FWRenderer::getScroll() const { + return 0; +} + /*! \brief Placeholder for Operation Stealth implementation */ void FWRenderer::removeBg(unsigned int idx) { error("Future Wars renderer doesn't support multiple backgrounds"); } -void FWRenderer::saveBg(Common::OutSaveFile &fHandle) { +void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) { fHandle.write(_bgName, 13); } /*! \brief Restore active and backup palette from save * \param fHandle Savefile open for reading */ -void FWRenderer::restorePalette(Common::InSaveFile &fHandle) { +void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle) { int i; if (!_palette) { @@ -655,6 +661,49 @@ void FWRenderer::savePalette(Common::OutSaveFile &fHandle) { } } +/*! \brief Write active and backup palette to save + * \param fHandle Savefile open for writing + */ +void OSRenderer::savePalette(Common::OutSaveFile &fHandle) { + int i; + + assert(_activeHiPal); + + // Write the active 256 color palette. + for (i = 0; i < _hiPalSize; i++) { + fHandle.writeByte(_activeHiPal[i]); + } + + // Write the active 256 color palette a second time. + // FIXME: The backup 256 color palette should be saved here instead of the active one. + for (i = 0; i < _hiPalSize; i++) { + fHandle.writeByte(_activeHiPal[i]); + } +} + +/*! \brief Restore active and backup palette from save + * \param fHandle Savefile open for reading + */ +void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle) { + int i; + + if (!_activeHiPal) { + _activeHiPal = new byte[_hiPalSize]; + } + + assert(_activeHiPal); + + for (i = 0; i < _hiPalSize; i++) { + _activeHiPal[i] = fHandle.readByte(); + } + + // Jump over the backup 256 color palette. + // FIXME: Load the backup 256 color palette and use it properly. + fHandle.seek(_hiPalSize, SEEK_CUR); + + _changePal = 1; +} + /*! \brief Rotate active palette * \param a First color to rotate * \param b Last color to rotate @@ -1247,6 +1296,13 @@ void OSRenderer::setScroll(unsigned int shift) { _bgShift = shift; } +/*! \brief Get background scroll + * \return Background scroll in pixels + */ +uint OSRenderer::getScroll() const { + return _bgShift; +} + /*! \brief Unload background from renderer * \param idx Background to unload */ @@ -1270,6 +1326,12 @@ void OSRenderer::removeBg(unsigned int idx) { memset(_bgTable[idx].name, 0, sizeof (_bgTable[idx].name)); } +void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) { + for (int i = 0; i < 8; i++) { + fHandle.write(_bgTable[i].name, 13); + } +} + /*! \brief Fade to black * \bug Operation Stealth sometimes seems to fade to black using * transformPalette resulting in double fadeout diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h index c63c79ac82..6a3aa1ef89 100644 --- a/engines/cine/gfx.h +++ b/engines/cine/gfx.h @@ -108,13 +108,14 @@ public: virtual void selectBg(unsigned int idx); virtual void selectScrollBg(unsigned int idx); virtual void setScroll(unsigned int shift); + virtual uint getScroll() const; virtual void removeBg(unsigned int idx); - void saveBg(Common::OutSaveFile &fHandle); + virtual void saveBgNames(Common::OutSaveFile &fHandle); virtual void refreshPalette(); virtual void reloadPalette(); - void restorePalette(Common::InSaveFile &fHandle); - void savePalette(Common::OutSaveFile &fHandle); + virtual void restorePalette(Common::SeekableReadStream &fHandle); + virtual void savePalette(Common::OutSaveFile &fHandle); virtual void rotatePalette(int a, int b, int c); virtual void transformPalette(int first, int last, int r, int g, int b); @@ -128,6 +129,7 @@ public: */ class OSRenderer : public FWRenderer { private: + // FIXME: Background table's size is probably 8 instead of 9. Check to make sure and correct if necessary. palBg _bgTable[9]; ///< Table of backgrounds loaded into renderer byte *_activeHiPal; ///< Active 256 color palette unsigned int _currentBg; ///< Current background @@ -163,10 +165,14 @@ public: void selectBg(unsigned int idx); void selectScrollBg(unsigned int idx); void setScroll(unsigned int shift); + uint getScroll() const; void removeBg(unsigned int idx); + void saveBgNames(Common::OutSaveFile &fHandle); void refreshPalette(); void reloadPalette(); + void restorePalette(Common::SeekableReadStream &fHandle); + void savePalette(Common::OutSaveFile &fHandle); void rotatePalette(int a, int b, int c); void transformPalette(int first, int last, int r, int g, int b); diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp index 547379f02d..deac4fd57f 100644 --- a/engines/cine/main_loop.cpp +++ b/engines/cine/main_loop.cpp @@ -175,6 +175,20 @@ int getKeyData() { return k; } +/** Removes elements from seqList that have their member variable var4 set to value -1. */ +void purgeSeqList() { + Common::List<SeqListElement>::iterator it = seqList.begin(); + while (it != seqList.end()) { + if (it->var4 == -1) { + // Erase the element and jump to the next element + it = seqList.erase(it); + } else { + // Let the element be and jump to the next element + it++; + } + } +} + void CineEngine::mainLoop(int bootScriptIdx) { bool playerAction; byte di; @@ -187,7 +201,7 @@ void CineEngine::mainLoop(int bootScriptIdx) { errorVar = 0; - addScriptToList0(bootScriptIdx); + addScriptToGlobalScripts(bootScriptIdx); menuVar = 0; @@ -235,12 +249,17 @@ void CineEngine::mainLoop(int bootScriptIdx) { } } - processSeqList(); - executeList1(); - executeList0(); + if (g_cine->getGameType() == Cine::GType_OS) { + processSeqList(); + } + executeObjectScripts(); + executeGlobalScripts(); - purgeList1(); - purgeList0(); + purgeObjectScripts(); + purgeGlobalScripts(); + if (g_cine->getGameType() == Cine::GType_OS) { + purgeSeqList(); + } if (playerCommand == -1) { setMouseCursor(MOUSE_CURSOR_NORMAL); diff --git a/engines/cine/object.h b/engines/cine/object.h index 103b2f50ba..7ad65eb75f 100644 --- a/engines/cine/object.h +++ b/engines/cine/object.h @@ -50,7 +50,7 @@ struct overlay { }; #define NUM_MAX_OBJECT 255 -#define NUM_MAX_VAR 256 +#define NUM_MAX_VAR 255 extern objectStruct objectTable[NUM_MAX_OBJECT]; diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp index b39f1eff7d..88f2dcef52 100644 --- a/engines/cine/part.cpp +++ b/engines/cine/part.cpp @@ -289,8 +289,8 @@ void dumpBundle(const char *fileName) { debug(0, "%s", partBuffer[i].partName); - Common::File out; - if (out.open(Common::String("dumps/") + partBuffer[i].partName, Common::File::kFileWriteMode)) { + Common::DumpFile out; + if (out.open(Common::String("dumps/") + partBuffer[i].partName)) { out.write(data, partBuffer[i].unpackedSize); out.close(); } diff --git a/engines/cine/script.h b/engines/cine/script.h index fcd21990fa..19576e4c1a 100644 --- a/engines/cine/script.h +++ b/engines/cine/script.h @@ -61,7 +61,7 @@ private: public: // Explicit to prevent var=0 instead of var[i]=0 typos. explicit ScriptVars(unsigned int len = 50); - ScriptVars(Common::InSaveFile &fHandle, unsigned int len = 50); + ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len = 50); ScriptVars(const ScriptVars &src); ~ScriptVars(void); @@ -71,8 +71,8 @@ public: void save(Common::OutSaveFile &fHandle) const; void save(Common::OutSaveFile &fHandle, unsigned int len) const; - void load(Common::InSaveFile &fHandle); - void load(Common::InSaveFile &fHandle, unsigned int len); + void load(Common::SeekableReadStream &fHandle); + void load(Common::SeekableReadStream &fHandle, unsigned int len); void reset(void); }; @@ -198,7 +198,7 @@ protected: int o1_blitAndFade(); int o1_fadeToBlack(); int o1_transformPaletteRange(); - int o1_setDefaultMenuColor2(); + int o1_setDefaultMenuBgColor(); int o1_palRotate(); int o1_break(); int o1_endScript(); @@ -213,7 +213,7 @@ protected: int o1_initializeZoneData(); int o1_setZoneDataEntry(); int o1_getZoneDataEntry(); - int o1_setDefaultMenuColor(); + int o1_setPlayerCommandPosY(); int o1_allowPlayerInput(); int o1_disallowPlayerInput(); int o1_changeDataDisk(); @@ -371,16 +371,16 @@ void dumpScript(char *dumpName); #define OP_requestCheckPendingDataLoad 0x42 #define OP_endScript 0x50 -void addScriptToList0(uint16 idx); +void addScriptToGlobalScripts(uint16 idx); int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx); void runObjectScript(int16 entryIdx); -void executeList1(void); -void executeList0(void); +void executeObjectScripts(void); +void executeGlobalScripts(void); -void purgeList1(void); -void purgeList0(void); +void purgeObjectScripts(void); +void purgeGlobalScripts(void); } // End of namespace Cine diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp index 54a4976000..e761a0c8e4 100644 --- a/engines/cine/script_fw.cpp +++ b/engines/cine/script_fw.cpp @@ -38,7 +38,13 @@ namespace Cine { -ScriptVars globalVars(NUM_MAX_VAR); +/** + * Global variables. + * 255 of these are saved, but there's one more that's used for bypassing the copy protection. + * In CineEngine::mainLoop(int bootScriptIdx) there's this code: globalVars[VAR_BYPASS_PROTECTION] = 0; + * And as VAR_BYPASS_PROTECTION is 255 that's why we're allocating one more than we otherwise would. + */ +ScriptVars globalVars(NUM_MAX_VAR + 1); uint16 compareVars(int16 a, int16 b); @@ -135,7 +141,7 @@ const Opcode FWScript::_opcodeTable[] = { { &FWScript::o1_transformPaletteRange, "bbwww" }, /* 48 */ { 0, 0 }, - { &FWScript::o1_setDefaultMenuColor2, "b" }, + { &FWScript::o1_setDefaultMenuBgColor, "b" }, { &FWScript::o1_palRotate, "bbb" }, { 0, 0 }, /* 4C */ @@ -174,7 +180,7 @@ const Opcode FWScript::_opcodeTable[] = { { &FWScript::o1_setZoneDataEntry, "bw" }, { &FWScript::o1_getZoneDataEntry, "bb" }, /* 68 */ - { &FWScript::o1_setDefaultMenuColor, "b" }, + { &FWScript::o1_setPlayerCommandPosY, "b" }, { &FWScript::o1_allowPlayerInput, "" }, { &FWScript::o1_disallowPlayerInput, "" }, { &FWScript::o1_changeDataDisk, "b" }, @@ -230,7 +236,7 @@ ScriptVars::ScriptVars(unsigned int len) : _size(len), _vars(new int16[len]) { * \param fHandle Savefile open for reading * \param len Size of array */ -ScriptVars::ScriptVars(Common::InSaveFile &fHandle, unsigned int len) +ScriptVars::ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len) : _size(len), _vars(new int16[len]) { assert(_vars); @@ -306,7 +312,7 @@ void ScriptVars::save(Common::OutSaveFile &fHandle, unsigned int len) const { /*! \brief Restore array from savefile * \param fHandle Savefile open for reading */ -void ScriptVars::load(Common::InSaveFile &fHandle) { +void ScriptVars::load(Common::SeekableReadStream &fHandle) { load(fHandle, _size); } @@ -314,7 +320,7 @@ void ScriptVars::load(Common::InSaveFile &fHandle) { * \param fHandle Savefile open for reading * \param len Length of data to be read */ -void ScriptVars::load(Common::InSaveFile &fHandle, unsigned int len) { +void ScriptVars::load(Common::SeekableReadStream &fHandle, unsigned int len) { debug(6, "assert(%d <= %d)", len, _size); assert(len <= _size); for (unsigned int i = 0; i < len; i++) { @@ -1273,7 +1279,7 @@ int FWScript::o1_startGlobalScript() { assert(param < NUM_MAX_SCRIPT); debugC(5, kCineDebugScript, "Line: %d: startScript(%d)", _line, param); - addScriptToList0(param); + addScriptToGlobalScripts(param); return 0; } @@ -1399,10 +1405,11 @@ int FWScript::o1_transformPaletteRange() { return 0; } -int FWScript::o1_setDefaultMenuColor2() { +/** Set the default background color used for message boxes. */ +int FWScript::o1_setDefaultMenuBgColor() { byte param = getNextByte(); - debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor2(%d)", _line, param); + debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuBgColor(%d)", _line, param); renderer->_messageBg = param; return 0; @@ -1568,10 +1575,11 @@ int FWScript::o1_getZoneDataEntry() { return 0; } -int FWScript::o1_setDefaultMenuColor() { +/** Set the player command string's vertical position on-screen. */ +int FWScript::o1_setPlayerCommandPosY() { byte param = getNextByte(); - debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor(%d)", _line, param); + debugC(5, kCineDebugScript, "Line: %d: setPlayerCommandPosY(%d)", _line, param); renderer->_cmdY = param; return 0; @@ -1746,7 +1754,7 @@ int FWScript::o1_unloadMask5() { //----------------------------------------------------------------------- -void addScriptToList0(uint16 idx) { +void addScriptToGlobalScripts(uint16 idx) { ScriptPtr tmp(scriptInfo->create(*scriptTable[idx], idx)); assert(tmp); globalScripts.push_back(tmp); @@ -1820,7 +1828,7 @@ uint16 compareVars(int16 a, int16 b) { return flag; } -void executeList1(void) { +void executeObjectScripts(void) { ScriptList::iterator it = objectScripts.begin(); for (; it != objectScripts.end();) { if ((*it)->_index < 0 || (*it)->execute() < 0) { @@ -1831,7 +1839,7 @@ void executeList1(void) { } } -void executeList0(void) { +void executeGlobalScripts(void) { ScriptList::iterator it = globalScripts.begin(); for (; it != globalScripts.end();) { if ((*it)->_index < 0 || (*it)->execute() < 0) { @@ -1842,12 +1850,16 @@ void executeList0(void) { } } -/*! \todo objectScripts.clear()? +/*! \todo Remove object scripts with script index of -1 (Not script position, but script index!). + * This would seem to be valid for both Future Wars and Operation Stealth. */ -void purgeList1(void) { +void purgeObjectScripts(void) { } -void purgeList0(void) { +/*! \todo Remove global scripts with script index of -1 (Not script position, but script index!). + * This would seem to be valid for both Future Wars and Operation Stealth. + */ +void purgeGlobalScripts(void) { } //////////////////////////////////// @@ -2380,7 +2392,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx) param = *(localScriptPtr + position); position++; - sprintf(lineBuffer, "setDefaultMenuColor2(%d)\n", param); + sprintf(lineBuffer, "setDefaultMenuBgColor(%d)\n", param); break; } @@ -2530,7 +2542,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx) param = *(localScriptPtr + position); position++; - sprintf(lineBuffer, "setDefaultMenuBoxColor(%d)\n", param); + sprintf(lineBuffer, "setPlayerCommandPosY(%d)\n", param); break; } @@ -2945,10 +2957,10 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx) } void dumpScript(char *dumpName) { - Common::File fHandle; + Common::DumpFile fHandle; uint16 i; - fHandle.open(dumpName, Common::File::kFileWriteMode); + fHandle.open(dumpName); for (i = 0; i < decompileBufferPosition; i++) { fHandle.writeString(Common::String(decompileBuffer[i])); diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp index 78b6c55564..0289a2a0bc 100644 --- a/engines/cine/script_os.cpp +++ b/engines/cine/script_os.cpp @@ -131,7 +131,7 @@ const Opcode OSScript::_opcodeTable[] = { { &FWScript::o1_transformPaletteRange, "bbwww" }, /* 48 */ { 0, 0 }, - { &FWScript::o1_setDefaultMenuColor2, "b" }, + { &FWScript::o1_setDefaultMenuBgColor, "b" }, { &FWScript::o1_palRotate, "bbb" }, { 0, 0 }, /* 4C */ @@ -170,7 +170,7 @@ const Opcode OSScript::_opcodeTable[] = { { &FWScript::o1_setZoneDataEntry, "bw" }, { &FWScript::o1_getZoneDataEntry, "bb" }, /* 68 */ - { &FWScript::o1_setDefaultMenuColor, "b" }, + { &FWScript::o1_setPlayerCommandPosY, "b" }, { &FWScript::o1_allowPlayerInput, "" }, { &FWScript::o1_disallowPlayerInput, "" }, { &FWScript::o1_changeDataDisk, "b" }, /* Same as opcodes 0x95 and 0xA9. */ @@ -636,7 +636,7 @@ int FWScript::o2_loadAbs() { const char *param2 = getNextString(); debugC(5, kCineDebugScript, "Line: %d: loadABS(%d,%s)", _line, param1, param2); - loadAbs(param2, param1); + loadResource(param2, param1); return 0; } diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp index b70201ce99..c490756403 100644 --- a/engines/cine/various.cpp +++ b/engines/cine/various.cpp @@ -187,6 +187,15 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { frame = ABS((int16)(objectTable[it->objIdx].frame)); part = objectTable[it->objIdx].part; + // Additional case for negative frame values in Operation Stealth + if (g_cine->getGameType() == Cine::GType_OS && objectTable[it->objIdx].frame < 0) { + if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) { + return it->objIdx; + } else { + continue; + } + } + if (it->type == 0) { threshold = animDataTable[frame]._var1; } else { @@ -199,16 +208,19 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { xdif = x - objX; ydif = y - objY; - if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif < 0) || (ydif >= height) || !animDataTable[frame].data()) { + if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !animDataTable[frame].data()) { continue; } if (g_cine->getGameType() == Cine::GType_OS) { + // This test isn't present in Operation Stealth's PC version's disassembly + // but removing it makes things crash sometimes (e.g. when selecting a verb + // and moving the mouse cursor around the floor in the airport's bathroom). if (xdif >= width) { continue; } - if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != part) { + if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) { return it->objIdx; } else if (it->type == 1 && gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) { return it->objIdx; @@ -227,6 +239,143 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { return -1; } +bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) { + out.writeUint32BE(header.id); + out.writeUint32BE(header.version); + out.writeUint32BE(header.size); + return !out.ioFailed(); +} + +bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) { + header.id = in.readUint32BE(); + header.version = in.readUint32BE(); + header.size = in.readUint32BE(); + return !in.ioFailed(); +} + +void saveObjectTable(Common::OutSaveFile &out) { + out.writeUint16BE(NUM_MAX_OBJECT); // Entry count + out.writeUint16BE(0x20); // Entry size + + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + out.writeUint16BE(objectTable[i].x); + out.writeUint16BE(objectTable[i].y); + out.writeUint16BE(objectTable[i].mask); + out.writeUint16BE(objectTable[i].frame); + out.writeUint16BE(objectTable[i].costume); + out.write(objectTable[i].name, 20); + out.writeUint16BE(objectTable[i].part); + } +} + +void saveZoneData(Common::OutSaveFile &out) { + for (int i = 0; i < 16; i++) { + out.writeUint16BE(zoneData[i]); + } +} + +void saveCommandVariables(Common::OutSaveFile &out) { + for (int i = 0; i < 4; i++) { + out.writeUint16BE(commandVar3[i]); + } +} + +void saveAnimDataTable(Common::OutSaveFile &out) { + out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count + out.writeUint16BE(0x1E); // Entry size + + for (int i = 0; i < NUM_MAX_ANIMDATA; i++) { + animDataTable[i].save(out); + } +} + +void saveScreenParams(Common::OutSaveFile &out) { + // Screen parameters, unhandled + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); +} + +void saveGlobalScripts(Common::OutSaveFile &out) { + ScriptList::const_iterator it; + out.writeUint16BE(globalScripts.size()); + for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { + (*it)->save(out); + } +} + +void saveObjectScripts(Common::OutSaveFile &out) { + ScriptList::const_iterator it; + out.writeUint16BE(objectScripts.size()); + for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { + (*it)->save(out); + } +} + +void saveOverlayList(Common::OutSaveFile &out) { + Common::List<overlay>::const_iterator it; + + out.writeUint16BE(overlayList.size()); + + for (it = overlayList.begin(); it != overlayList.end(); ++it) { + out.writeUint32BE(0); // next + out.writeUint32BE(0); // previous? + out.writeUint16BE(it->objIdx); + out.writeUint16BE(it->type); + out.writeSint16BE(it->x); + out.writeSint16BE(it->y); + out.writeSint16BE(it->width); + out.writeSint16BE(it->color); + } +} + +void saveBgIncrustList(Common::OutSaveFile &out) { + Common::List<BGIncrust>::const_iterator it; + out.writeUint16BE(bgIncrustList.size()); + + for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { + out.writeUint32BE(0); // next + out.writeUint32BE(0); // previous? + out.writeUint16BE(it->objIdx); + out.writeUint16BE(it->param); + out.writeUint16BE(it->x); + out.writeUint16BE(it->y); + out.writeUint16BE(it->frame); + out.writeUint16BE(it->part); + } +} + +void saveZoneQuery(Common::OutSaveFile &out) { + for (int i = 0; i < 16; i++) { + out.writeUint16BE(zoneQuery[i]); + } +} + +void saveSeqList(Common::OutSaveFile &out) { + Common::List<SeqListElement>::const_iterator it; + out.writeUint16BE(seqList.size()); + + for (it = seqList.begin(); it != seqList.end(); ++it) { + out.writeSint16BE(it->var4); + out.writeUint16BE(it->objIdx); + out.writeSint16BE(it->var8); + out.writeSint16BE(it->frame); + out.writeSint16BE(it->varC); + out.writeSint16BE(it->varE); + out.writeSint16BE(it->var10); + out.writeSint16BE(it->var12); + out.writeSint16BE(it->var14); + out.writeSint16BE(it->var16); + out.writeSint16BE(it->var18); + out.writeSint16BE(it->var1A); + out.writeSint16BE(it->var1C); + out.writeSint16BE(it->var1E); + } +} + bool CineEngine::loadSaveDirectory(void) { Common::InSaveFile *fHandle; char tmp[80]; @@ -244,21 +393,143 @@ bool CineEngine::loadSaveDirectory(void) { return true; } +/*! \brief 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 + uint32 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. + static const uint relativeDataPos = 2 * 4; + bool pointersIntact = false; + for (uint i = 0; i < animEntriesCount; i++) { + fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos); + uint32 data = fHandle.readUint32BE(); + uint32 mask = fHandle.readUint32BE(); + if ((data != 0) || (mask != 0)) { + pointersIntact = true; + break; + } + } + result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN); + } + } else if (animEntrySizeMatches.size() > 1) { + warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format"); + } else { // animEtrySizeMatches.size() == 0 + debug(3, "Savegame format detector was unable to detect savegame's format"); + } + + fHandle.seek(prevStreamPos); + return result; +} + /*! \brief Restore script list item from savefile - * \param fHandle Savefile handlem open for reading + * \param fHandle Savefile handle open for reading * \param isGlobal Restore object or global script? */ -void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { +void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) { ScriptVars localVars, labels; uint16 compare, pos; int16 idx; - labels.load(*fHandle); - localVars.load(*fHandle); + labels.load(fHandle); + localVars.load(fHandle); - compare = fHandle->readUint16BE(); - pos = fHandle->readUint16BE(); - idx = fHandle->readUint16BE(); + compare = fHandle.readUint16BE(); + pos = fHandle.readUint16BE(); + idx = fHandle.readUint16BE(); // no way to reinitialize these if (idx < 0) { @@ -281,7 +552,7 @@ void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { /*! \brief Restore overlay sprites from savefile * \param fHandle Savefile open for reading */ -void loadOverlayFromSave(Common::InSaveFile &fHandle) { +void loadOverlayFromSave(Common::SeekableReadStream &fHandle) { overlay tmp; fHandle.readUint32BE(); @@ -297,128 +568,10 @@ void loadOverlayFromSave(Common::InSaveFile &fHandle) { overlayList.push_back(tmp); } -/*! \brief Savefile format tester - * \param fHandle Savefile to check - * - * This function seeks through savefile and tries to guess if it's the original - * savegame format or broken format from ScummVM 0.10/0.11 - * The test is incomplete but this should cover 99.99% of cases. - * If anyone makes a savefile which could confuse this test, assert will - * report it - */ -bool brokenSave(Common::InSaveFile &fHandle) { - // Backward seeking not supported in compressed savefiles - // if you really want it, finish it yourself - return false; - - // fixed size part: 14093 bytes (12308 bytes in broken save) - // animDataTable begins at byte 6431 - - int filesize = fHandle.size(); - int startpos = fHandle.pos(); - int pos, tmp; - bool correct = false, broken = false; - - // check for correct format - while (filesize > 14093) { - pos = 14093; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - - if (pos == filesize) correct = true; - break; - } - debug(5, "brokenSave: correct format check %s: size=%d, pos=%d", - correct ? "passed" : "failed", filesize, pos); - - // check for broken format - while (filesize > 12308) { - pos = 12308; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - - if (pos == filesize) broken = true; - break; - } - debug(5, "brokenSave: broken format check %s: size=%d, pos=%d", - broken ? "passed" : "failed", filesize, pos); - - // there's a very small chance that both cases will match - // if anyone runs into it, you'll have to walk through - // the animDataTable and try to open part file for each entry - if (!correct && !broken) { - error("brokenSave: file format check failed"); - } else if (correct && broken) { - error("brokenSave: both file formats seem to apply"); - } - - fHandle.seek(startpos); - debug(5, "brokenSave: detected %s file format", - correct ? "correct" : "broken"); - - return broken; -} - -/*! \todo Implement Operation Stealth loading, this is obviously Future Wars only - * \todo Add support for loading the zoneQuery table (Operation Stealth specific) - */ -bool CineEngine::makeLoad(char *saveName) { - int16 i; - int16 size; - bool broken; - Common::InSaveFile *fHandle; - char bgName[13]; - - fHandle = g_saveFileMan->openForLoading(saveName); - - if (!fHandle) { - drawString(otherMessages[0], 0); - waitPlayerInput(); - // restoreScreen(); - checkDataDisk(-1); - return false; - } - +void CineEngine::resetEngine() { g_sound->stopMusic(); freeAnimDataTable(); overlayList.clear(); - // if (g_cine->getGameType() == Cine::GType_OS) { - // freeUnkList(); - // } bgIncrustList.clear(); closePart(); @@ -428,7 +581,9 @@ bool CineEngine::makeLoad(char *saveName) { scriptTable.clear(); messageTable.clear(); - for (i = 0; i < NUM_MAX_OBJECT; i++) { + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + objectTable[i].x = 0; + objectTable[i].y = 0; objectTable[i].part = 0; objectTable[i].name[0] = 0; objectTable[i].frame = 0; @@ -462,29 +617,294 @@ bool CineEngine::makeLoad(char *saveName) { checkForPendingDataLoadSwitch = 0; - broken = brokenSave(*fHandle); + if (g_cine->getGameType() == Cine::GType_OS) { + seqList.clear(); + currentAdditionalBgIdx = 0; + currentAdditionalBgIdx2 = 0; + // TODO: Add resetting of the following variables + // adBgVar1 = 0; + // adBgVar0 = 0; + // gfxFadeOutCompleted = 0; + } +} + +bool loadObjectTable(Common::SeekableReadStream &in) { + in.readUint16BE(); // Entry count + in.readUint16BE(); // Entry size + + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + objectTable[i].x = in.readSint16BE(); + objectTable[i].y = in.readSint16BE(); + objectTable[i].mask = in.readUint16BE(); + objectTable[i].frame = in.readSint16BE(); + objectTable[i].costume = in.readSint16BE(); + in.read(objectTable[i].name, 20); + objectTable[i].part = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadZoneData(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneData[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadCommandVariables(Common::SeekableReadStream &in) { + for (int i = 0; i < 4; i++) { + commandVar3[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadScreenParams(Common::SeekableReadStream &in) { + // TODO: handle screen params (really required ?) + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + return !in.ioFailed(); +} + +bool loadGlobalScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, true); + } + return !in.ioFailed(); +} + +bool loadObjectScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, false); + } + return !in.ioFailed(); +} + +bool loadOverlayList(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadOverlayFromSave(in); + } + return !in.ioFailed(); +} + +bool loadSeqList(Common::SeekableReadStream &in) { + uint size = in.readUint16BE(); + SeqListElement tmp; + for (uint i = 0; i < size; i++) { + tmp.var4 = in.readSint16BE(); + tmp.objIdx = in.readUint16BE(); + tmp.var8 = in.readSint16BE(); + tmp.frame = in.readSint16BE(); + tmp.varC = in.readSint16BE(); + tmp.varE = in.readSint16BE(); + tmp.var10 = in.readSint16BE(); + tmp.var12 = in.readSint16BE(); + tmp.var14 = in.readSint16BE(); + tmp.var16 = in.readSint16BE(); + tmp.var18 = in.readSint16BE(); + tmp.var1A = in.readSint16BE(); + tmp.var1C = in.readSint16BE(); + tmp.var1E = in.readSint16BE(); + seqList.push_back(tmp); + } + return !in.ioFailed(); +} + +bool loadZoneQuery(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneQuery[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { + char musicName[13]; + char bgNames[8][13]; + + // First check the temporary Operation Stealth savegame format header. + ChunkHeader hdr; + loadChunkHeader(in, hdr); + if (hdr.id != TEMP_OS_FORMAT_ID) { + warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame"); + return false; + } else if (hdr.version > CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected newer format version. Not loading savegame"); + return false; + } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break"); + } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER + debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match)."); + } + + // There shouldn't be any data in the header's chunk currently so it's an error if there is. + if (hdr.size > 0) { + warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame"); + return false; + } + + // Ok, so we've got a correct header for a temporary Operation Stealth savegame. + // Let's start loading the plain savegame data then. + currentDisk = in.readUint16BE(); + in.read(currentPartName, 13); + in.read(currentPrcName, 13); + in.read(currentRelName, 13); + in.read(currentMsgName, 13); + + // Load the 8 background names. + for (uint i = 0; i < 8; i++) { + in.read(bgNames[i], 13); + } + + in.read(currentCtName, 13); + + // Moved the loading of current procedure, relation, + // backgrounds and Ct here because if they were at the + // end of this function then the global scripts loading + // made an array out of bounds access. In the original + // game's disassembly these aren't here but at the end. + // The difference is probably in how we handle loading + // the global scripts and some other things (i.e. the + // loading routines aren't exactly the same and subtle + // semantic differences result in having to do things + // in a different order). + { + // Not sure if this is needed with Operation Stealth... + checkDataDisk(currentDisk); + + if (strlen(currentPrcName)) { + loadPrc(currentPrcName); + } + + if (strlen(currentRelName)) { + loadRel(currentRelName); + } + + // Load first background (Uses loadBg) + if (strlen(bgNames[0])) { + loadBg(bgNames[0]); + } + + // Add backgrounds 1-7 (Uses addBackground) + for (int i = 1; i < 8; i++) { + if (strlen(bgNames[i])) { + addBackground(bgNames[i], i); + } + } + + if (strlen(currentCtName)) { + loadCtOS(currentCtName); + } + } + + loadObjectTable(in); + renderer->restorePalette(in); + globalVars.load(in, NUM_MAX_VAR); + loadZoneData(in); + loadCommandVariables(in); + in.read(commandBuffer, 0x50); + loadZoneQuery(in); + + // TODO: Use the loaded string (Current music name (String, 13 bytes)). + in.read(musicName, 13); + + // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)). + in.readUint16BE(); + + // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)). + in.readUint16BE(); + + renderer->_cmdY = in.readUint16BE(); + in.readUint16BE(); // Some unknown variable that seems to always be zero + allowPlayerInput = in.readUint16BE(); + playerCommand = in.readUint16BE(); + commandVar1 = in.readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); + var5 = in.readUint16BE(); + var4 = in.readUint16BE(); + var3 = in.readUint16BE(); + var2 = in.readUint16BE(); + commandVar2 = in.readUint16BE(); + renderer->_messageBg = in.readUint16BE(); + + // TODO: Use the loaded value (adBgVar1 (Uint16BE)). + in.readUint16BE(); + + currentAdditionalBgIdx = in.readSint16BE(); + currentAdditionalBgIdx2 = in.readSint16BE(); + + // TODO: Check whether the scroll value really gets used correctly after this. + // Note that the backgrounds are loaded only later than this value is set. + renderer->setScroll(in.readUint16BE()); + + // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?). + in.readUint16BE(); + + disableSystemMenu = in.readUint16BE(); + + // TODO: adBgVar1 = 1 here + + // Load the animDataTable entries + in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth). + in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth). + loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT); + + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadSeqList(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); + + // Left this here instead of moving it earlier in this function with + // the other current value loadings (e.g. loading of current procedure, + // current backgrounds etc). Mostly emulating the way we've handled + // Future Wars savegames and hoping that things work out. + if (strlen(currentMsgName)) { + loadMsg(currentMsgName); + } + + // TODO: Add current music loading and playing here + // TODO: Palette handling? + + if (in.pos() == in.size()) { + debug(3, "loadTempSaveOS: Loaded the whole savefile."); + } else { + warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over"); + } + + return !in.ioFailed(); +} + +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { + char bgName[13]; // At savefile position 0x0000: - currentDisk = fHandle->readUint16BE(); + currentDisk = in.readUint16BE(); // At 0x0002: - fHandle->read(currentPartName, 13); + in.read(currentPartName, 13); // At 0x000F: - fHandle->read(currentDatName, 13); + in.read(currentDatName, 13); // At 0x001C: - saveVar2 = fHandle->readSint16BE(); + saveVar2 = in.readSint16BE(); // At 0x001E: - fHandle->read(currentPrcName, 13); + in.read(currentPrcName, 13); // At 0x002B: - fHandle->read(currentRelName, 13); + in.read(currentRelName, 13); // At 0x0038: - fHandle->read(currentMsgName, 13); + in.read(currentMsgName, 13); // At 0x0045: - fHandle->read(bgName, 13); + in.read(bgName, 13); // At 0x0052: - fHandle->read(currentCtName, 13); + in.read(currentCtName, 13); checkDataDisk(currentDisk); @@ -509,118 +929,69 @@ bool CineEngine::makeLoad(char *saveName) { } // At 0x005F: - fHandle->readUint16BE(); - // At 0x0061: - fHandle->readUint16BE(); + loadObjectTable(in); - // At 0x0063: - for (i = 0; i < 255; i++) { - // At 0x0063 + i * 32 + 0: - objectTable[i].x = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 2: - objectTable[i].y = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 4: - objectTable[i].mask = fHandle->readUint16BE(); - // At 0x0063 + i * 32 + 6: - objectTable[i].frame = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 8: - objectTable[i].costume = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 10: - fHandle->read(objectTable[i].name, 20); - // At 0x0063 + i * 32 + 30: - objectTable[i].part = fHandle->readUint16BE(); - } - - // At 0x2043 (i.e. 0x0063 + 255 * 32): - renderer->restorePalette(*fHandle); + // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): + renderer->restorePalette(in); // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): - globalVars.load(*fHandle, NUM_MAX_VAR - 1); + globalVars.load(in, NUM_MAX_VAR); // At 0x2281 (i.e. 0x2083 + 255 * 2): - for (i = 0; i < 16; i++) { - // At 0x2281 + i * 2: - zoneData[i] = fHandle->readUint16BE(); - } + loadZoneData(in); // At 0x22A1 (i.e. 0x2281 + 16 * 2): - for (i = 0; i < 4; i++) { - // At 0x22A1 + i * 2: - commandVar3[i] = fHandle->readUint16BE(); - } + loadCommandVariables(in); // At 0x22A9 (i.e. 0x22A1 + 4 * 2): - fHandle->read(commandBuffer, 0x50); + in.read(commandBuffer, 0x50); renderer->setCommand(commandBuffer); // At 0x22F9 (i.e. 0x22A9 + 0x50): - renderer->_cmdY = fHandle->readUint16BE(); + renderer->_cmdY = in.readUint16BE(); // At 0x22FB: - bgVar0 = fHandle->readUint16BE(); + bgVar0 = in.readUint16BE(); // At 0x22FD: - allowPlayerInput = fHandle->readUint16BE(); + allowPlayerInput = in.readUint16BE(); // At 0x22FF: - playerCommand = fHandle->readSint16BE(); + playerCommand = in.readSint16BE(); // At 0x2301: - commandVar1 = fHandle->readSint16BE(); + commandVar1 = in.readSint16BE(); // At 0x2303: - isDrawCommandEnabled = fHandle->readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); // At 0x2305: - var5 = fHandle->readUint16BE(); + var5 = in.readUint16BE(); // At 0x2307: - var4 = fHandle->readUint16BE(); + var4 = in.readUint16BE(); // At 0x2309: - var3 = fHandle->readUint16BE(); + var3 = in.readUint16BE(); // At 0x230B: - var2 = fHandle->readUint16BE(); + var2 = in.readUint16BE(); // At 0x230D: - commandVar2 = fHandle->readSint16BE(); + commandVar2 = in.readSint16BE(); // At 0x230F: - renderer->_messageBg = fHandle->readUint16BE(); + renderer->_messageBg = in.readUint16BE(); // At 0x2311: - fHandle->readUint16BE(); + in.readUint16BE(); // At 0x2313: - fHandle->readUint16BE(); + in.readUint16BE(); // At 0x2315: - loadResourcesFromSave(*fHandle, broken); - - // TODO: handle screen params (really required ?) - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, true); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, false); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadOverlayFromSave(*fHandle); - } - - loadBgIncrustFromSave(*fHandle); + loadResourcesFromSave(in, saveGameFormat); - delete fHandle; + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); if (strlen(currentMsgName)) { loadMsg(currentMsgName); } - setMouseCursor(MOUSE_CURSOR_NORMAL); - if (strlen(currentDatName)) { /* i = saveVar2; saveVar2 = 0; @@ -630,137 +1001,139 @@ bool CineEngine::makeLoad(char *saveName) { }*/ } - return true; + return !in.ioFailed(); } -/*! \todo Add support for saving the zoneQuery table (Operation Stealth specific) - */ -void makeSave(char *saveFileName) { - int16 i; - Common::OutSaveFile *fHandle; - - fHandle = g_saveFileMan->openForSaving(saveFileName); +bool CineEngine::makeLoad(char *saveName) { + Common::SharedPtr<Common::InSaveFile> saveFile(g_saveFileMan->openForLoading(saveName)); - if (!fHandle) { - drawString(otherMessages[1], 0); + if (!saveFile) { + drawString(otherMessages[0], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); - return; - } - - fHandle->writeUint16BE(currentDisk); - fHandle->write(currentPartName, 13); - fHandle->write(currentDatName, 13); - fHandle->writeUint16BE(saveVar2); - fHandle->write(currentPrcName, 13); - fHandle->write(currentRelName, 13); - fHandle->write(currentMsgName, 13); - renderer->saveBg(*fHandle); - fHandle->write(currentCtName, 13); - - fHandle->writeUint16BE(0xFF); - fHandle->writeUint16BE(0x20); - - for (i = 0; i < 255; i++) { - fHandle->writeUint16BE(objectTable[i].x); - fHandle->writeUint16BE(objectTable[i].y); - fHandle->writeUint16BE(objectTable[i].mask); - fHandle->writeUint16BE(objectTable[i].frame); - fHandle->writeUint16BE(objectTable[i].costume); - fHandle->write(objectTable[i].name, 20); - fHandle->writeUint16BE(objectTable[i].part); - } - - renderer->savePalette(*fHandle); - - globalVars.save(*fHandle, NUM_MAX_VAR - 1); - - for (i = 0; i < 16; i++) { - fHandle->writeUint16BE(zoneData[i]); + return false; } - for (i = 0; i < 4; i++) { - fHandle->writeUint16BE(commandVar3[i]); + setMouseCursor(MOUSE_CURSOR_DISK); + + uint32 saveSize = saveFile->size(); + // TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format. + if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it + // Can't get information about the savefile's size so let's try + // reading as much as we can from the file up to a predefined upper limit. + // + // Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each): + // With 256 global scripts, object scripts, overlays and background incrusts: + // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB + // With 512 global scripts, object scripts, overlays and background incrusts: + // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB + // + // I think it extremely unlikely that there would be over 512 global scripts, object scripts, + // overlays and background incrusts so 256kB seems like quite a safe upper limit. + // NOTE: If the savegame format is changed then this value might have to be re-evaluated! + // Hopefully devices with more limited memory can also cope with this memory allocation. + saveSize = 256 * 1024; + } + Common::SharedPtr<Common::MemoryReadStream> in(saveFile->readStream(saveSize)); + + // Try to detect the used savegame format + enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in); + + // Handle problematic savegame formats + bool load = true; // Should we try to load the savegame? + bool result = false; + if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) { + // One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but + // that's not implemented here because it was never used in a stable + // release of ScummVM but only during development (From revision 31453, + // which introduced the problem, until revision 32073, which fixed it). + // Therefore be bail out if we detect this particular savegame format. + warning("Detected a known broken savegame format, not loading savegame"); + load = false; // Don't load the savegame + } else if (saveGameFormat == ANIMSIZE_UNKNOWN) { + // If we can't detect the savegame format + // then let's try the default format and hope for the best. + warning("Couldn't detect the used savegame format, trying default savegame format. Things may break"); + saveGameFormat = ANIMSIZE_30_PTRS_INTACT; + } + + if (load) { + // Reset the engine's state + resetEngine(); + + if (saveGameFormat == TEMP_OS_FORMAT) { + // Load the temporary Operation Stealth savegame format + result = loadTempSaveOS(*in); + } else { + // Load the plain Future Wars savegame format + result = loadPlainSaveFW(*in, saveGameFormat); + } } - fHandle->write(commandBuffer, 0x50); - - fHandle->writeUint16BE(renderer->_cmdY); - - fHandle->writeUint16BE(bgVar0); - fHandle->writeUint16BE(allowPlayerInput); - fHandle->writeUint16BE(playerCommand); - fHandle->writeUint16BE(commandVar1); - fHandle->writeUint16BE(isDrawCommandEnabled); - fHandle->writeUint16BE(var5); - fHandle->writeUint16BE(var4); - fHandle->writeUint16BE(var3); - fHandle->writeUint16BE(var2); - fHandle->writeUint16BE(commandVar2); - - fHandle->writeUint16BE(renderer->_messageBg); - - fHandle->writeUint16BE(0xFF); - fHandle->writeUint16BE(0x1E); + setMouseCursor(MOUSE_CURSOR_NORMAL); - for (i = 0; i < NUM_MAX_ANIMDATA; i++) { - animDataTable[i].save(*fHandle); - } + return result; +} - fHandle->writeUint16BE(0); // Screen params, unhandled - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); +void CineEngine::makeSaveFW(Common::OutSaveFile &out) { + out.writeUint16BE(currentDisk); + out.write(currentPartName, 13); + out.write(currentDatName, 13); + out.writeUint16BE(saveVar2); + out.write(currentPrcName, 13); + out.write(currentRelName, 13); + out.write(currentMsgName, 13); + renderer->saveBgNames(out); + out.write(currentCtName, 13); + + saveObjectTable(out); + renderer->savePalette(out); + globalVars.save(out, NUM_MAX_VAR); + saveZoneData(out); + saveCommandVariables(out); + out.write(commandBuffer, 0x50); + + out.writeUint16BE(renderer->_cmdY); + out.writeUint16BE(bgVar0); + out.writeUint16BE(allowPlayerInput); + out.writeUint16BE(playerCommand); + out.writeUint16BE(commandVar1); + out.writeUint16BE(isDrawCommandEnabled); + out.writeUint16BE(var5); + out.writeUint16BE(var4); + out.writeUint16BE(var3); + out.writeUint16BE(var2); + out.writeUint16BE(commandVar2); + out.writeUint16BE(renderer->_messageBg); + + saveAnimDataTable(out); + saveScreenParams(out); + + saveGlobalScripts(out); + saveObjectScripts(out); + saveOverlayList(out); + saveBgIncrustList(out); +} - { - ScriptList::iterator it; - fHandle->writeUint16BE(globalScripts.size()); - for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { - (*it)->save(*fHandle); - } +void CineEngine::makeSave(char *saveFileName) { + Common::SharedPtr<Common::OutSaveFile> fHandle(g_saveFileMan->openForSaving(saveFileName)); - fHandle->writeUint16BE(objectScripts.size()); - for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { - (*it)->save(*fHandle); - } - } + setMouseCursor(MOUSE_CURSOR_DISK); - { - Common::List<overlay>::iterator it; - - fHandle->writeUint16BE(overlayList.size()); - - for (it = overlayList.begin(); it != overlayList.end(); ++it) { - fHandle->writeUint32BE(0); - fHandle->writeUint32BE(0); - fHandle->writeUint16BE(it->objIdx); - fHandle->writeUint16BE(it->type); - fHandle->writeSint16BE(it->x); - fHandle->writeSint16BE(it->y); - fHandle->writeSint16BE(it->width); - fHandle->writeSint16BE(it->color); + if (!fHandle) { + drawString(otherMessages[1], 0); + waitPlayerInput(); + // restoreScreen(); + checkDataDisk(-1); + } else { + if (g_cine->getGameType() == GType_FW) { + makeSaveFW(*fHandle); + } else { + makeSaveOS(*fHandle); } } - Common::List<BGIncrust>::iterator it; - fHandle->writeUint16BE(bgIncrustList.size()); - - for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { - fHandle->writeUint32BE(0); // next - fHandle->writeUint32BE(0); // unkPtr - fHandle->writeUint16BE(it->objIdx); - fHandle->writeUint16BE(it->param); - fHandle->writeUint16BE(it->x); - fHandle->writeUint16BE(it->y); - fHandle->writeUint16BE(it->frame); - fHandle->writeUint16BE(it->part); - } - - delete fHandle; - setMouseCursor(MOUSE_CURSOR_NORMAL); } @@ -901,6 +1274,89 @@ void CineEngine::makeSystemMenu(void) { } } +/** + * Save an Operation Stealth type savegame. WIP! + * + * NOTE: This is going to be very much a work in progress so the Operation Stealth's + * savegame formats that are going to be tried are extremely probably not going + * to be supported at all after Operation Stealth becomes officially supported. + * This means that the savegame format will hopefully change to something nicer + * when official support for Operation Stealth begins. + */ +void CineEngine::makeSaveOS(Common::OutSaveFile &out) { + int i; + + // Make a temporary Operation Stealth savegame format chunk header and save it. + ChunkHeader header; + header.id = TEMP_OS_FORMAT_ID; + header.version = CURRENT_OS_SAVE_VER; + header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it. + writeChunkHeader(out, header); + + // Start outputting the plain savegame data right after the chunk header. + out.writeUint16BE(currentDisk); + out.write(currentPartName, 13); + out.write(currentPrcName, 13); + out.write(currentRelName, 13); + out.write(currentMsgName, 13); + renderer->saveBgNames(out); + out.write(currentCtName, 13); + + saveObjectTable(out); + renderer->savePalette(out); + globalVars.save(out, NUM_MAX_VAR); + saveZoneData(out); + saveCommandVariables(out); + out.write(commandBuffer, 0x50); + saveZoneQuery(out); + + // FIXME: Save a proper name here, saving an empty string currently. + // 0x2925: Current music name (String, 13 bytes). + for (i = 0; i < 13; i++) { + out.writeByte(0); + } + // FIXME: Save proper value for this variable, currently writing zero + // 0x2932: Is music loaded? (Uint16BE, Boolean). + out.writeUint16BE(0); + // FIXME: Save proper value for this variable, currently writing zero + // 0x2934: Is music playing? (Uint16BE, Boolean). + out.writeUint16BE(0); + + out.writeUint16BE(renderer->_cmdY); + out.writeUint16BE(0); // Some unknown variable that seems to always be zero + out.writeUint16BE(allowPlayerInput); + out.writeUint16BE(playerCommand); + out.writeUint16BE(commandVar1); + out.writeUint16BE(isDrawCommandEnabled); + out.writeUint16BE(var5); + out.writeUint16BE(var4); + out.writeUint16BE(var3); + out.writeUint16BE(var2); + out.writeUint16BE(commandVar2); + out.writeUint16BE(renderer->_messageBg); + + // FIXME: Save proper value for this variable, currently writing zero. + // An unknown variable at 0x295E: adBgVar1 (Uint16BE). + out.writeUint16BE(0); + out.writeSint16BE(currentAdditionalBgIdx); + out.writeSint16BE(currentAdditionalBgIdx2); + // FIXME: Save proper value for this variable, currently writing zero. + // 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift. + out.writeUint16BE(0); + // FIXME: Save proper value for this variable, currently writing zero. + // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0? + out.writeUint16BE(0); + out.writeUint16BE(disableSystemMenu); + + saveAnimDataTable(out); + saveScreenParams(out); + saveGlobalScripts(out); + saveObjectScripts(out); + saveSeqList(out); + saveOverlayList(out); + saveBgIncrustList(out); +} + void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) { gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom @@ -1567,7 +2023,7 @@ void checkForPendingDataLoad(void) { // fixes a crash when failing copy protection in Amiga or Atari ST // versions of Future Wars. if (loadPrcOk) { - addScriptToList0(1); + addScriptToGlobalScripts(1); } else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) { // We only show an error here for other files than the file that // is loaded if copy protection fails (i.e. L201.ANI). @@ -1740,6 +2196,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele const int8 *ptr2; int16 di; + debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d", + param1, objIdx, ptr, element.var8, element.var14, param3); + // In the original an error string is set and 0 is returned if the following doesn't hold assert(ptr); @@ -1845,6 +2304,19 @@ void processSeqListElement(SeqListElement &element) { int16 var_10; int16 var_4; int16 var_2; + + // Initial interpretations for variables addressed through ptr1 (8-bit addressing): + // These may be inaccurate! + // 0: ? + // 1: xRadius + // 2: yRadius + // 3: ? + // 4: xAdd + // 5: yAdd + // 6: ? + // 7: ? + // After this come (At least at positions 0, 1 and 3 in 16-bit addressing) + // 16-bit big-endian values used for addressing through ptr1. if (element.var12 < element.var10) { element.var12++; diff --git a/engines/cine/various.h b/engines/cine/various.h index 3befde7eb8..5f24d502be 100644 --- a/engines/cine/various.h +++ b/engines/cine/various.h @@ -44,7 +44,7 @@ extern bool inMenu; struct SeqListElement { int16 var4; - uint16 objIdx; + uint16 objIdx; ///< Is this really unsigned? int16 var8; int16 frame; int16 varC; |