static void writePixel(Common::WriteStream &file, const byte *&data, PixelFormat format) { if (format == kPixelFormatRGB) { file.writeByte(data[2]); file.writeByte(data[1]); file.writeByte(data[0]); file.writeByte(0xFF); data += 3; } else if (format == kPixelFormatBGR) { file.writeByte(data[0]); file.writeByte(data[1]); file.writeByte(data[2]); file.writeByte(0xFF); data += 3; } else if (format == kPixelFormatRGBA) { file.writeByte(data[2]); file.writeByte(data[1]); file.writeByte(data[0]); file.writeByte(data[3]); data += 4; } else if (format == kPixelFormatBGRA) { file.writeByte(data[0]); file.writeByte(data[1]); file.writeByte(data[2]); file.writeByte(data[3]); data += 4; } else throw Common::Exception("Unsupported pixel format: %d", (int) format); }
void TwoDAFile::writeASCII(Common::WriteStream &out) const { // Write header out.writeString("2DA V2.0\n"); if (!_defaultString.empty()) out.writeString(Common::UString::format("DEFAULT: %s", _defaultString.c_str())); out.writeByte('\n'); // Calculate column lengths std::vector<size_t> colLength; colLength.resize(_headers.size() + 1, 0); const Common::UString maxRow = Common::UString::format("%d", (int)_rows.size() - 1); colLength[0] = maxRow.size(); for (size_t i = 0; i < _headers.size(); i++) colLength[i + 1] = _headers[i].size(); for (size_t i = 0; i < _rows.size(); i++) { for (size_t j = 0; j < _rows[i]->_data.size(); j++) { const bool needQuote = _rows[i]->_data[j].contains(' '); const size_t length = needQuote ? _rows[i]->_data[j].size() + 2 : _rows[i]->_data[j].size(); colLength[j + 1] = MAX<size_t>(colLength[j + 1], length); } } // Write column headers out.writeString(Common::UString::format("%-*s", (int)colLength[0], "")); for (size_t i = 0; i < _headers.size(); i++) out.writeString(Common::UString::format(" %-*s", (int)colLength[i + 1], _headers[i].c_str())); out.writeByte('\n'); // Write array for (size_t i = 0; i < _rows.size(); i++) { out.writeString(Common::UString::format("%*u", (int)colLength[0], (uint)i)); for (size_t j = 0; j < _rows[i]->_data.size(); j++) { const bool needQuote = _rows[i]->_data[j].contains(' '); Common::UString cellString; if (needQuote) cellString = Common::UString::format("\"%s\"", _rows[i]->_data[j].c_str()); else cellString = _rows[i]->_data[j]; out.writeString(Common::UString::format(" %-*s", (int)colLength[j + 1], cellString.c_str())); } out.writeByte('\n'); } out.flush(); }
bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb) { if (thumb.format.bytesPerPixel != 2) { warning("trying to save thumbnail with bpp different than 2"); return false; } ThumbnailHeader header; header.type = MKTAG('T','H','M','B'); header.size = ThumbnailHeaderSize + thumb.w*thumb.h*thumb.format.bytesPerPixel; header.version = THMB_VERSION; header.width = thumb.w; header.height = thumb.h; header.bpp = thumb.format.bytesPerPixel; out.writeUint32BE(header.type); out.writeUint32BE(header.size); out.writeByte(header.version); out.writeUint16BE(header.width); out.writeUint16BE(header.height); out.writeByte(header.bpp); // TODO: for later this shouldn't be casted to uint16... uint16 *pixels = (uint16 *)thumb.pixels; for (uint16 p = 0; p < thumb.w*thumb.h; ++p, ++pixels) out.writeUint16BE(*pixels); return true; }
void TimerManager::saveDataToFile(Common::WriteStream &file) const { const uint32 saveTime = _isPaused ? _pauseStart : _system->getMillis(); file.writeByte(count()); for (CIterator pos = _timers.begin(); pos != _timers.end(); ++pos) { file.writeByte(pos->id); file.writeByte(pos->enabled); file.writeSint32BE(pos->countdown); file.writeSint32BE(pos->lastUpdate - saveTime); } }
/* Simple LZSS decompression. * * Code loosely based on DSDecmp by Barubary, released under the terms of the MIT license. * * See <https://github.com/gravgun/dsdecmp/blob/master/CSharp/DSDecmp/Formats/Nitro/LZ10.cs#L121> * and <https://code.google.com/p/dsdecmp/>. */ static void decompress10(Common::SeekableReadStream &small, Common::WriteStream &out, uint32 size) { byte buffer[0x10000]; uint32 bufferPos = 0; uint16 flags = 0xFF00; uint32 outSize = 0; while (outSize < size) { // Only our canaries left => Read flags for the next 8 blocks if (flags == 0xFF00) flags = (small.readByte() << 8) | 0x00FF; if (flags & 0x8000) { // Copy from buffer const byte data1 = small.readByte(); const byte data2 = small.readByte(); // Copy how many bytes from where (relative) in the buffer? const uint8 length = (data1 >> 4) + 3; const uint16 offset = (((data1 & 0x0F) << 8) | data2) + 1; // Direct offset. Add size of the buffer once, to protect from overroll uint32 copyOffset = bufferPos + sizeof(buffer) - offset; // Copy length bytes (and store each back into the buffer) for (uint8 i = 0; i < length; i++, copyOffset++) { if ((copyOffset % sizeof(buffer)) > outSize) throw Common::Exception("Tried to copy past the buffer"); const byte data = buffer[copyOffset % sizeof(buffer)]; out.writeByte(data); outSize++; buffer[bufferPos] = data; bufferPos = (bufferPos + 1) % sizeof(buffer); } } else { // Read literal byte const byte data = small.readByte(); out.writeByte(data); outSize++; buffer[bufferPos] = data; bufferPos = (bufferPos + 1) % sizeof(buffer); } flags <<= 1; }
void LocString::writeLocString(Common::WriteStream &stream, bool withNullTerminate) const { for (StringMap::const_iterator iter = _strings.begin(); iter != _strings.end() ; iter++) { stream.writeUint32LE((*iter).first); stream.writeUint32LE((*iter).second.size()); stream.write((*iter).second.c_str(), (*iter).second.size()); if (withNullTerminate) stream.writeByte(0); } }
bool LureEngine::saveGame(uint8 slotNumber, Common::String &caption) { Common::WriteStream *f = this->_saveFileMan->openForSaving( generateSaveName(slotNumber)); if (f == NULL) return false; f->write("lure", 5); f->writeByte(getLanguage()); f->writeByte(LURE_SAVEGAME_MINOR); f->writeString(caption); f->writeByte(0); // End of string terminator Resources::getReference().saveToStream(f); Game::getReference().saveToStream(f); Sound.saveToStream(f); Fights.saveToStream(f); Room::getReference().saveToStream(f); delete f; return true; }
bool SavePartInfo::write(Common::WriteStream &stream) const { if (!_header.write(stream)) return false; stream.writeUint32LE(_gameID); stream.writeUint32LE(_gameVersion); stream.writeByte(_endian); stream.writeUint32LE(_varCount); stream.writeUint32LE(_descMaxLength); if (stream.write(_desc, _descMaxLength) != _descMaxLength) return false; return flushStream(stream); }
void TwoDAFile::writeCSV(Common::WriteStream &out) const { // Write column headers for (size_t i = 0; i < _headers.size(); i++) { const bool needQuote = _headers[i].contains(','); if (needQuote) out.writeByte('"'); out.writeString(_headers[i]); if (needQuote) out.writeByte('"'); if (i < (_headers.size() - 1)) out.writeByte(','); } out.writeByte('\n'); // Write array for (size_t i = 0; i < _rows.size(); i++) { for (size_t j = 0; j < _rows[i]->_data.size(); j++) { const bool needQuote = _rows[i]->_data[j].contains(','); if (needQuote) out.writeByte('"'); if (_rows[i]->_data[j] != "****") out.writeString(_rows[i]->_data[j]); if (needQuote) out.writeByte('"'); if (j < (_rows[i]->_data.size() - 1)) out.writeByte(','); } out.writeByte('\n'); } out.flush(); }
Common::WriteStream *KyraEngine_v1::openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const { if (shouldQuit()) return 0; Common::WriteStream *out = 0; if (!(out = _saveFileMan->openForSaving(filename))) { warning("Can't create file '%s', game not saved", filename); return 0; } // Savegame version out->writeUint32BE(MKTAG('W', 'W', 'S', 'V')); out->writeByte(_flags.gameID); out->writeUint32BE(CURRENT_SAVE_VERSION); out->write(saveName, strlen(saveName) + 1); if (_flags.isTalkie) out->writeUint32BE(GF_TALKIE); else if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) out->writeUint32BE(GF_FMTOWNS); else out->writeUint32BE(GF_FLOPPY); if (out->err()) { warning("Can't write file '%s'. (Disk full?)", filename); delete out; return 0; } Graphics::Surface *genThumbnail = 0; if (!thumbnail) thumbnail = genThumbnail = generateSaveThumbnail(); if (thumbnail) Graphics::saveThumbnail(*out, *thumbnail); else Graphics::saveThumbnail(*out); if (genThumbnail) { genThumbnail->free(); delete genThumbnail; } return out; }
bool SavePartSprite::write(Common::WriteStream &stream) const { if (!_header.write(stream)) return false; // The sprite's dimensions stream.writeUint32LE(_width); stream.writeUint32LE(_height); stream.writeByte(_trueColor); // Sprite data if (stream.write(_dataSprite, _spriteSize) != _spriteSize) return false; // Palette data if (stream.write(_dataPalette, 768) != 768) return false; return flushStream(stream); }
reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); #ifdef ENABLE_SCI32 // Cache the file existence result for the Phantasmagoria // save index file, as the game scripts keep checking for // its existence. if (name == PHANTASMAGORIA_SAVEGAME_INDEX && s->_virtualIndexFile) return TRUE_REG; #endif bool exists = false; // Check for regular file exists = Common::File::exists(name); // Check for a savegame with the name Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); if (!exists) exists = !saveFileMan->listSavefiles(name).empty(); // Try searching for the file prepending "target-" const Common::String wrappedName = g_sci->wrapFilename(name); if (!exists) { exists = !saveFileMan->listSavefiles(wrappedName).empty(); } // SCI2+ debug mode if (DebugMan.isDebugChannelEnabled(kDebugLevelDebugMode)) { if (!exists && name == "1.scr") // PQ4 exists = true; if (!exists && name == "18.scr") // QFG4 exists = true; if (!exists && name == "99.scr") // GK1, KQ7 exists = true; if (!exists && name == "classes") // GK2, SQ6, LSL7 exists = true; } // Special case for non-English versions of LSL5: The English version of // LSL5 calls kFileIO(), case K_FILEIO_OPEN for reading to check if // memory.drv exists (which is where the game's password is stored). If // it's not found, it calls kFileIO() again, case K_FILEIO_OPEN for // writing and creates a new file. Non-English versions call kFileIO(), // case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be // found. We create a default memory.drv file with no password, so that // the game can continue. if (!exists && name == "memory.drv") { // Create a new file, and write the bytes for the empty password // string inside byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 }; Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName); for (int i = 0; i < 10; i++) outFile->writeByte(defaultContent[i]); outFile->finalize(); exists = !outFile->err(); // check whether we managed to create the file. delete outFile; } // Special case for KQ6 Mac: The game checks for two video files to see // if they exist before it plays them. Since we support multiple naming // schemes for resource fork files, we also need to support that here in // case someone has a "HalfDome.bin" file, etc. if (!exists && g_sci->getGameId() == GID_KQ6 && g_sci->getPlatform() == Common::kPlatformMacintosh && (name == "HalfDome" || name == "Kq6Movie")) exists = Common::MacResManager::exists(name); debugC(kDebugLevelFile, "kFileIO(fileExists) %s -> %d", name.c_str(), exists); return make_reg(0, exists); }
void TwoDAFile::writeBinary(Common::WriteStream &out) const { const size_t columnCount = _headers.size(); const size_t rowCount = _rows.size(); const size_t cellCount = columnCount * rowCount; out.writeString("2DA V2.b\n"); // Write the column headers for (std::vector<Common::UString>::const_iterator h = _headers.begin(); h != _headers.end(); ++h) { out.writeString(*h); out.writeByte('\t'); } out.writeByte('\0'); // Write the row indices out.writeUint32LE((uint32) rowCount); for (size_t i = 0; i < rowCount; i++) { out.writeString(Common::composeString(i)); out.writeByte('\t'); } /* Deduplicate cell data strings. Binary 2DA files don't store the * data for each cell directly: instead, each cell contains an offset * into a data array. This way, cells with the same data only need to * to store this data once. * * The original binary 2DA files in KotOR/KotOR2 make extensive use * of that, and we should do this as well. * * Basically, this involves going through each cell, and looking up * if we already saved this particular piece of data. If not, save * it, otherwise only remember the offset. There's no need to be * particularly smart about it, so we're just doing it the naive * O(n^2) way. */ std::vector<Common::UString> data; std::vector<size_t> offsets; data.reserve(cellCount); offsets.reserve(cellCount); size_t dataSize = 0; std::vector<size_t> cells; cells.reserve(cellCount); for (size_t i = 0; i < rowCount; i++) { assert(_rows[i]); for (size_t j = 0; j < columnCount; j++) { const Common::UString cell = _rows[i]->getString(j); // Do we already know about this cell data string? size_t foundCell = SIZE_MAX; for (size_t k = 0; k < data.size(); k++) { if (data[k] == cell) { foundCell = k; break; } } // If not, add it to the cell data array if (foundCell == SIZE_MAX) { foundCell = data.size(); data.push_back(cell); offsets.push_back(dataSize); dataSize += data.back().size() + 1; if (dataSize > 65535) throw Common::Exception("TwoDAFile::writeBinary(): Cell data size overflow"); } // Remember the offset to the cell data array cells.push_back(offsets[foundCell]); } } // Write cell data offsets for (std::vector<size_t>::const_iterator c = cells.begin(); c != cells.end(); ++c) out.writeUint16LE((uint16) *c); // Size of the all cell data strings out.writeUint16LE((uint16) dataSize); // Write cell data strings for (std::vector<Common::UString>::const_iterator d = data.begin(); d != data.end(); ++d) { out.writeString(*d); out.writeByte('\0'); } }