diff options
Diffstat (limited to 'engines/cine')
-rw-r--r-- | engines/cine/anim.cpp | 74 | ||||
-rw-r--r-- | engines/cine/anim.h | 57 | ||||
-rw-r--r-- | engines/cine/bg.cpp | 2 | ||||
-rw-r--r-- | engines/cine/bg.h | 3 | ||||
-rw-r--r-- | engines/cine/bg_list.cpp | 4 | ||||
-rw-r--r-- | engines/cine/bg_list.h | 2 | ||||
-rw-r--r-- | engines/cine/cine.cpp | 5 | ||||
-rw-r--r-- | engines/cine/cine.h | 8 | ||||
-rw-r--r-- | engines/cine/gfx.cpp | 81 | ||||
-rw-r--r-- | engines/cine/gfx.h | 12 | ||||
-rw-r--r-- | engines/cine/main_loop.cpp | 39 | ||||
-rw-r--r-- | engines/cine/object.cpp | 75 | ||||
-rw-r--r-- | engines/cine/object.h | 10 | ||||
-rw-r--r-- | engines/cine/pal.h | 2 | ||||
-rw-r--r-- | engines/cine/part.cpp | 4 | ||||
-rw-r--r-- | engines/cine/prc.cpp | 9 | ||||
-rw-r--r-- | engines/cine/prc.h | 2 | ||||
-rw-r--r-- | engines/cine/script.h | 30 | ||||
-rw-r--r-- | engines/cine/script_fw.cpp | 88 | ||||
-rw-r--r-- | engines/cine/script_os.cpp | 87 | ||||
-rw-r--r-- | engines/cine/sound.cpp | 1 | ||||
-rw-r--r-- | engines/cine/texte.cpp | 10 | ||||
-rw-r--r-- | engines/cine/texte.h | 5 | ||||
-rw-r--r-- | engines/cine/unpack.cpp | 2 | ||||
-rw-r--r-- | engines/cine/various.cpp | 1385 | ||||
-rw-r--r-- | engines/cine/various.h | 8 |
26 files changed, 1424 insertions, 581 deletions
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp index 055eb733c3..8dbccebedf 100644 --- a/engines/cine/anim.cpp +++ b/engines/cine/anim.cpp @@ -769,19 +769,18 @@ void loadAbs(const char *resourceName, uint16 idx) { /*! \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) { +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) { int16 currentAnim, foundFileIdx; int8 isMask = 0, isSpl = 0; byte *dataPtr, *ptr; char *animName, part[256]; byte transparentColor = 0; - AnimData *currentPtr; AnimHeaderStruct animHeader; uint16 width, height, bpp, var1; @@ -791,30 +790,46 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { 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(); + for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim += animHeader.numFrames) { + // Initialize the number of frames variable to a sane number. + // This is needed when using continue later in this function. + animHeader.numFrames = 1; + + // 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) { continue; } + // Alright, the animation entry looks to be valid so let's start handling it... if (strcmp(currentPartName, name)) { closePart(); loadPart(name); @@ -823,13 +838,14 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { animName = partBuffer[foundFileIdx].partName; ptr = dataPtr = readBundleFile(foundFileIdx); + // isSpl and isMask are mutually exclusive cases isSpl = (strstr(animName, ".SPL")) ? 1 : 0; isMask = (strstr(animName, ".MSK")) ? 1 : 0; if (isSpl) { width = (uint16) partBuffer[foundFileIdx].unpackedSize; height = 1; - frame = 0; + animHeader.numFrames = 1; type = ANIM_RAW; } else { Common::MemoryReadStream readS(ptr, 0x16); @@ -843,25 +859,35 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { 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; - } + loadRelatedPalette(animName); + transparentColor = getAnimTransparentColor(animName); + // Make sure we load at least one frame and also that we + // don't overflow the animDataTable by writing beyond its end. + animHeader.numFrames = CLIP<uint16>(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim); + + // Load the frames + for (frame = 0; frame < animHeader.numFrames; frame++) { + // 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; } + + // Load a single frame + animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor); } - ptr += frame * width * height; - currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor); free(dataPtr); } 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..b0ce55f7ee 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; @@ -101,7 +156,7 @@ 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); +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 cf25f1d355..fddca078e5 100644 --- a/engines/cine/bg_list.cpp +++ b/engines/cine/bg_list.cpp @@ -63,6 +63,7 @@ void addSpriteFilledToBGList(int16 objIdx) { void createBgIncrustListElement(int16 objIdx, int16 param) { BGIncrust tmp; + tmp.unkPtr = 0; tmp.objIdx = objIdx; tmp.param = param; tmp.x = objectTable[objIdx].x; @@ -82,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(); @@ -90,6 +91,7 @@ void loadBgIncrustFromSave(Common::InSaveFile &fHandle) { fHandle.readUint32BE(); fHandle.readUint32BE(); + tmp.unkPtr = 0; tmp.objIdx = fHandle.readUint16BE(); tmp.param = fHandle.readUint16BE(); tmp.x = fHandle.readUint16BE(); 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.cpp b/engines/cine/cine.cpp index efc33fadaf..f6778b6457 100644 --- a/engines/cine/cine.cpp +++ b/engines/cine/cine.cpp @@ -42,7 +42,6 @@ #include "cine/sound.h" #include "cine/various.h" - namespace Cine { Sound *g_sound; @@ -70,6 +69,10 @@ CineEngine::~CineEngine() { freeErrmessDat(); } Common::clearAllSpecialDebugLevels(); + + free(palPtr); + free(partBuffer); + free(textDataPtr); } int CineEngine::init() { diff --git a/engines/cine/cine.h b/engines/cine/cine.h index 710840c17e..eaae555812 100644 --- a/engines/cine/cine.h +++ b/engines/cine/cine.h @@ -94,10 +94,17 @@ public: Common::StringList _volumeResourceFiles; StringPtrHashMap _volumeEntriesMap; + TextHandler _textHandler; 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(); @@ -107,6 +114,7 @@ private: extern CineEngine *g_cine; #define BOOT_PRC_NAME "AUTO00.PRC" +#define COPY_PROT_FAIL_PRC_NAME "L201.ANI" enum { VAR_MOUSE_X_MODE = 253, diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp index 47446f2410..cbddf0fc59 100644 --- a/engines/cine/gfx.cpp +++ b/engines/cine/gfx.cpp @@ -337,7 +337,7 @@ int FWRenderer::drawChar(char character, int x, int y) { x += 5; } else if ((width = fontParamTable[(unsigned char)character].characterWidth)) { idx = fontParamTable[(unsigned char)character].characterIdx; - drawSpriteRaw(textTable[idx][0], textTable[idx][1], 16, 8, _backBuffer, x, y); + drawSpriteRaw(g_cine->_textHandler.textTable[idx][0], g_cine->_textHandler.textTable[idx][1], 16, 8, _backBuffer, x, y); x += width + 1; } @@ -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 @@ -938,7 +987,7 @@ int OSRenderer::drawChar(char character, int x, int y) { x += 5; } else if ((width = fontParamTable[(unsigned char)character].characterWidth)) { idx = fontParamTable[(unsigned char)character].characterIdx; - drawSpriteRaw2(textTable[idx][0], 0, 16, 8, _backBuffer, x, y); + drawSpriteRaw2(g_cine->_textHandler.textTable[idx][0], 0, 16, 8, _backBuffer, x, y); x += width + 1; } @@ -969,6 +1018,7 @@ void OSRenderer::drawBackground() { /*! \brief Draw one overlay * \param it Overlay info + * \todo Add handling of type 22 overlays */ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) { int len; @@ -979,6 +1029,9 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) { switch (it->type) { // color sprite case 0: + if (objectTable[it->objIdx].frame < 0) { + break; + } sprite = animDataTable + objectTable[it->objIdx].frame; len = sprite->_realWidth * sprite->_height; mask = new byte[len]; @@ -988,6 +1041,13 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) { delete[] mask; break; + // bitmap + case 4: + if (objectTable[it->objIdx].frame >= 0) { + FWRenderer::renderOverlay(it); + } + break; + // masked background case 20: assert(it->objIdx < NUM_MAX_OBJECT); @@ -1236,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 */ @@ -1259,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 cfb828cf3c..e5e670c973 100644 --- a/engines/cine/main_loop.cpp +++ b/engines/cine/main_loop.cpp @@ -179,6 +179,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; uint16 quitFlag; @@ -186,6 +200,7 @@ void CineEngine::mainLoop(int bootScriptIdx) { uint16 mouseButton; quitFlag = 0; + exitEngine = 0; if (_preLoad == false) { resetBgIncrustList(); @@ -194,7 +209,7 @@ void CineEngine::mainLoop(int bootScriptIdx) { errorVar = 0; - addScriptToList0(bootScriptIdx); + addScriptToGlobalScripts(bootScriptIdx); menuVar = 0; @@ -234,13 +249,25 @@ void CineEngine::mainLoop(int bootScriptIdx) { do { stopMusicAfterFadeOut(); di = executePlayerInput(); + + // Clear the zoneQuery table (Operation Stealth specific) + if (g_cine->getGameType() == Cine::GType_OS) { + for (uint i = 0; i < NUM_MAX_ZONE; i++) { + zoneQuery[i] = 0; + } + } - 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.cpp b/engines/cine/object.cpp index 7666f05352..c02e01c8ce 100644 --- a/engines/cine/object.cpp +++ b/engines/cine/object.cpp @@ -99,21 +99,36 @@ int removeOverlay(uint16 objIdx, uint16 param) { /*! \brief Add new overlay sprite to the list * \param objIdx Associate the overlay with this object - * \param param Type of new overlay + * \param type Type of new overlay * \todo Why are x, y, width and color left uninitialized? */ -void addOverlay(uint16 objIdx, uint16 param) { +void addOverlay(uint16 objIdx, uint16 type) { Common::List<overlay>::iterator it; overlay tmp; for (it = overlayList.begin(); it != overlayList.end(); ++it) { + // This is done for both Future Wars and Operation Stealth if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask) { break; } + + // There are additional checks in Operation Stealth's implementation + if (g_cine->getGameType() == Cine::GType_OS && (it->type == 2 || it->type == 3)) { + break; + } + } + + // In Operation Stealth's implementation we might bail out early + if (g_cine->getGameType() == Cine::GType_OS && it != overlayList.end() && it->objIdx == objIdx && it->type == type) { + return; } tmp.objIdx = objIdx; - tmp.type = param; + tmp.type = type; + tmp.x = 0; + tmp.y = 0; + tmp.width = 0; + tmp.color = 0; overlayList.insert(it, tmp); } @@ -122,24 +137,22 @@ void addOverlay(uint16 objIdx, uint16 param) { * \param objIdx Associate the overlay with this object * \param param source background index */ -void addGfxElementA0(int16 objIdx, int16 param) { +void addGfxElement(int16 objIdx, int16 param, int16 type) { Common::List<overlay>::iterator it; overlay tmp; for (it = overlayList.begin(); it != overlayList.end(); ++it) { - // wtf?! - if (objectTable[it->objIdx].mask == objectTable[objIdx].mask && - (it->type == 2 || it->type == 3)) { + if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask || it->type == 2 || it->type == 3) { break; } } - if (it != overlayList.end() && it->objIdx == objIdx && it->type == 20 && it->x == param) { + if (it != overlayList.end() && it->objIdx == objIdx && it->type == type && it->x == param) { return; } tmp.objIdx = objIdx; - tmp.type = 20; + tmp.type = type; tmp.x = param; tmp.y = 0; tmp.width = 0; @@ -153,11 +166,11 @@ void addGfxElementA0(int16 objIdx, int16 param) { * \param param Remove overlay using this background * \todo Check that it works */ -void removeGfxElementA0(int16 objIdx, int16 param) { +void removeGfxElement(int16 objIdx, int16 param, int16 type) { Common::List<overlay>::iterator it; for (it = overlayList.begin(); it != overlayList.end(); ++it) { - if (it->objIdx == objIdx && it->type == 20 && it->x == param) { + if (it->objIdx == objIdx && it->type == type && it->x == param) { overlayList.erase(it); return; } @@ -170,8 +183,12 @@ void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint1 objectTable[objIdx].mask = param3; objectTable[objIdx].frame = param4; - if (removeOverlay(objIdx, 0)) { - addOverlay(objIdx, 0); + if (g_cine->getGameType() == Cine::GType_OS) { + resetGfxEntityEntry(objIdx); + } else { // Future Wars + if (removeOverlay(objIdx, 0)) { + addOverlay(objIdx, 0); + } } } @@ -199,9 +216,12 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) { case 3: objectTable[objIdx].mask = newValue; - // TODO: Check this part against disassembly - if (removeOverlay(objIdx, 0)) { - addOverlay(objIdx, 0); + if (g_cine->getGameType() == Cine::GType_OS) { // Operation Stealth specific + resetGfxEntityEntry(objIdx); + } else { // Future Wars specific + if (removeOverlay(objIdx, 0)) { + addOverlay(objIdx, 0); + } } break; case 4: @@ -221,6 +241,29 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) { } } +/** + * Check if at least one of the range B's endpoints is inside range A, + * not counting the starting and ending points of range A. + * Used at least by Operation Stealth's opcode 0x8D i.e. 141. + */ +bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd) { + return (bStart > aStart && bStart < aEnd) || (bEnd > aStart && bEnd < aEnd); +} + +uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2) { + assert(objIdx1 < NUM_MAX_OBJECT && objIdx2 < NUM_MAX_OBJECT); + const objectStruct &obj1 = objectTable[objIdx1]; + const objectStruct &obj2 = objectTable[objIdx2]; + + if (compareRanges(obj1.x, obj1.x + xAdd1, obj2.x, obj2.x + xAdd2) && + compareRanges(obj1.y, obj1.y + yAdd1, obj2.y, obj2.y + yAdd2) && + compareRanges(obj1.mask, obj1.mask + maskAdd1, obj2.mask, obj2.mask + maskAdd2)) { + return kCmpEQ; + } else { + return 0; + } +} + uint16 compareObjectParam(byte objIdx, byte type, int16 value) { uint16 compareResult = 0; int16 objectParam = getObjectParam(objIdx, type); diff --git a/engines/cine/object.h b/engines/cine/object.h index e7de39649d..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]; @@ -60,15 +60,17 @@ void loadObject(char *pObjectName); void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4); void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue); -void addOverlay(uint16 objIdx, uint16 param); +void addOverlay(uint16 objIdx, uint16 type); int removeOverlay(uint16 objIdx, uint16 param); -void addGfxElementA0(int16 objIdx, int16 param); -void removeGfxElementA0(int16 objIdx, int16 param); +void addGfxElement(int16 objIdx, int16 param, int16 type); +void removeGfxElement(int16 objIdx, int16 param, int16 type); int16 getObjectParam(uint16 objIdx, uint16 paramIdx); void addObjectParam(byte objIdx, byte paramIdx, int16 newValue); void subObjectParam(byte objIdx, byte paramIdx, int16 newValue); +bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd); +uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2); uint16 compareObjectParam(byte objIdx, byte param1, int16 param2); } // End of namespace Cine diff --git a/engines/cine/pal.h b/engines/cine/pal.h index 70fcc0d98a..768cf0d27d 100644 --- a/engines/cine/pal.h +++ b/engines/cine/pal.h @@ -34,6 +34,8 @@ struct PalEntry { byte pal2[16]; }; +extern PalEntry *palPtr; + void loadPal(const char *fileName); void loadRelatedPalette(const char *fileName); 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/prc.cpp b/engines/cine/prc.cpp index 402c97b1a6..27b1044620 100644 --- a/engines/cine/prc.cpp +++ b/engines/cine/prc.cpp @@ -40,8 +40,9 @@ ScriptList objectScripts; /*! \todo Is script size of 0 valid? * \todo Fix script dump code + * @return Was the loading successful? */ -void loadPrc(const char *pPrcName) { +bool loadPrc(const char *pPrcName) { byte i; uint16 numScripts; byte *scriptPtr, *dataPtr; @@ -52,9 +53,9 @@ void loadPrc(const char *pPrcName) { scriptTable.clear(); // This is copy protection. Used to hang the machine - if (!scumm_stricmp(pPrcName, "L201.ANI")) { + if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) { exitEngine = 1; - return; + return false; } checkDataDisk(-1); @@ -107,6 +108,8 @@ void loadPrc(const char *pPrcName) { } } #endif + + return true; } } // End of namespace Cine diff --git a/engines/cine/prc.h b/engines/cine/prc.h index f5129d28b1..05bb240372 100644 --- a/engines/cine/prc.h +++ b/engines/cine/prc.h @@ -31,7 +31,7 @@ namespace Cine { extern ScriptList globalScripts; extern ScriptList objectScripts; -void loadPrc(const char *pPrcName); +bool loadPrc(const char *pPrcName); } // End of namespace Cine diff --git a/engines/cine/script.h b/engines/cine/script.h index eeac0e8809..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(); @@ -237,7 +237,7 @@ protected: int o2_playSample(); int o2_playSampleAlt(); int o2_op81(); - int o2_op82(); + int o2_modifySeqListElement(); int o2_isSeqRunning(); int o2_gotoIfSupNearest(); int o2_gotoIfSupEquNearest(); @@ -258,10 +258,10 @@ protected: int o2_useBgScroll(); int o2_setAdditionalBgVScroll(); int o2_op9F(); - int o2_addGfxElementA0(); - int o2_removeGfxElementA0(); - int o2_opA2(); - int o2_opA3(); + int o2_addGfxElementType20(); + int o2_removeGfxElementType20(); + int o2_addGfxElementType21(); + int o2_removeGfxElementType21(); int o2_loadMask22(); int o2_unloadMask22(); @@ -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 845120c99e..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++) { @@ -1019,6 +1025,20 @@ int FWScript::o1_divVar() { } int FWScript::o1_compareVar() { + // WORKAROUND: A workaround for a script bug in script file CODE2.PRC + // in at least some of the Amiga and Atari ST versions of Future Wars. + // Fixes bug #2016647 (FW: crash with italian amiga version). A local + // variable 251 is compared against value 0 although it's quite apparent + // from the context in the script that instead global variable 251 should + // be compared against value 0. So looks like someone made a typo when + // making the scripts. Therefore we change that particular comparison + // from using the local variable 251 to using the global variable 251. + if (g_cine->getGameType() == Cine::GType_FW && scumm_stricmp(currentPrcName, "CODE2.PRC") == 0 && + (g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) && + _script.getByte(_pos) == 251 && _script.getByte(_pos + 1) == 0 && _script.getWord(_pos + 2) == 0) { + return o1_compareGlobalVar(); + } + byte varIdx = getNextByte(); byte varType = getNextByte(); @@ -1259,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; } @@ -1385,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; @@ -1554,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; @@ -1732,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); @@ -1764,18 +1786,32 @@ int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneI int16 lx = objectTable[objIdx].x + x; int16 ly = objectTable[objIdx].y + y; int16 idx; + int16 result = 0; for (int16 i = 0; i < numZones; i++) { idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320); - assert(idx >= 0 && idx <= NUM_MAX_ZONE); + assert(idx >= 0 && idx < NUM_MAX_ZONE); + + // The zoneQuery table is updated here only in Operation Stealth + if (g_cine->getGameType() == Cine::GType_OS) { + if (zoneData[idx] < NUM_MAX_ZONE) { + zoneQuery[zoneData[idx]]++; + } + } if (zoneData[idx] == zoneIdx) { - return 1; + result = 1; + // Future Wars breaks out early on the first match, but + // Operation Stealth doesn't because it needs to update + // the zoneQuery table for the whole loop's period. + if (g_cine->getGameType() == Cine::GType_FW) { + break; + } } } - return 0; + return result; } uint16 compareVars(int16 a, int16 b) { @@ -1792,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) { @@ -1803,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) { @@ -1814,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) { } //////////////////////////////////// @@ -2352,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; } @@ -2502,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; } @@ -2917,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 319fca5d3c..a764281758 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. */ @@ -202,7 +202,7 @@ const Opcode OSScript::_opcodeTable[] = { /* 80 */ { &FWScript::o2_removeSeq, "bb" }, { &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_op82, "bbwwb" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_modifySeqListElement, "bbwwb" }, { &FWScript::o2_isSeqRunning, "bb" }, /* 84 */ { &FWScript::o2_gotoIfSupNearest, "b" }, @@ -240,10 +240,10 @@ const Opcode OSScript::_opcodeTable[] = { { &FWScript::o2_setAdditionalBgVScroll, "c" }, { &FWScript::o2_op9F, "ww" }, /* TODO: Name this opcode properly. */ /* A0 */ - { &FWScript::o2_addGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_removeGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_opA2, "ww" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_opA3, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_addGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_removeGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_addGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_removeGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */ /* A4 */ { &FWScript::o2_loadMask22, "b" }, /* TODO: Name this opcode properly. */ { &FWScript::o2_unloadMask22, "b" }, /* TODO: Name this opcode properly. */ @@ -442,6 +442,7 @@ int FWScript::o2_removeSeq() { } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op81() { warning("STUB: o2_op81()"); @@ -449,23 +450,25 @@ int FWScript::o2_op81() { return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_op82() { +int FWScript::o2_modifySeqListElement() { byte a = getNextByte(); byte b = getNextByte(); uint16 c = getNextWord(); uint16 d = getNextWord(); byte e = getNextByte(); - warning("STUB: o2_op82(%x, %x, %x, %x, %x)", a, b, c, d, e); + debugC(5, kCineDebugScript, "Line: %d: o2_modifySeqListElement(%d,%d,%d,%d,%d)", _line, a, b, c, d, e); + + modifySeqListElement(a, 0, b, c, d, e); return 0; } +/*! \todo Check whether this opcode's name is backwards (i.e. should it be o2_isSeqNotRunning?) + */ int FWScript::o2_isSeqRunning() { byte a = getNextByte(); byte b = getNextByte(); - debugC(5, kCineDebugScript, "Line: %d: OP83(%d,%d) -> TODO", _line, a, b); + debugC(5, kCineDebugScript, "Line: %d: o2_isSeqRunning(%d,%d)", _line, a, b); if (isSeqRunning(a, 0, b)) { _compare = 1; @@ -593,19 +596,18 @@ int FWScript::o2_stopObjectScript() { return 0; } -/*! \todo Implement this instruction - */ int FWScript::o2_op8D() { - uint16 a = getNextWord(); - uint16 b = getNextWord(); - uint16 c = getNextWord(); - uint16 d = getNextWord(); - uint16 e = getNextWord(); - uint16 f = getNextWord(); - uint16 g = getNextWord(); - uint16 h = getNextWord(); - warning("STUB: o2_op8D(%x, %x, %x, %x, %x, %x, %x, %x)", a, b, c, d, e, f, g, h); - // _currentScriptElement->compareResult = ... + uint16 objIdx1 = getNextWord(); + uint16 xAdd1 = getNextWord(); + uint16 yAdd1 = getNextWord(); + uint16 maskAdd1 = getNextWord(); + uint16 objIdx2 = getNextWord(); + uint16 xAdd2 = getNextWord(); + uint16 yAdd2 = getNextWord(); + uint16 maskAdd2 = getNextWord(); + debugC(5, kCineDebugScript, "Line: %d: o2_op8D(%d, %d, %d, %d, %d, %d, %d, %d)", _line, objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2); + + _compare = compareObjectParamRanges(objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2); return 0; } @@ -649,16 +651,15 @@ int FWScript::o2_loadBg() { return 0; } -/*! \todo Check the current implementation for correctness - */ int FWScript::o2_wasZoneChecked() { byte param = getNextByte(); - _compare = (param < 16 && zoneData[param]); + _compare = (param < NUM_MAX_ZONE && zoneQuery[param]) ? 1 : 0; debugC(5, kCineDebugScript, "Line: %d: o2_wasZoneChecked(%d)", _line, param); return 0; } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op9B() { uint16 a = getNextWord(); @@ -674,6 +675,7 @@ int FWScript::o2_op9B() { } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op9C() { uint16 a = getNextWord(); @@ -713,6 +715,7 @@ int FWScript::o2_setAdditionalBgVScroll() { } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op9F() { warning("o2_op9F()"); @@ -721,42 +724,36 @@ int FWScript::o2_op9F() { return 0; } -int FWScript::o2_addGfxElementA0() { +int FWScript::o2_addGfxElementType20() { uint16 param1 = getNextWord(); uint16 param2 = getNextWord(); - debugC(5, kCineDebugScript, "Line: %d: addGfxElementA0(%d,%d)", _line, param1, param2); - addGfxElementA0(param1, param2); + debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType20(%d,%d)", _line, param1, param2); + addGfxElement(param1, param2, 20); return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_removeGfxElementA0() { +int FWScript::o2_removeGfxElementType20() { uint16 idx = getNextWord(); uint16 param = getNextWord(); - warning("STUB? o2_removeGfxElementA0(%x, %x)", idx, param); - removeGfxElementA0(idx, param); + debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType20(%d,%d)", _line, idx, param); + removeGfxElement(idx, param, 20); return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_opA2() { +int FWScript::o2_addGfxElementType21() { uint16 a = getNextWord(); uint16 b = getNextWord(); - warning("STUB: o2_opA2(%x, %x)", a, b); - // addGfxElementA2(); + debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType21(%d,%d)", _line, a, b); + addGfxElement(a, b, 21); return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_opA3() { +int FWScript::o2_removeGfxElementType21() { uint16 a = getNextWord(); uint16 b = getNextWord(); - warning("STUB: o2_opA3(%x, %x)", a, b); - // removeGfxElementA2(); + debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType21(%d,%d)", _line, a, b); + removeGfxElement(a, b, 21); return 0; } diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp index e808de6922..f26032fe98 100644 --- a/engines/cine/sound.cpp +++ b/engines/cine/sound.cpp @@ -249,6 +249,7 @@ AdlibSoundDriver::AdlibSoundDriver(Audio::Mixer *mixer) AdlibSoundDriver::~AdlibSoundDriver() { _mixer->stopHandle(_soundHandle); + OPLDestroy(_opl); } void AdlibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) { diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp index 9b4b83f420..e4fd334926 100644 --- a/engines/cine/texte.cpp +++ b/engines/cine/texte.cpp @@ -31,8 +31,6 @@ namespace Cine { byte *textDataPtr; -byte textTable[256][2][16 * 8]; - const char **failureMessages; const CommandeType *defaultActionCommand; const CommandeType *systemMenu; @@ -77,14 +75,14 @@ void loadTextData(const char *pFileName, byte *pDestinationBuffer) { loadRelatedPalette(pFileName); for (i = 0; i < numCharacters; i++) { - gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 16, 8); - generateMask(textTable[i][0], textTable[i][1], 16 * 8, 0); + gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 16, 8); + generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 16 * 8, 0); tempBuffer += dataSize; } } else { for (i = 0; i < 90; i++) { - gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 8, 8); - generateMask(textTable[i][0], textTable[i][1], 8 * 8, 0); + gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 8, 8); + generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 8 * 8, 0); tempBuffer += 0x40; } } diff --git a/engines/cine/texte.h b/engines/cine/texte.h index ae82832aea..f471c3c49e 100644 --- a/engines/cine/texte.h +++ b/engines/cine/texte.h @@ -34,7 +34,10 @@ namespace Cine { typedef char CommandeType[20]; extern byte *textDataPtr; -extern byte textTable[256][2][16 * 8]; + +struct TextHandler { + byte textTable[256][2][16 * 8]; +}; extern const char **failureMessages; extern const CommandeType *defaultActionCommand; diff --git a/engines/cine/unpack.cpp b/engines/cine/unpack.cpp index dcd3181242..5d85ff6cab 100644 --- a/engines/cine/unpack.cpp +++ b/engines/cine/unpack.cpp @@ -111,7 +111,7 @@ bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen) while (_dst >= _dstBegin && !_error) { /* Bits => Action: - 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..9) + 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..8) 1 1 1 => unpackRawBytes(8 bits + 9) i.e. unpackRawBytes(9..264) 0 1 => copyRelocatedBytes(8 bits, 2) i.e. copyRelocatedBytes(0..255, 2) 1 0 0 => copyRelocatedBytes(9 bits, 3) i.e. copyRelocatedBytes(0..511, 3) diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp index 9b98ddb253..2fcb015fcd 100644 --- a/engines/cine/various.cpp +++ b/engines/cine/various.cpp @@ -95,6 +95,9 @@ int16 saveVar2; byte isInPause = 0; +// TODO: Implement inputVar0's changes in the program +// Currently inputVar0 isn't updated anywhere even though it's used at least in processSeqListElement. +uint16 inputVar0 = 0; byte inputVar1 = 0; uint16 inputVar2 = 0, inputVar3 = 0; @@ -112,6 +115,7 @@ int16 objListTab[20]; uint16 exitEngine; uint16 zoneData[NUM_MAX_ZONE]; +uint16 zoneQuery[NUM_MAX_ZONE]; //!< Only exists in Operation Stealth void stopMusicAfterFadeOut(void) { @@ -132,6 +136,7 @@ void runObjectScript(int16 entryIdx) { */ void addPlayerCommandMessage(int16 cmd) { overlay tmp; + memset(&tmp, 0, sizeof(tmp)); tmp.objIdx = cmd; tmp.type = 3; @@ -224,6 +229,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]; @@ -241,21 +383,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) { @@ -278,7 +542,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(); @@ -294,127 +558,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 - */ -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(); @@ -424,7 +571,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; @@ -458,124 +607,381 @@ 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; + } +} - currentDisk = fHandle->readUint16BE(); +bool loadObjectTable(Common::SeekableReadStream &in) { + in.readUint16BE(); // Entry count + in.readUint16BE(); // Entry size - fHandle->read(currentPartName, 13); - fHandle->read(currentDatName, 13); + 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(); +} - saveVar2 = fHandle->readSint16BE(); +bool loadZoneData(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneData[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} - fHandle->read(currentPrcName, 13); - fHandle->read(currentRelName, 13); - fHandle->read(currentMsgName, 13); - fHandle->read(bgName, 13); - fHandle->read(currentCtName, 13); +bool loadCommandVariables(Common::SeekableReadStream &in) { + for (int i = 0; i < 4; i++) { + commandVar3[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} - checkDataDisk(currentDisk); +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(); +} - if (strlen(currentPartName)) { - loadPart(currentPartName); +bool loadGlobalScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, true); } + return !in.ioFailed(); +} - if (strlen(currentPrcName)) { - loadPrc(currentPrcName); +bool loadObjectScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, false); } + return !in.ioFailed(); +} - if (strlen(currentRelName)) { - loadRel(currentRelName); +bool loadOverlayList(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadOverlayFromSave(in); } + return !in.ioFailed(); +} - if (strlen(bgName)) { - loadBg(bgName); - } +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(); +} - if (strlen(currentCtName)) { - loadCtFW(currentCtName); +bool loadZoneQuery(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneQuery[i] = in.readUint16BE(); } + return !in.ioFailed(); +} - fHandle->readUint16BE(); - fHandle->readUint16BE(); +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { + char musicName[13]; + char bgNames[8][13]; - for (i = 0; i < 255; i++) { - objectTable[i].x = fHandle->readSint16BE(); - objectTable[i].y = fHandle->readSint16BE(); - objectTable[i].mask = fHandle->readUint16BE(); - objectTable[i].frame = fHandle->readSint16BE(); - objectTable[i].costume = fHandle->readSint16BE(); - fHandle->read(objectTable[i].name, 20); - objectTable[i].part = fHandle->readUint16BE(); + // 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; } - renderer->restorePalette(*fHandle); + // 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]); + } - globalVars.load(*fHandle, NUM_MAX_VAR - 1); + // Add backgrounds 1-7 (Uses addBackground) + for (int i = 1; i < 8; i++) { + if (strlen(bgNames[i])) { + addBackground(bgNames[i], i); + } + } - for (i = 0; i < 16; i++) { - zoneData[i] = fHandle->readUint16BE(); + if (strlen(currentCtName)) { + loadCtOS(currentCtName); + } } - for (i = 0; i < 4; i++) { - commandVar3[i] = fHandle->readUint16BE(); + 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); } - fHandle->read(commandBuffer, 0x50); - renderer->setCommand(commandBuffer); + // TODO: Add current music loading and playing here + // TODO: Palette handling? - renderer->_cmdY = fHandle->readUint16BE(); + 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"); + } - bgVar0 = fHandle->readUint16BE(); - allowPlayerInput = fHandle->readUint16BE(); - playerCommand = fHandle->readSint16BE(); - commandVar1 = fHandle->readSint16BE(); - isDrawCommandEnabled = fHandle->readUint16BE(); - var5 = fHandle->readUint16BE(); - var4 = fHandle->readUint16BE(); - var3 = fHandle->readUint16BE(); - var2 = fHandle->readUint16BE(); - commandVar2 = fHandle->readSint16BE(); + return !in.ioFailed(); +} - renderer->_messageBg = fHandle->readUint16BE(); +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { + char bgName[13]; - fHandle->readUint16BE(); - fHandle->readUint16BE(); + // At savefile position 0x0000: + currentDisk = in.readUint16BE(); - loadResourcesFromSave(*fHandle, broken); + // At 0x0002: + in.read(currentPartName, 13); + // At 0x000F: + in.read(currentDatName, 13); - // TODO: handle screen params (really required ?) - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); + // At 0x001C: + saveVar2 = in.readSint16BE(); - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, true); + // At 0x001E: + in.read(currentPrcName, 13); + // At 0x002B: + in.read(currentRelName, 13); + // At 0x0038: + in.read(currentMsgName, 13); + // At 0x0045: + in.read(bgName, 13); + // At 0x0052: + in.read(currentCtName, 13); + + checkDataDisk(currentDisk); + + if (strlen(currentPartName)) { + loadPart(currentPartName); } - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, false); + if (strlen(currentPrcName)) { + loadPrc(currentPrcName); } - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadOverlayFromSave(*fHandle); + if (strlen(currentRelName)) { + loadRel(currentRelName); } - loadBgIncrustFromSave(*fHandle); + if (strlen(bgName)) { + loadBg(bgName); + } - delete fHandle; + if (strlen(currentCtName)) { + loadCtFW(currentCtName); + } + + // At 0x005F: + loadObjectTable(in); + + // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): + renderer->restorePalette(in); + + // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): + globalVars.load(in, NUM_MAX_VAR); + + // At 0x2281 (i.e. 0x2083 + 255 * 2): + loadZoneData(in); + + // At 0x22A1 (i.e. 0x2281 + 16 * 2): + loadCommandVariables(in); + + // At 0x22A9 (i.e. 0x22A1 + 4 * 2): + in.read(commandBuffer, 0x50); + renderer->setCommand(commandBuffer); + + // At 0x22F9 (i.e. 0x22A9 + 0x50): + renderer->_cmdY = in.readUint16BE(); + + // At 0x22FB: + bgVar0 = in.readUint16BE(); + // At 0x22FD: + allowPlayerInput = in.readUint16BE(); + // At 0x22FF: + playerCommand = in.readSint16BE(); + // At 0x2301: + commandVar1 = in.readSint16BE(); + // At 0x2303: + isDrawCommandEnabled = in.readUint16BE(); + // At 0x2305: + var5 = in.readUint16BE(); + // At 0x2307: + var4 = in.readUint16BE(); + // At 0x2309: + var3 = in.readUint16BE(); + // At 0x230B: + var2 = in.readUint16BE(); + // At 0x230D: + commandVar2 = in.readSint16BE(); + + // At 0x230F: + renderer->_messageBg = in.readUint16BE(); + + // At 0x2311: + in.readUint16BE(); + // At 0x2313: + in.readUint16BE(); + + // At 0x2315: + loadResourcesFromSave(in, saveGameFormat); + + 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; @@ -585,135 +991,139 @@ bool CineEngine::makeLoad(char *saveName) { }*/ } - return true; + return !in.ioFailed(); } -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); } @@ -854,6 +1264,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 @@ -1510,12 +2003,22 @@ void mainLoopSub6(void) { void checkForPendingDataLoad(void) { if (newPrcName[0] != 0) { - loadPrc(newPrcName); + bool loadPrcOk = loadPrc(newPrcName); strcpy(currentPrcName, newPrcName); strcpy(newPrcName, ""); - addScriptToList0(1); + // Check that the loading of the script file was successful before + // trying to add script 1 from it to the global scripts list. This + // fixes a crash when failing copy protection in Amiga or Atari ST + // versions of Future Wars. + if (loadPrcOk) { + 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). + warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName); + } } if (newRelName[0] != 0) { @@ -1582,16 +2085,19 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) { } } -uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3) { +bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) { Common::List<SeqListElement>::iterator it; for (it = seqList.begin(); it != seqList.end(); ++it) { if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) { - return 1; + // Just to be on the safe side there's a restriction of the + // addition's result to 16-bit arithmetic here like in the + // original. It's possible that it's not strictly needed. + return ((it->var14 + it->var16) & 0xFFFF) == 0; } } - return 0; + return true; } void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) { @@ -1618,6 +2124,19 @@ void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, i seqList.insert(it, tmp); } +void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) { + // Find a suitable list element and modify it + for (Common::List<SeqListElement>::iterator it = seqList.begin(); it != seqList.end(); ++it) { + if (it->objIdx == objIdx && it->var4 == var4Test) { + it->varC = param1; + it->var18 = param2; + it->var1A = param3; + it->var10 = it->var12 = param4; + break; + } + } +} + void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1, int16 param2, int16 x2, int16 y2) { element.var16 = 0; @@ -1662,105 +2181,51 @@ uint16 computeMove2(SeqListElement &element) { return returnVar; } -// sort all the gfx stuff... - -void resetGfxEntityEntry(uint16 objIdx) { -#if 0 - overlayHeadElement* tempHead = &overlayHead; - byte* var_16 = NULL; - uint16 var_10 = 0; - uint16 var_12 = 0; - overlayHeadElement* currentHead = tempHead->next; - byte* var_1A = NULL; - overlayHeadElement* var1E = &overlayHead; - - while (currentHead) { - tempHead2 = currentHead->next; - - if (currentHead->objIdx == objIdx && currentHead->type!=2 && currentHead->type!=3 && currentHead->type!=0x14) { - tempHead->next = tempHead2; - - if (tempHead2) { - tempHead2->previous = currentHead->previous; - } else { - seqVar0 = currentHead->previous; - } - - var_22 = var_16; - - if (!var_22) { - // todo: goto? - } - - var_22->previous = currentHead; - } else { - } - - if (currentHead->type == 0x14) { - } else { - } - - if (currentHead->type == 0x2 || currentHead->type == 0x3) { - si = 10000; - } else { - si = objectTable[currentHead->objIdx]; - } - - if (objectTable[objIdx]>si) { - var1E = currentHead; - } - - tempHead = tempHead->next; - - } - - if (var_1A) { - currentHead = var_16; - var_22 = var_1E->next; - var_1E->next = currentHead; - var_1A->next = var_22; - - if (var_1E != &gfxEntityHead) { - currentHead->previous = var_1E; - } - - if (!var_22) { - seqVar0 = var_1A; - } else { - var_22->previous = var_1A; - } - - } -#endif -} - -uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &element, uint16 param3, int16 *param4) { - const byte *currentPtr = ptr; - const byte *ptrData; - const byte *ptr2; +uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) { + const int8 *ptrData; + const int8 *ptr2; int16 di; - assert(ptr); - assert(param4); + 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); - dummyU16 = READ_BE_UINT16((currentPtr + param1 * 2) + 8); + // In the original an error string is set and 0 is returned if the following doesn't hold + assert(ptr); + // We probably could just use a local variable here instead of the dummyU16 but + // haven't checked if this has any side-effects so keeping it this way still. + dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8); ptrData = ptr + dummyU16; + // In the original an error string is set and 0 is returned if the following doesn't hold assert(*ptrData); di = (objectTable[objIdx].costume + 1) % (*ptrData); - ptr2 = (ptrData + (di * 8)) + 1; - + ++ptrData; // Jump over the just read byte + // Here ptr2 seems to be indexing a table of structs (8 bytes per struct): + // struct { + // int8 x; // 0 (Used with checkCollision) + // int8 y; // 1 (Used with checkCollision) + // int8 numZones; // 2 (Used with checkCollision) + // int8 var3; // 3 (Not used in this function) + // int8 xAdd; // 4 (Used with an object) + // int8 yAdd; // 5 (Used with an object) + // int8 maskAdd; // 6 (Used with an object) + // int8 frameAdd; // 7 (Used with an object) + // }; + ptr2 = ptrData + di * 8; + + // We might probably safely discard the AND by 1 here because + // at least in the original checkCollision returns always 0 or 1. if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) { return 0; } - objectTable[objIdx].x += (int8)ptr2[4]; - objectTable[objIdx].y += (int8)ptr2[5]; - objectTable[objIdx].mask += (int8)ptr2[6]; + objectTable[objIdx].x += ptr2[4]; + objectTable[objIdx].y += ptr2[5]; + objectTable[objIdx].mask += ptr2[6]; - if (objectTable[objIdx].frame) { + if (ptr2[6]) { resetGfxEntityEntry(objIdx); } @@ -1769,19 +2234,79 @@ uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &ele if (param3 || !element.var14) { objectTable[objIdx].costume = di; } else { + assert(param4); *param4 = di; } return 1; } +/*! + * Permutates the overlay list into a different order according to some logic. + * \todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors) + */ +void resetGfxEntityEntry(uint16 objIdx) { + Common::List<overlay>::iterator it, bObjsCutPoint; + Common::List<overlay> aReverseObjs, bObjs; + bool foundCutPoint = false; + + // Go through the overlay list and partition the whole list into two categories (Type A and type B objects) + for (it = overlayList.begin(); it != overlayList.end(); ++it) { + if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object + aReverseObjs.push_front(*it); + } else { // Type B object + bObjs.push_back(*it); + uint16 objectMask; + if (it->type == 2 || it->type == 3) { + objectMask = 10000; + } else { + objectMask = objectTable[it->objIdx].mask; + } + + if (objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point + bObjsCutPoint = bObjs.reverse_begin(); + foundCutPoint = true; + } + } + } + + // Recreate the overlay list in a different order. + overlayList.clear(); + if (foundCutPoint) { + // If a cut point was found the order is: + // B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point. + ++bObjsCutPoint; // Include the cut point in the first list insertion + overlayList.insert(overlayList.end(), bObjs.begin(), bObjsCutPoint); + overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); + overlayList.insert(overlayList.end(), bObjsCutPoint, bObjs.end()); + } else { + // If no cut point was found the order is: + // A objects in reverse order, B objects. + overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); + overlayList.insert(overlayList.end(), bObjs.begin(), bObjs.end()); + } +} + void processSeqListElement(SeqListElement &element) { int16 x = objectTable[element.objIdx].x; int16 y = objectTable[element.objIdx].y; - const byte *ptr1 = animDataTable[element.frame].data(); + const int8 *ptr1 = (const int8 *) animDataTable[element.frame].data(); 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++; @@ -1791,22 +2316,44 @@ void processSeqListElement(SeqListElement &element) { element.var12 = 0; if (ptr1) { - uint16 param1 = ptr1[1]; - uint16 param2 = ptr1[2]; + int16 param1 = ptr1[1]; + int16 param2 = ptr1[2]; if (element.varC != 255) { - // FIXME: Why is this here? Fingolfin gets lots of these - // in his copy of Operation Stealth (value 0 or 236) under - // Mac OS X. Maybe it's a endian issue? At least the graphics - // in the copy protection screen are partially messed up. - warning("processSeqListElement: varC = %d", element.varC); - } - - if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { - computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); + int16 x2 = element.var18; + int16 y2 = element.var1A; + if (element.varC) { + x2 += objectTable[element.varC].x; + y2 += objectTable[element.varC].y; + } + computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2); } else { - element.var16 = 0; - element.var14 = 0; + if (inputVar0 && allowPlayerInput) { + int16 adder = param1 + 1; + if (inputVar0 != 1) { + adder = -adder; + } + // FIXME: In Operation Stealth's disassembly global variable 251 is used here + // but it's named as VAR_MOUSE_Y_MODE in ScummVM. Is it correct or a + // left over from Future Wars's reverse engineering? + globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder; + } + + if (inputVar1 && allowPlayerInput) { + int16 adder = param2 + 1; + if (inputVar1 != 1) { + adder = -adder; + } + // TODO: Name currently unnamed global variable 252 + globalVars[VAR_MOUSE_Y_POS] = globalVars[252] = ptr1[5] + y + adder; + } + + if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { + computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); + } else { + element.var16 = 0; + element.var14 = 0; + } } var_10 = computeMove2(element); @@ -1847,14 +2394,14 @@ void processSeqListElement(SeqListElement &element) { } } - if (element.var16 + element.var14) { + if (element.var16 + element.var14 == 0) { if (element.var1C) { if (element.var1E) { objectTable[element.objIdx].costume = 0; element.var1E = 0; } - addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, (int16 *) & var2); + addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2); } } diff --git a/engines/cine/various.h b/engines/cine/various.h index 91662c16ff..d87679ca08 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; @@ -130,16 +130,20 @@ struct SelectedObjStruct { #define NUM_MAX_ZONE 16 extern uint16 zoneData[NUM_MAX_ZONE]; +extern uint16 zoneQuery[NUM_MAX_ZONE]; void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5); void removeMessages(); void removeSeq(uint16 param1, uint16 param2, uint16 param3); -uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3); +bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3); void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8); +void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4); void processSeqList(void); +void resetGfxEntityEntry(uint16 objIdx); + bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y); } // End of namespace Cine |