diff options
author | Christopher Page | 2008-08-04 22:34:07 +0000 |
---|---|---|
committer | Christopher Page | 2008-08-04 22:34:07 +0000 |
commit | 4198ee962399305a4a158b1f43224c00e2e04a1b (patch) | |
tree | eea375b5cb471509df2e2c5d9123d963a62403c6 /engines/cine/various.cpp | |
parent | a51f45407659bba43254b466d20b6af2e8f17ffd (diff) | |
parent | 4f5479ee744ac6b419cdf7ec1e96fbf7c83d36ef (diff) | |
download | scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.gz scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.bz2 scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.zip |
Merged revisions 33188-33189,33191-33193,33196,33198,33202-33203,33206,33210,33212,33218-33220,33222,33224-33226,33229-33243,33246,33248-33250,33252,33258-33261,33263,33266,33270,33272-33283,33285,33287-33290,33295-33298,33321,33325-33330,33332-33335,33337-33340,33342,33345,33347,33349-33350,33352-33357,33359-33367,33369-33371,33373,33375-33377,33379-33380,33383-33385,33387-33389,33392-33394,33400-33402,33404-33405,33407-33410,33412-33416,33418-33419,33425-33427,33432,33436-33438,33444,33446,33452-33453,33455-33459,33463-33464,33466-33471,33473-33474,33478,33490,33492,33495-33496,33509-33512,33518-33519,33522-33527,33529-33530,33537,33541,33544,33546,33550,33552-33554,33556,33558,33561-33562,33565,33568,33570,33574,33576,33578-33581,33584-33587,33590,33596,33604-33611,33614-33615,33617-33618,33620-33621 via svnmerge from
https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk
svn-id: r33624
Diffstat (limited to 'engines/cine/various.cpp')
-rw-r--r-- | engines/cine/various.cpp | 1132 |
1 files changed, 802 insertions, 330 deletions
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp index b70201ce99..c490756403 100644 --- a/engines/cine/various.cpp +++ b/engines/cine/various.cpp @@ -187,6 +187,15 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { frame = ABS((int16)(objectTable[it->objIdx].frame)); part = objectTable[it->objIdx].part; + // Additional case for negative frame values in Operation Stealth + if (g_cine->getGameType() == Cine::GType_OS && objectTable[it->objIdx].frame < 0) { + if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) { + return it->objIdx; + } else { + continue; + } + } + if (it->type == 0) { threshold = animDataTable[frame]._var1; } else { @@ -199,16 +208,19 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { xdif = x - objX; ydif = y - objY; - if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif < 0) || (ydif >= height) || !animDataTable[frame].data()) { + if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !animDataTable[frame].data()) { continue; } if (g_cine->getGameType() == Cine::GType_OS) { + // This test isn't present in Operation Stealth's PC version's disassembly + // but removing it makes things crash sometimes (e.g. when selecting a verb + // and moving the mouse cursor around the floor in the airport's bathroom). if (xdif >= width) { continue; } - if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != part) { + if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) { return it->objIdx; } else if (it->type == 1 && gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) { return it->objIdx; @@ -227,6 +239,143 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { return -1; } +bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) { + out.writeUint32BE(header.id); + out.writeUint32BE(header.version); + out.writeUint32BE(header.size); + return !out.ioFailed(); +} + +bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) { + header.id = in.readUint32BE(); + header.version = in.readUint32BE(); + header.size = in.readUint32BE(); + return !in.ioFailed(); +} + +void saveObjectTable(Common::OutSaveFile &out) { + out.writeUint16BE(NUM_MAX_OBJECT); // Entry count + out.writeUint16BE(0x20); // Entry size + + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + out.writeUint16BE(objectTable[i].x); + out.writeUint16BE(objectTable[i].y); + out.writeUint16BE(objectTable[i].mask); + out.writeUint16BE(objectTable[i].frame); + out.writeUint16BE(objectTable[i].costume); + out.write(objectTable[i].name, 20); + out.writeUint16BE(objectTable[i].part); + } +} + +void saveZoneData(Common::OutSaveFile &out) { + for (int i = 0; i < 16; i++) { + out.writeUint16BE(zoneData[i]); + } +} + +void saveCommandVariables(Common::OutSaveFile &out) { + for (int i = 0; i < 4; i++) { + out.writeUint16BE(commandVar3[i]); + } +} + +void saveAnimDataTable(Common::OutSaveFile &out) { + out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count + out.writeUint16BE(0x1E); // Entry size + + for (int i = 0; i < NUM_MAX_ANIMDATA; i++) { + animDataTable[i].save(out); + } +} + +void saveScreenParams(Common::OutSaveFile &out) { + // Screen parameters, unhandled + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); +} + +void saveGlobalScripts(Common::OutSaveFile &out) { + ScriptList::const_iterator it; + out.writeUint16BE(globalScripts.size()); + for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { + (*it)->save(out); + } +} + +void saveObjectScripts(Common::OutSaveFile &out) { + ScriptList::const_iterator it; + out.writeUint16BE(objectScripts.size()); + for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { + (*it)->save(out); + } +} + +void saveOverlayList(Common::OutSaveFile &out) { + Common::List<overlay>::const_iterator it; + + out.writeUint16BE(overlayList.size()); + + for (it = overlayList.begin(); it != overlayList.end(); ++it) { + out.writeUint32BE(0); // next + out.writeUint32BE(0); // previous? + out.writeUint16BE(it->objIdx); + out.writeUint16BE(it->type); + out.writeSint16BE(it->x); + out.writeSint16BE(it->y); + out.writeSint16BE(it->width); + out.writeSint16BE(it->color); + } +} + +void saveBgIncrustList(Common::OutSaveFile &out) { + Common::List<BGIncrust>::const_iterator it; + out.writeUint16BE(bgIncrustList.size()); + + for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { + out.writeUint32BE(0); // next + out.writeUint32BE(0); // previous? + out.writeUint16BE(it->objIdx); + out.writeUint16BE(it->param); + out.writeUint16BE(it->x); + out.writeUint16BE(it->y); + out.writeUint16BE(it->frame); + out.writeUint16BE(it->part); + } +} + +void saveZoneQuery(Common::OutSaveFile &out) { + for (int i = 0; i < 16; i++) { + out.writeUint16BE(zoneQuery[i]); + } +} + +void saveSeqList(Common::OutSaveFile &out) { + Common::List<SeqListElement>::const_iterator it; + out.writeUint16BE(seqList.size()); + + for (it = seqList.begin(); it != seqList.end(); ++it) { + out.writeSint16BE(it->var4); + out.writeUint16BE(it->objIdx); + out.writeSint16BE(it->var8); + out.writeSint16BE(it->frame); + out.writeSint16BE(it->varC); + out.writeSint16BE(it->varE); + out.writeSint16BE(it->var10); + out.writeSint16BE(it->var12); + out.writeSint16BE(it->var14); + out.writeSint16BE(it->var16); + out.writeSint16BE(it->var18); + out.writeSint16BE(it->var1A); + out.writeSint16BE(it->var1C); + out.writeSint16BE(it->var1E); + } +} + bool CineEngine::loadSaveDirectory(void) { Common::InSaveFile *fHandle; char tmp[80]; @@ -244,21 +393,143 @@ bool CineEngine::loadSaveDirectory(void) { return true; } +/*! \brief Savegame format detector + * \param fHandle Savefile to check + * \return Savegame format on success, ANIMSIZE_UNKNOWN on failure + * + * This function seeks through the savefile and tries to determine the + * savegame format it uses. There's a miniscule chance that the detection + * algorithm could get confused and think that the file uses both the older + * and the newer format but that is such a remote possibility that I wouldn't + * worry about it at all. + * + * Also detects the temporary Operation Stealth savegame format now. + */ +enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) { + const uint32 prevStreamPos = fHandle.pos(); + + // First check for the temporary Operation Stealth savegame format. + fHandle.seek(0); + ChunkHeader hdr; + loadChunkHeader(fHandle, hdr); + fHandle.seek(prevStreamPos); + if (hdr.id == TEMP_OS_FORMAT_ID) { + return TEMP_OS_FORMAT; + } + + // Ok, so the savegame isn't using the temporary Operation Stealth savegame format. + // Let's check for the plain Future Wars savegame format and its different versions then. + // The animDataTable begins at savefile position 0x2315. + // Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443) + // and 30 bytes in the save format after that (Revision 31444 and onwards). + // There are 255 entries in the animDataTable in both of the savefile formats. + static const uint animDataTableStart = 0x2315; + static const uint animEntriesCount = 255; + static const uint oldAnimEntrySize = 23; + static const uint newAnimEntrySize = 30; + static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize}; + Common::Array<uint> animEntrySizeMatches; + + // Try to walk through the savefile using different animDataTable entry sizes + // and make a list of all the successful entry sizes. + for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) { + // 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries) + // 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries) + static const uint sizeofScreenParams = 2 * 6; + static const uint globalScriptEntrySize = 206; + static const uint objectScriptEntrySize = 206; + static const uint overlayEntrySize = 20; + static const uint bgIncrustEntrySize = 20; + static const uint chainEntrySizes[] = { + globalScriptEntrySize, + objectScriptEntrySize, + overlayEntrySize, + bgIncrustEntrySize + }; + + uint animEntrySize = animEntrySizeChoices[i]; + // Jump over the animDataTable entries and the screen parameters + uint32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams; + // Check that there's data left after the point we're going to jump to + if (newPos >= fHandle.size()) { + continue; + } + fHandle.seek(newPos); + + // Jump over the remaining items in the savegame file + // (i.e. the global scripts, object scripts, overlays and background incrusts). + bool chainWalkSuccess = true; + for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) { + // Read entry count and jump over the entries + int entryCount = fHandle.readSint16BE(); + newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount; + // Check that we didn't go past the end of file. + // Note that getting exactly to the end of file is acceptable. + if (newPos > fHandle.size()) { + chainWalkSuccess = false; + break; + } + fHandle.seek(newPos); + } + + // If we could walk the chain successfully and + // got exactly to the end of file then we've got a match. + if (chainWalkSuccess && fHandle.pos() == fHandle.size()) { + // We found a match, let's save it + animEntrySizeMatches.push_back(animEntrySize); + } + } + + // Check that we got only one entry size match. + // If we didn't, then return an error. + enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN; + if (animEntrySizeMatches.size() == 1) { + const uint animEntrySize = animEntrySizeMatches[0]; + assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize); + if (animEntrySize == oldAnimEntrySize) { + result = ANIMSIZE_23; + } else { // animEntrySize == newAnimEntrySize + // Check data and mask pointers in all of the animDataTable entries + // to see whether we've got the version with the broken data and mask pointers or not. + // In the broken format all data and mask pointers were always zero. + static const uint relativeDataPos = 2 * 4; + bool pointersIntact = false; + for (uint i = 0; i < animEntriesCount; i++) { + fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos); + uint32 data = fHandle.readUint32BE(); + uint32 mask = fHandle.readUint32BE(); + if ((data != 0) || (mask != 0)) { + pointersIntact = true; + break; + } + } + result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN); + } + } else if (animEntrySizeMatches.size() > 1) { + warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format"); + } else { // animEtrySizeMatches.size() == 0 + debug(3, "Savegame format detector was unable to detect savegame's format"); + } + + fHandle.seek(prevStreamPos); + return result; +} + /*! \brief Restore script list item from savefile - * \param fHandle Savefile handlem open for reading + * \param fHandle Savefile handle open for reading * \param isGlobal Restore object or global script? */ -void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { +void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) { ScriptVars localVars, labels; uint16 compare, pos; int16 idx; - labels.load(*fHandle); - localVars.load(*fHandle); + labels.load(fHandle); + localVars.load(fHandle); - compare = fHandle->readUint16BE(); - pos = fHandle->readUint16BE(); - idx = fHandle->readUint16BE(); + compare = fHandle.readUint16BE(); + pos = fHandle.readUint16BE(); + idx = fHandle.readUint16BE(); // no way to reinitialize these if (idx < 0) { @@ -281,7 +552,7 @@ void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { /*! \brief Restore overlay sprites from savefile * \param fHandle Savefile open for reading */ -void loadOverlayFromSave(Common::InSaveFile &fHandle) { +void loadOverlayFromSave(Common::SeekableReadStream &fHandle) { overlay tmp; fHandle.readUint32BE(); @@ -297,128 +568,10 @@ void loadOverlayFromSave(Common::InSaveFile &fHandle) { overlayList.push_back(tmp); } -/*! \brief Savefile format tester - * \param fHandle Savefile to check - * - * This function seeks through savefile and tries to guess if it's the original - * savegame format or broken format from ScummVM 0.10/0.11 - * The test is incomplete but this should cover 99.99% of cases. - * If anyone makes a savefile which could confuse this test, assert will - * report it - */ -bool brokenSave(Common::InSaveFile &fHandle) { - // Backward seeking not supported in compressed savefiles - // if you really want it, finish it yourself - return false; - - // fixed size part: 14093 bytes (12308 bytes in broken save) - // animDataTable begins at byte 6431 - - int filesize = fHandle.size(); - int startpos = fHandle.pos(); - int pos, tmp; - bool correct = false, broken = false; - - // check for correct format - while (filesize > 14093) { - pos = 14093; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - - if (pos == filesize) correct = true; - break; - } - debug(5, "brokenSave: correct format check %s: size=%d, pos=%d", - correct ? "passed" : "failed", filesize, pos); - - // check for broken format - while (filesize > 12308) { - pos = 12308; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - - if (pos == filesize) broken = true; - break; - } - debug(5, "brokenSave: broken format check %s: size=%d, pos=%d", - broken ? "passed" : "failed", filesize, pos); - - // there's a very small chance that both cases will match - // if anyone runs into it, you'll have to walk through - // the animDataTable and try to open part file for each entry - if (!correct && !broken) { - error("brokenSave: file format check failed"); - } else if (correct && broken) { - error("brokenSave: both file formats seem to apply"); - } - - fHandle.seek(startpos); - debug(5, "brokenSave: detected %s file format", - correct ? "correct" : "broken"); - - return broken; -} - -/*! \todo Implement Operation Stealth loading, this is obviously Future Wars only - * \todo Add support for loading the zoneQuery table (Operation Stealth specific) - */ -bool CineEngine::makeLoad(char *saveName) { - int16 i; - int16 size; - bool broken; - Common::InSaveFile *fHandle; - char bgName[13]; - - fHandle = g_saveFileMan->openForLoading(saveName); - - if (!fHandle) { - drawString(otherMessages[0], 0); - waitPlayerInput(); - // restoreScreen(); - checkDataDisk(-1); - return false; - } - +void CineEngine::resetEngine() { g_sound->stopMusic(); freeAnimDataTable(); overlayList.clear(); - // if (g_cine->getGameType() == Cine::GType_OS) { - // freeUnkList(); - // } bgIncrustList.clear(); closePart(); @@ -428,7 +581,9 @@ bool CineEngine::makeLoad(char *saveName) { scriptTable.clear(); messageTable.clear(); - for (i = 0; i < NUM_MAX_OBJECT; i++) { + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + objectTable[i].x = 0; + objectTable[i].y = 0; objectTable[i].part = 0; objectTable[i].name[0] = 0; objectTable[i].frame = 0; @@ -462,29 +617,294 @@ bool CineEngine::makeLoad(char *saveName) { checkForPendingDataLoadSwitch = 0; - broken = brokenSave(*fHandle); + if (g_cine->getGameType() == Cine::GType_OS) { + seqList.clear(); + currentAdditionalBgIdx = 0; + currentAdditionalBgIdx2 = 0; + // TODO: Add resetting of the following variables + // adBgVar1 = 0; + // adBgVar0 = 0; + // gfxFadeOutCompleted = 0; + } +} + +bool loadObjectTable(Common::SeekableReadStream &in) { + in.readUint16BE(); // Entry count + in.readUint16BE(); // Entry size + + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + objectTable[i].x = in.readSint16BE(); + objectTable[i].y = in.readSint16BE(); + objectTable[i].mask = in.readUint16BE(); + objectTable[i].frame = in.readSint16BE(); + objectTable[i].costume = in.readSint16BE(); + in.read(objectTable[i].name, 20); + objectTable[i].part = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadZoneData(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneData[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadCommandVariables(Common::SeekableReadStream &in) { + for (int i = 0; i < 4; i++) { + commandVar3[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool loadScreenParams(Common::SeekableReadStream &in) { + // TODO: handle screen params (really required ?) + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + return !in.ioFailed(); +} + +bool loadGlobalScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, true); + } + return !in.ioFailed(); +} + +bool loadObjectScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, false); + } + return !in.ioFailed(); +} + +bool loadOverlayList(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadOverlayFromSave(in); + } + return !in.ioFailed(); +} + +bool loadSeqList(Common::SeekableReadStream &in) { + uint size = in.readUint16BE(); + SeqListElement tmp; + for (uint i = 0; i < size; i++) { + tmp.var4 = in.readSint16BE(); + tmp.objIdx = in.readUint16BE(); + tmp.var8 = in.readSint16BE(); + tmp.frame = in.readSint16BE(); + tmp.varC = in.readSint16BE(); + tmp.varE = in.readSint16BE(); + tmp.var10 = in.readSint16BE(); + tmp.var12 = in.readSint16BE(); + tmp.var14 = in.readSint16BE(); + tmp.var16 = in.readSint16BE(); + tmp.var18 = in.readSint16BE(); + tmp.var1A = in.readSint16BE(); + tmp.var1C = in.readSint16BE(); + tmp.var1E = in.readSint16BE(); + seqList.push_back(tmp); + } + return !in.ioFailed(); +} + +bool loadZoneQuery(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneQuery[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { + char musicName[13]; + char bgNames[8][13]; + + // First check the temporary Operation Stealth savegame format header. + ChunkHeader hdr; + loadChunkHeader(in, hdr); + if (hdr.id != TEMP_OS_FORMAT_ID) { + warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame"); + return false; + } else if (hdr.version > CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected newer format version. Not loading savegame"); + return false; + } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break"); + } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER + debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match)."); + } + + // There shouldn't be any data in the header's chunk currently so it's an error if there is. + if (hdr.size > 0) { + warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame"); + return false; + } + + // Ok, so we've got a correct header for a temporary Operation Stealth savegame. + // Let's start loading the plain savegame data then. + currentDisk = in.readUint16BE(); + in.read(currentPartName, 13); + in.read(currentPrcName, 13); + in.read(currentRelName, 13); + in.read(currentMsgName, 13); + + // Load the 8 background names. + for (uint i = 0; i < 8; i++) { + in.read(bgNames[i], 13); + } + + in.read(currentCtName, 13); + + // Moved the loading of current procedure, relation, + // backgrounds and Ct here because if they were at the + // end of this function then the global scripts loading + // made an array out of bounds access. In the original + // game's disassembly these aren't here but at the end. + // The difference is probably in how we handle loading + // the global scripts and some other things (i.e. the + // loading routines aren't exactly the same and subtle + // semantic differences result in having to do things + // in a different order). + { + // Not sure if this is needed with Operation Stealth... + checkDataDisk(currentDisk); + + if (strlen(currentPrcName)) { + loadPrc(currentPrcName); + } + + if (strlen(currentRelName)) { + loadRel(currentRelName); + } + + // Load first background (Uses loadBg) + if (strlen(bgNames[0])) { + loadBg(bgNames[0]); + } + + // Add backgrounds 1-7 (Uses addBackground) + for (int i = 1; i < 8; i++) { + if (strlen(bgNames[i])) { + addBackground(bgNames[i], i); + } + } + + if (strlen(currentCtName)) { + loadCtOS(currentCtName); + } + } + + loadObjectTable(in); + renderer->restorePalette(in); + globalVars.load(in, NUM_MAX_VAR); + loadZoneData(in); + loadCommandVariables(in); + in.read(commandBuffer, 0x50); + loadZoneQuery(in); + + // TODO: Use the loaded string (Current music name (String, 13 bytes)). + in.read(musicName, 13); + + // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)). + in.readUint16BE(); + + // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)). + in.readUint16BE(); + + renderer->_cmdY = in.readUint16BE(); + in.readUint16BE(); // Some unknown variable that seems to always be zero + allowPlayerInput = in.readUint16BE(); + playerCommand = in.readUint16BE(); + commandVar1 = in.readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); + var5 = in.readUint16BE(); + var4 = in.readUint16BE(); + var3 = in.readUint16BE(); + var2 = in.readUint16BE(); + commandVar2 = in.readUint16BE(); + renderer->_messageBg = in.readUint16BE(); + + // TODO: Use the loaded value (adBgVar1 (Uint16BE)). + in.readUint16BE(); + + currentAdditionalBgIdx = in.readSint16BE(); + currentAdditionalBgIdx2 = in.readSint16BE(); + + // TODO: Check whether the scroll value really gets used correctly after this. + // Note that the backgrounds are loaded only later than this value is set. + renderer->setScroll(in.readUint16BE()); + + // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?). + in.readUint16BE(); + + disableSystemMenu = in.readUint16BE(); + + // TODO: adBgVar1 = 1 here + + // Load the animDataTable entries + in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth). + in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth). + loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT); + + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadSeqList(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); + + // Left this here instead of moving it earlier in this function with + // the other current value loadings (e.g. loading of current procedure, + // current backgrounds etc). Mostly emulating the way we've handled + // Future Wars savegames and hoping that things work out. + if (strlen(currentMsgName)) { + loadMsg(currentMsgName); + } + + // TODO: Add current music loading and playing here + // TODO: Palette handling? + + if (in.pos() == in.size()) { + debug(3, "loadTempSaveOS: Loaded the whole savefile."); + } else { + warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over"); + } + + return !in.ioFailed(); +} + +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { + char bgName[13]; // At savefile position 0x0000: - currentDisk = fHandle->readUint16BE(); + currentDisk = in.readUint16BE(); // At 0x0002: - fHandle->read(currentPartName, 13); + in.read(currentPartName, 13); // At 0x000F: - fHandle->read(currentDatName, 13); + in.read(currentDatName, 13); // At 0x001C: - saveVar2 = fHandle->readSint16BE(); + saveVar2 = in.readSint16BE(); // At 0x001E: - fHandle->read(currentPrcName, 13); + in.read(currentPrcName, 13); // At 0x002B: - fHandle->read(currentRelName, 13); + in.read(currentRelName, 13); // At 0x0038: - fHandle->read(currentMsgName, 13); + in.read(currentMsgName, 13); // At 0x0045: - fHandle->read(bgName, 13); + in.read(bgName, 13); // At 0x0052: - fHandle->read(currentCtName, 13); + in.read(currentCtName, 13); checkDataDisk(currentDisk); @@ -509,118 +929,69 @@ bool CineEngine::makeLoad(char *saveName) { } // At 0x005F: - fHandle->readUint16BE(); - // At 0x0061: - fHandle->readUint16BE(); + loadObjectTable(in); - // At 0x0063: - for (i = 0; i < 255; i++) { - // At 0x0063 + i * 32 + 0: - objectTable[i].x = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 2: - objectTable[i].y = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 4: - objectTable[i].mask = fHandle->readUint16BE(); - // At 0x0063 + i * 32 + 6: - objectTable[i].frame = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 8: - objectTable[i].costume = fHandle->readSint16BE(); - // At 0x0063 + i * 32 + 10: - fHandle->read(objectTable[i].name, 20); - // At 0x0063 + i * 32 + 30: - objectTable[i].part = fHandle->readUint16BE(); - } - - // At 0x2043 (i.e. 0x0063 + 255 * 32): - renderer->restorePalette(*fHandle); + // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): + renderer->restorePalette(in); // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): - globalVars.load(*fHandle, NUM_MAX_VAR - 1); + globalVars.load(in, NUM_MAX_VAR); // At 0x2281 (i.e. 0x2083 + 255 * 2): - for (i = 0; i < 16; i++) { - // At 0x2281 + i * 2: - zoneData[i] = fHandle->readUint16BE(); - } + loadZoneData(in); // At 0x22A1 (i.e. 0x2281 + 16 * 2): - for (i = 0; i < 4; i++) { - // At 0x22A1 + i * 2: - commandVar3[i] = fHandle->readUint16BE(); - } + loadCommandVariables(in); // At 0x22A9 (i.e. 0x22A1 + 4 * 2): - fHandle->read(commandBuffer, 0x50); + in.read(commandBuffer, 0x50); renderer->setCommand(commandBuffer); // At 0x22F9 (i.e. 0x22A9 + 0x50): - renderer->_cmdY = fHandle->readUint16BE(); + renderer->_cmdY = in.readUint16BE(); // At 0x22FB: - bgVar0 = fHandle->readUint16BE(); + bgVar0 = in.readUint16BE(); // At 0x22FD: - allowPlayerInput = fHandle->readUint16BE(); + allowPlayerInput = in.readUint16BE(); // At 0x22FF: - playerCommand = fHandle->readSint16BE(); + playerCommand = in.readSint16BE(); // At 0x2301: - commandVar1 = fHandle->readSint16BE(); + commandVar1 = in.readSint16BE(); // At 0x2303: - isDrawCommandEnabled = fHandle->readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); // At 0x2305: - var5 = fHandle->readUint16BE(); + var5 = in.readUint16BE(); // At 0x2307: - var4 = fHandle->readUint16BE(); + var4 = in.readUint16BE(); // At 0x2309: - var3 = fHandle->readUint16BE(); + var3 = in.readUint16BE(); // At 0x230B: - var2 = fHandle->readUint16BE(); + var2 = in.readUint16BE(); // At 0x230D: - commandVar2 = fHandle->readSint16BE(); + commandVar2 = in.readSint16BE(); // At 0x230F: - renderer->_messageBg = fHandle->readUint16BE(); + renderer->_messageBg = in.readUint16BE(); // At 0x2311: - fHandle->readUint16BE(); + in.readUint16BE(); // At 0x2313: - fHandle->readUint16BE(); + in.readUint16BE(); // At 0x2315: - loadResourcesFromSave(*fHandle, broken); - - // TODO: handle screen params (really required ?) - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, true); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, false); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadOverlayFromSave(*fHandle); - } - - loadBgIncrustFromSave(*fHandle); + loadResourcesFromSave(in, saveGameFormat); - delete fHandle; + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); if (strlen(currentMsgName)) { loadMsg(currentMsgName); } - setMouseCursor(MOUSE_CURSOR_NORMAL); - if (strlen(currentDatName)) { /* i = saveVar2; saveVar2 = 0; @@ -630,137 +1001,139 @@ bool CineEngine::makeLoad(char *saveName) { }*/ } - return true; + return !in.ioFailed(); } -/*! \todo Add support for saving the zoneQuery table (Operation Stealth specific) - */ -void makeSave(char *saveFileName) { - int16 i; - Common::OutSaveFile *fHandle; - - fHandle = g_saveFileMan->openForSaving(saveFileName); +bool CineEngine::makeLoad(char *saveName) { + Common::SharedPtr<Common::InSaveFile> saveFile(g_saveFileMan->openForLoading(saveName)); - if (!fHandle) { - drawString(otherMessages[1], 0); + if (!saveFile) { + drawString(otherMessages[0], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); - return; - } - - fHandle->writeUint16BE(currentDisk); - fHandle->write(currentPartName, 13); - fHandle->write(currentDatName, 13); - fHandle->writeUint16BE(saveVar2); - fHandle->write(currentPrcName, 13); - fHandle->write(currentRelName, 13); - fHandle->write(currentMsgName, 13); - renderer->saveBg(*fHandle); - fHandle->write(currentCtName, 13); - - fHandle->writeUint16BE(0xFF); - fHandle->writeUint16BE(0x20); - - for (i = 0; i < 255; i++) { - fHandle->writeUint16BE(objectTable[i].x); - fHandle->writeUint16BE(objectTable[i].y); - fHandle->writeUint16BE(objectTable[i].mask); - fHandle->writeUint16BE(objectTable[i].frame); - fHandle->writeUint16BE(objectTable[i].costume); - fHandle->write(objectTable[i].name, 20); - fHandle->writeUint16BE(objectTable[i].part); - } - - renderer->savePalette(*fHandle); - - globalVars.save(*fHandle, NUM_MAX_VAR - 1); - - for (i = 0; i < 16; i++) { - fHandle->writeUint16BE(zoneData[i]); + return false; } - for (i = 0; i < 4; i++) { - fHandle->writeUint16BE(commandVar3[i]); + setMouseCursor(MOUSE_CURSOR_DISK); + + uint32 saveSize = saveFile->size(); + // TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format. + if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it + // Can't get information about the savefile's size so let's try + // reading as much as we can from the file up to a predefined upper limit. + // + // Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each): + // With 256 global scripts, object scripts, overlays and background incrusts: + // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB + // With 512 global scripts, object scripts, overlays and background incrusts: + // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB + // + // I think it extremely unlikely that there would be over 512 global scripts, object scripts, + // overlays and background incrusts so 256kB seems like quite a safe upper limit. + // NOTE: If the savegame format is changed then this value might have to be re-evaluated! + // Hopefully devices with more limited memory can also cope with this memory allocation. + saveSize = 256 * 1024; + } + Common::SharedPtr<Common::MemoryReadStream> in(saveFile->readStream(saveSize)); + + // Try to detect the used savegame format + enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in); + + // Handle problematic savegame formats + bool load = true; // Should we try to load the savegame? + bool result = false; + if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) { + // One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but + // that's not implemented here because it was never used in a stable + // release of ScummVM but only during development (From revision 31453, + // which introduced the problem, until revision 32073, which fixed it). + // Therefore be bail out if we detect this particular savegame format. + warning("Detected a known broken savegame format, not loading savegame"); + load = false; // Don't load the savegame + } else if (saveGameFormat == ANIMSIZE_UNKNOWN) { + // If we can't detect the savegame format + // then let's try the default format and hope for the best. + warning("Couldn't detect the used savegame format, trying default savegame format. Things may break"); + saveGameFormat = ANIMSIZE_30_PTRS_INTACT; + } + + if (load) { + // Reset the engine's state + resetEngine(); + + if (saveGameFormat == TEMP_OS_FORMAT) { + // Load the temporary Operation Stealth savegame format + result = loadTempSaveOS(*in); + } else { + // Load the plain Future Wars savegame format + result = loadPlainSaveFW(*in, saveGameFormat); + } } - fHandle->write(commandBuffer, 0x50); - - fHandle->writeUint16BE(renderer->_cmdY); - - fHandle->writeUint16BE(bgVar0); - fHandle->writeUint16BE(allowPlayerInput); - fHandle->writeUint16BE(playerCommand); - fHandle->writeUint16BE(commandVar1); - fHandle->writeUint16BE(isDrawCommandEnabled); - fHandle->writeUint16BE(var5); - fHandle->writeUint16BE(var4); - fHandle->writeUint16BE(var3); - fHandle->writeUint16BE(var2); - fHandle->writeUint16BE(commandVar2); - - fHandle->writeUint16BE(renderer->_messageBg); - - fHandle->writeUint16BE(0xFF); - fHandle->writeUint16BE(0x1E); + setMouseCursor(MOUSE_CURSOR_NORMAL); - for (i = 0; i < NUM_MAX_ANIMDATA; i++) { - animDataTable[i].save(*fHandle); - } + return result; +} - fHandle->writeUint16BE(0); // Screen params, unhandled - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); +void CineEngine::makeSaveFW(Common::OutSaveFile &out) { + out.writeUint16BE(currentDisk); + out.write(currentPartName, 13); + out.write(currentDatName, 13); + out.writeUint16BE(saveVar2); + out.write(currentPrcName, 13); + out.write(currentRelName, 13); + out.write(currentMsgName, 13); + renderer->saveBgNames(out); + out.write(currentCtName, 13); + + saveObjectTable(out); + renderer->savePalette(out); + globalVars.save(out, NUM_MAX_VAR); + saveZoneData(out); + saveCommandVariables(out); + out.write(commandBuffer, 0x50); + + out.writeUint16BE(renderer->_cmdY); + out.writeUint16BE(bgVar0); + out.writeUint16BE(allowPlayerInput); + out.writeUint16BE(playerCommand); + out.writeUint16BE(commandVar1); + out.writeUint16BE(isDrawCommandEnabled); + out.writeUint16BE(var5); + out.writeUint16BE(var4); + out.writeUint16BE(var3); + out.writeUint16BE(var2); + out.writeUint16BE(commandVar2); + out.writeUint16BE(renderer->_messageBg); + + saveAnimDataTable(out); + saveScreenParams(out); + + saveGlobalScripts(out); + saveObjectScripts(out); + saveOverlayList(out); + saveBgIncrustList(out); +} - { - ScriptList::iterator it; - fHandle->writeUint16BE(globalScripts.size()); - for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { - (*it)->save(*fHandle); - } +void CineEngine::makeSave(char *saveFileName) { + Common::SharedPtr<Common::OutSaveFile> fHandle(g_saveFileMan->openForSaving(saveFileName)); - fHandle->writeUint16BE(objectScripts.size()); - for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { - (*it)->save(*fHandle); - } - } + setMouseCursor(MOUSE_CURSOR_DISK); - { - Common::List<overlay>::iterator it; - - fHandle->writeUint16BE(overlayList.size()); - - for (it = overlayList.begin(); it != overlayList.end(); ++it) { - fHandle->writeUint32BE(0); - fHandle->writeUint32BE(0); - fHandle->writeUint16BE(it->objIdx); - fHandle->writeUint16BE(it->type); - fHandle->writeSint16BE(it->x); - fHandle->writeSint16BE(it->y); - fHandle->writeSint16BE(it->width); - fHandle->writeSint16BE(it->color); + if (!fHandle) { + drawString(otherMessages[1], 0); + waitPlayerInput(); + // restoreScreen(); + checkDataDisk(-1); + } else { + if (g_cine->getGameType() == GType_FW) { + makeSaveFW(*fHandle); + } else { + makeSaveOS(*fHandle); } } - Common::List<BGIncrust>::iterator it; - fHandle->writeUint16BE(bgIncrustList.size()); - - for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { - fHandle->writeUint32BE(0); // next - fHandle->writeUint32BE(0); // unkPtr - fHandle->writeUint16BE(it->objIdx); - fHandle->writeUint16BE(it->param); - fHandle->writeUint16BE(it->x); - fHandle->writeUint16BE(it->y); - fHandle->writeUint16BE(it->frame); - fHandle->writeUint16BE(it->part); - } - - delete fHandle; - setMouseCursor(MOUSE_CURSOR_NORMAL); } @@ -901,6 +1274,89 @@ void CineEngine::makeSystemMenu(void) { } } +/** + * Save an Operation Stealth type savegame. WIP! + * + * NOTE: This is going to be very much a work in progress so the Operation Stealth's + * savegame formats that are going to be tried are extremely probably not going + * to be supported at all after Operation Stealth becomes officially supported. + * This means that the savegame format will hopefully change to something nicer + * when official support for Operation Stealth begins. + */ +void CineEngine::makeSaveOS(Common::OutSaveFile &out) { + int i; + + // Make a temporary Operation Stealth savegame format chunk header and save it. + ChunkHeader header; + header.id = TEMP_OS_FORMAT_ID; + header.version = CURRENT_OS_SAVE_VER; + header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it. + writeChunkHeader(out, header); + + // Start outputting the plain savegame data right after the chunk header. + out.writeUint16BE(currentDisk); + out.write(currentPartName, 13); + out.write(currentPrcName, 13); + out.write(currentRelName, 13); + out.write(currentMsgName, 13); + renderer->saveBgNames(out); + out.write(currentCtName, 13); + + saveObjectTable(out); + renderer->savePalette(out); + globalVars.save(out, NUM_MAX_VAR); + saveZoneData(out); + saveCommandVariables(out); + out.write(commandBuffer, 0x50); + saveZoneQuery(out); + + // FIXME: Save a proper name here, saving an empty string currently. + // 0x2925: Current music name (String, 13 bytes). + for (i = 0; i < 13; i++) { + out.writeByte(0); + } + // FIXME: Save proper value for this variable, currently writing zero + // 0x2932: Is music loaded? (Uint16BE, Boolean). + out.writeUint16BE(0); + // FIXME: Save proper value for this variable, currently writing zero + // 0x2934: Is music playing? (Uint16BE, Boolean). + out.writeUint16BE(0); + + out.writeUint16BE(renderer->_cmdY); + out.writeUint16BE(0); // Some unknown variable that seems to always be zero + out.writeUint16BE(allowPlayerInput); + out.writeUint16BE(playerCommand); + out.writeUint16BE(commandVar1); + out.writeUint16BE(isDrawCommandEnabled); + out.writeUint16BE(var5); + out.writeUint16BE(var4); + out.writeUint16BE(var3); + out.writeUint16BE(var2); + out.writeUint16BE(commandVar2); + out.writeUint16BE(renderer->_messageBg); + + // FIXME: Save proper value for this variable, currently writing zero. + // An unknown variable at 0x295E: adBgVar1 (Uint16BE). + out.writeUint16BE(0); + out.writeSint16BE(currentAdditionalBgIdx); + out.writeSint16BE(currentAdditionalBgIdx2); + // FIXME: Save proper value for this variable, currently writing zero. + // 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift. + out.writeUint16BE(0); + // FIXME: Save proper value for this variable, currently writing zero. + // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0? + out.writeUint16BE(0); + out.writeUint16BE(disableSystemMenu); + + saveAnimDataTable(out); + saveScreenParams(out); + saveGlobalScripts(out); + saveObjectScripts(out); + saveSeqList(out); + saveOverlayList(out); + saveBgIncrustList(out); +} + void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) { gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom @@ -1567,7 +2023,7 @@ void checkForPendingDataLoad(void) { // fixes a crash when failing copy protection in Amiga or Atari ST // versions of Future Wars. if (loadPrcOk) { - addScriptToList0(1); + addScriptToGlobalScripts(1); } else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) { // We only show an error here for other files than the file that // is loaded if copy protection fails (i.e. L201.ANI). @@ -1740,6 +2196,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele const int8 *ptr2; int16 di; + debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d", + param1, objIdx, ptr, element.var8, element.var14, param3); + // In the original an error string is set and 0 is returned if the following doesn't hold assert(ptr); @@ -1845,6 +2304,19 @@ void processSeqListElement(SeqListElement &element) { int16 var_10; int16 var_4; int16 var_2; + + // Initial interpretations for variables addressed through ptr1 (8-bit addressing): + // These may be inaccurate! + // 0: ? + // 1: xRadius + // 2: yRadius + // 3: ? + // 4: xAdd + // 5: yAdd + // 6: ? + // 7: ? + // After this come (At least at positions 0, 1 and 3 in 16-bit addressing) + // 16-bit big-endian values used for addressing through ptr1. if (element.var12 < element.var10) { element.var12++; |