Common::Error CineEngine::saveGameState(int slot, const Common::String &desc) { // Load savegame descriptions from index file loadSaveDirectory(); // Set description for selected slot making sure it ends with a trailing zero strncpy(currentSaveName[slot], desc.c_str(), 20); currentSaveName[slot][sizeof(CommandeType) - 1] = 0; // Update savegame descriptions Common::String indexFile = _targetName + ".dir"; Common::OutSaveFile *fHandle = _saveFileMan->openForSaving(indexFile); if (!fHandle) { warning("Unable to open file %s for saving", indexFile.c_str()); return Common::kUnknownError; } fHandle->write(currentSaveName, 10 * 20); delete fHandle; // Save game char saveFileName[256]; sprintf(saveFileName, "%s.%1d", _targetName.c_str(), slot); makeSave(saveFileName); checkDataDisk(-1); return Common::kNoError; }
void loadRel(char *pRelName) { uint16 numEntry; uint16 i; byte *ptr; checkDataDisk(-1); for (i = 0; i < NUM_MAX_REL; i++) { if (relTable[i].data) { free(relTable[i].data); relTable[i].data = NULL; relTable[i].size = 0; } } ptr = readBundleFile(findFileInBundle(pRelName)); setMouseCursor(MOUSE_CURSOR_DISK); numEntry = READ_BE_UINT16(ptr); ptr += 2; assert(numEntry <= NUM_MAX_REL); for (i = 0; i < numEntry; i++) { relTable[i].size = READ_BE_UINT16(ptr); ptr += 2; relTable[i].obj1Param1 = READ_BE_UINT16(ptr); ptr += 2; relTable[i].obj1Param2 = READ_BE_UINT16(ptr); ptr += 2; relTable[i].obj2Param = READ_BE_UINT16(ptr); ptr += 2; } for (i = 0; i < numEntry; i++) { if (relTable[i].size) { relTable[i].data = (byte *)malloc(relTable[i].size); assert(relTable[i].data); memcpy(relTable[i].data, ptr, relTable[i].size); ptr += relTable[i].size; } } #ifdef DUMP_SCRIPTS { uint16 s; char buffer[256]; for (s = 0; s < numEntry; s++) { if (relTable[s].size) { sprintf(buffer, "%s_%03d.txt", pRelName, s); decompileScript(relTable[s].data, NULL, relTable[s].size, s); dumpScript(buffer); } } } #endif }
/*! \todo Is script size of 0 valid? * \todo Fix script dump code */ void loadRel(char *pRelName) { uint16 numEntry; uint16 i; uint16 size, p1, p2, p3; byte *ptr, *dataPtr; checkDataDisk(-1); objectScripts.clear(); relTable.clear(); ptr = dataPtr = readBundleFile(findFileInBundle(pRelName)); setMouseCursor(MOUSE_CURSOR_DISK); numEntry = READ_BE_UINT16(ptr); ptr += 2; for (i = 0; i < numEntry; i++) { size = READ_BE_UINT16(ptr); ptr += 2; p1 = READ_BE_UINT16(ptr); ptr += 2; p2 = READ_BE_UINT16(ptr); ptr += 2; p3 = READ_BE_UINT16(ptr); ptr += 2; RawObjectScriptPtr tmp(new RawObjectScript(size, p1, p2, p3)); assert(tmp); relTable.push_back(tmp); } for (i = 0; i < numEntry; i++) { size = relTable[i]->_size; // TODO: delete the test? if (size) { relTable[i]->setData(*scriptInfo, ptr); ptr += size; } } free(dataPtr); #ifdef DUMP_SCRIPTS { uint16 s; char buffer[256]; for (s = 0; s < numEntry; s++) { if (relTable[s]->_size) { sprintf(buffer, "%s_%03d.txt", pRelName, s); decompileScript((const byte *)relTable[s]->getString(0), relTable[s]->_size, s); dumpScript(buffer); } } } #endif }
void loadObject(char *pObjectName) { debug(5, "loadObject(\"%s\")", pObjectName); uint16 numEntry; uint16 entrySize; uint16 i; byte *ptr, *dataPtr; checkDataDisk(-1); ptr = dataPtr = readBundleFile(findFileInBundle(pObjectName)); setMouseCursor(MOUSE_CURSOR_DISK); numEntry = READ_BE_UINT16(ptr); ptr += 2; entrySize = READ_BE_UINT16(ptr); ptr += 2; assert(numEntry <= NUM_MAX_OBJECT); for (i = 0; i < numEntry; i++) { if (g_cine->_objectTable[i].costume != -2 && g_cine->_objectTable[i].costume != -3) { // flag is keep? Common::MemoryReadStream readS(ptr, entrySize); g_cine->_objectTable[i].x = readS.readSint16BE(); g_cine->_objectTable[i].y = readS.readSint16BE(); g_cine->_objectTable[i].mask = readS.readUint16BE(); g_cine->_objectTable[i].frame = readS.readSint16BE(); g_cine->_objectTable[i].costume = readS.readSint16BE(); readS.read(g_cine->_objectTable[i].name, 20); g_cine->_objectTable[i].part = readS.readUint16BE(); } ptr += entrySize; } if (!strcmp(pObjectName, "INTRO.OBJ")) { for (i = 0; i < 10; i++) { g_cine->_objectTable[i].costume = 0; } } free(dataPtr); }
void CineEngine::makeSave(char *saveFileName) { Common::SharedPtr<Common::OutSaveFile> fHandle(_saveFileMan->openForSaving(saveFileName)); setMouseCursor(MOUSE_CURSOR_DISK); if (!fHandle) { renderer->drawString(otherMessages[1], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); } else { if (getGameType() == GType_FW) { makeSaveFW(*fHandle); } else { makeSaveOS(*fHandle); } } setMouseCursor(MOUSE_CURSOR_NORMAL); }
void loadMsg(char *pMsgName) { uint32 sourceSize; checkDataDisk(-1); g_cine->_messageTable.clear(); byte *dataPtr = readBundleFile(findFileInBundle(pMsgName), &sourceSize); setMouseCursor(MOUSE_CURSOR_DISK); uint count = READ_BE_UINT16(dataPtr); uint messageLenPos = 2; uint messageDataPos = messageLenPos + 2 * count; // Read in the messages for (uint i = 0; i < count; i++) { // Read message's length uint messageLen = READ_BE_UINT16(dataPtr + messageLenPos); messageLenPos += 2; // Store the read message. // This code works around input data that has empty strings residing outside the input // buffer (e.g. message indexes 58-254 in BATEAU.MSG in PROCS08 in Operation Stealth). if (messageDataPos < sourceSize) { g_cine->_messageTable.push_back((const char *)(dataPtr + messageDataPos)); } else { if (messageLen > 0) { // Only warn about overflowing non-empty strings warning("loadMsg(%s): message (%d. / %d) is overflowing the input buffer. Replacing it with an empty string", pMsgName, i + 1, count); } else { debugC(5, kCineDebugPart, "loadMsg(%s): empty message (%d. / %d) resides outside input buffer", pMsgName, i + 1, count); } // Message resides outside the input buffer so we replace it with an empty string g_cine->_messageTable.push_back(""); } // Jump to the next message messageDataPos += messageLen; } free(dataPtr); }
/** * @todo Is script size of 0 valid? * @todo Fix script dump code * @return Was the loading successful? */ bool loadPrc(const char *pPrcName) { byte i; uint16 numScripts; byte *scriptPtr, *dataPtr; assert(pPrcName); g_cine->_globalScripts.clear(); g_cine->_scriptTable.clear(); // This is copy protection. Used to hang the machine if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) { Common::Event event; event.type = Common::EVENT_RTL; g_system->getEventManager()->pushEvent(event); return false; } checkDataDisk(-1); if ((g_cine->getGameType() == Cine::GType_FW) && (!scumm_stricmp(pPrcName, BOOT_PRC_NAME) || !scumm_stricmp(pPrcName, "demo.prc"))) { scriptPtr = dataPtr = readFile(pPrcName, (g_cine->getFeatures() & GF_CRYPTED_BOOT_PRC) != 0); } else { scriptPtr = dataPtr = readBundleFile(findFileInBundle(pPrcName)); } assert(scriptPtr); setMouseCursor(MOUSE_CURSOR_DISK); numScripts = READ_BE_UINT16(scriptPtr); scriptPtr += 2; assert(numScripts <= NUM_MAX_SCRIPT); for (i = 0; i < numScripts; i++) { RawScriptPtr tmp(new RawScript(READ_BE_UINT16(scriptPtr))); scriptPtr += 2; assert(tmp); g_cine->_scriptTable.push_back(tmp); } for (i = 0; i < numScripts; i++) { uint16 size = g_cine->_scriptTable[i]->_size; // TODO: delete the test? if (size) { g_cine->_scriptTable[i]->setData(*scriptInfo, scriptPtr); scriptPtr += size; } } free(dataPtr); #ifdef DUMP_SCRIPTS { uint16 s; char buffer[256]; for (s = 0; s < numScripts; s++) { if (scriptTable[s]->_size) { sprintf(buffer, "%s_%03d.txt", pPrcName, s); decompileScript((const byte *)scriptTable[s]->getString(0), scriptTable[s]->_size, s); dumpScript(buffer); } } } #endif return true; }
bool CineEngine::makeLoad(const Common::String &saveName) { Common::SharedPtr<Common::InSaveFile> saveFile(_saveFileMan->openForLoading(saveName)); if (!saveFile) { renderer->drawString(otherMessages[0], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); return false; } 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::SeekableReadStream> 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 we 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); } } setMouseCursor(MOUSE_CURSOR_NORMAL); return result; }
bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { char bgName[13]; // At savefile position 0x0000: currentDisk = in.readUint16BE(); // At 0x0002: in.read(currentPartName, 13); // At 0x000F: in.read(currentDatName, 13); // At 0x001C: musicIsPlaying = in.readSint16BE(); // 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); } if (strlen(currentPrcName)) { loadPrc(currentPrcName); } if (strlen(currentRelName)) { loadRel(currentRelName); } if (strlen(bgName)) { loadBg(bgName); } if (strlen(currentCtName)) { loadCtFW(currentCtName); } // At 0x005F: loadObjectTable(in); // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): renderer->restorePalette(in, 0); // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): g_cine->_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): char tempCommandBuffer[kMaxCommandBufferSize]; in.read(tempCommandBuffer, kMaxCommandBufferSize); g_cine->_commandBuffer = tempCommandBuffer; renderer->setCommand(g_cine->_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); } if (strlen(currentDatName)) { g_sound->loadMusic(currentDatName); if (musicIsPlaying) { g_sound->playMusic(); } } return !(in.eos() || in.err()); }
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, hdr.version); g_cine->_globalVars.load(in, NUM_MAX_VAR); loadZoneData(in); loadCommandVariables(in); char tempCommandBuffer[kMaxCommandBufferSize]; in.read(tempCommandBuffer, kMaxCommandBufferSize); g_cine->_commandBuffer = tempCommandBuffer; renderer->setCommand(g_cine->_commandBuffer); 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.eos() || in.err()); }