aboutsummaryrefslogtreecommitdiff
path: root/engines/cine
diff options
context:
space:
mode:
authorChristopher Page2008-08-04 22:34:07 +0000
committerChristopher Page2008-08-04 22:34:07 +0000
commit4198ee962399305a4a158b1f43224c00e2e04a1b (patch)
treeeea375b5cb471509df2e2c5d9123d963a62403c6 /engines/cine
parenta51f45407659bba43254b466d20b6af2e8f17ffd (diff)
parent4f5479ee744ac6b419cdf7ec1e96fbf7c83d36ef (diff)
downloadscummvm-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.cpp210
-rw-r--r--engines/cine/anim.h60
-rw-r--r--engines/cine/bg.cpp2
-rw-r--r--engines/cine/bg.h3
-rw-r--r--engines/cine/bg_list.cpp2
-rw-r--r--engines/cine/bg_list.h2
-rw-r--r--engines/cine/cine.h6
-rw-r--r--engines/cine/gfx.cpp66
-rw-r--r--engines/cine/gfx.h12
-rw-r--r--engines/cine/main_loop.cpp31
-rw-r--r--engines/cine/object.h2
-rw-r--r--engines/cine/part.cpp4
-rw-r--r--engines/cine/script.h20
-rw-r--r--engines/cine/script_fw.cpp54
-rw-r--r--engines/cine/script_os.cpp6
-rw-r--r--engines/cine/various.cpp1132
-rw-r--r--engines/cine/various.h2
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;