diff --git a/Changes.txt b/Changes.txt index cc1ca3b3b..76bc53cc4 100644 --- a/Changes.txt +++ b/Changes.txt @@ -18,6 +18,13 @@ * Extended global hotkeys for debug options. + * Added ability to load per-ROM properties file from a ZIP file containing + the ROM. This allows to distribute ROM and properties in one file, + which Stella can use directly. + +-Have fun! + + 6.2 to 6.2.1: (June 20, 2020) * Fixed Pitfall II ROM not working correctly. @@ -61,8 +68,6 @@ * The codebase now compiles under gcc6 again. Future versions will require gcc7, though. --Have fun! - 6.1.2 to 6.2: (June 7, 2020) diff --git a/src/common/FSNodeZIP.cxx b/src/common/FSNodeZIP.cxx index c5ace0f95..50e84ab3f 100644 --- a/src/common/FSNodeZIP.cxx +++ b/src/common/FSNodeZIP.cxx @@ -184,6 +184,19 @@ size_t FilesystemNodeZIP::read(ByteBuffer& image) const return found ? uInt32(myZipHandler->decompress(image)) : 0; // TODO: 64bit } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t FilesystemNodeZIP::read(stringstream& image) const +{ + // For now, we just read into a buffer and store in the stream + // TODO: maybe there's a more efficient way to do this? + ByteBuffer buffer; + size_t size = read(buffer); + if(size > 0) + image.write(reinterpret_cast(buffer.get()), size); + + return size; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AbstractFSNodePtr FilesystemNodeZIP::getParent() const { diff --git a/src/common/FSNodeZIP.hxx b/src/common/FSNodeZIP.hxx index 2a46afb4b..b2ae18f43 100644 --- a/src/common/FSNodeZIP.hxx +++ b/src/common/FSNodeZIP.hxx @@ -63,6 +63,7 @@ class FilesystemNodeZIP : public AbstractFSNode AbstractFSNodePtr getParent() const override; size_t read(ByteBuffer& image) const override; + size_t read(stringstream& image) const override; private: FilesystemNodeZIP(const string& zipfile, const string& virtualpath, diff --git a/src/emucore/FSNode.cxx b/src/emucore/FSNode.cxx index e9da05b03..633eb6eaf 100644 --- a/src/emucore/FSNode.cxx +++ b/src/emucore/FSNode.cxx @@ -225,7 +225,7 @@ bool FilesystemNode::rename(const string& newfile) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -size_t FilesystemNode::read(ByteBuffer& image) const +size_t FilesystemNode::read(ByteBuffer& buffer) const { size_t size = 0; @@ -234,11 +234,11 @@ size_t FilesystemNode::read(ByteBuffer& image) const throw runtime_error("File not found/readable"); // First let the private subclass attempt to open the file - if (_realNode && (size = _realNode->read(image)) > 0) + if (_realNode && (size = _realNode->read(buffer)) > 0) return size; // Otherwise, the default behaviour is to read from a normal C++ ifstream - image = make_unique(Cartridge::maxSize()); + buffer = make_unique(Cartridge::maxSize()); ifstream in(getPath(), std::ios::binary); if (in) { @@ -250,7 +250,41 @@ size_t FilesystemNode::read(ByteBuffer& image) const throw runtime_error("Zero-byte file"); size = std::min(length, Cartridge::maxSize()); - in.read(reinterpret_cast(image.get()), size); + in.read(reinterpret_cast(buffer.get()), size); + } + else + throw runtime_error("File open/read error"); + + return size; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t FilesystemNode::read(stringstream& buffer) const +{ + size_t size = 0; + + // File must actually exist + if (!(exists() && isReadable())) + throw runtime_error("File not found/readable"); + + // First let the private subclass attempt to open the file + if (_realNode && (size = _realNode->read(buffer)) > 0) + return size; + + // Otherwise, the default behaviour is to read from a normal C++ ifstream + // and convert to a stringstream + ifstream in(getPath(), std::ios::binary); + if (in) + { + in.seekg(0, std::ios::end); + std::streampos length = in.tellg(); + in.seekg(0, std::ios::beg); + + if (length == 0) + throw runtime_error("Zero-byte file"); + + size = std::min(length, Cartridge::maxSize()); + buffer << in.rdbuf(); } else throw runtime_error("File open/read error"); diff --git a/src/emucore/FSNode.hxx b/src/emucore/FSNode.hxx index 16360cafe..66a80ead1 100644 --- a/src/emucore/FSNode.hxx +++ b/src/emucore/FSNode.hxx @@ -241,6 +241,17 @@ class FilesystemNode */ size_t read(ByteBuffer& buffer) const; + /** + * Read data (text format) into the given stream. + * + * @param buffer The buffer stream to contain the data. + * + * @return The number of bytes read (0 in the case of failure) + * This method can throw exceptions, and should be used inside + * a try-catch block. + */ + size_t read(stringstream& buffer) const; + /** * The following methods are almost exactly the same as the various * getXXXX() methods above. Internally, they call the respective methods @@ -400,6 +411,17 @@ class AbstractFSNode * a try-catch block. */ virtual size_t read(ByteBuffer& buffer) const { return 0; } + + /** + * Read data (text format) into the given steam. + * + * @param buffer The buffer stream to containing the data + * + * @return The number of bytes read (0 in the case of failure) + * This method can throw exceptions, and should be used inside + * a try-catch block. + */ + virtual size_t read(stringstream& buffer) const { return 0; } }; #endif diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 0d1364790..8a5a16965 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -132,7 +132,7 @@ bool OSystem::create() << FilesystemNode(myConfigFile).getShortPath() << "'" << endl; buf << "Game properties: '" - << FilesystemNode(myPropertiesFile).getShortPath() << "'" << endl + << myPropertiesFile.getShortPath() << "'" << endl << "Cheat file: '" << FilesystemNode(myCheatFile).getShortPath() << "'" << endl << "Palette file: '" @@ -251,7 +251,7 @@ void OSystem::saveConfig() Logger::debug("Saving config options ..."); mySettings->save(); - if(myPropSet && myPropSet->save(myPropertiesFile)) + if(myPropSet && myPropSet->save(myPropertiesFile.getPath())) Logger::debug("Saving properties set ..."); } @@ -286,7 +286,7 @@ void OSystem::setConfigPaths() myCheatFile = FilesystemNode(myBaseDir + "stella.cht").getPath(); myPaletteFile = FilesystemNode(myBaseDir + "stella.pal").getPath(); - myPropertiesFile = FilesystemNode(myBaseDir + "stella.pro").getPath(); + myPropertiesFile = FilesystemNode(myBaseDir + "stella.pro"); #if 0 // Debug code diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 4958efead..baebd3375 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -545,7 +545,7 @@ class OSystem string myCheatFile; string myConfigFile; string myPaletteFile; - string myPropertiesFile; + FilesystemNode myPropertiesFile; FilesystemNode myRomFile; string myRomMD5; diff --git a/src/emucore/PropsSet.cxx b/src/emucore/PropsSet.cxx index a74a999e7..ce5a34840 100644 --- a/src/emucore/PropsSet.cxx +++ b/src/emucore/PropsSet.cxx @@ -24,13 +24,21 @@ #include "PropsSet.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PropertiesSet::load(const string& filename, bool save) +void PropertiesSet::load(const FilesystemNode& file, bool save) { - ifstream in(filename); - - Properties prop; - while(in >> prop) - insert(prop, save); + try + { + stringstream in; + if(file.exists() && file.read(in) > 0) + { + Properties prop; + while(in >> prop) + insert(prop, save); + } + } + catch(...) + { + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -166,8 +174,9 @@ void PropertiesSet::loadPerROM(const FilesystemNode& rom, const string& md5) // First, does this ROM have a per-ROM properties entry? // If so, load it into the database FilesystemNode propsNode(rom.getPathWithExt(".pro")); - if(propsNode.exists() && propsNode.isFile()) - load(propsNode.getPath(), false); + + if(propsNode.exists()) + load(propsNode, false); // Next, make sure we have a valid md5 and name Properties props; diff --git a/src/emucore/PropsSet.hxx b/src/emucore/PropsSet.hxx index f5a763ef8..6ecd993b3 100644 --- a/src/emucore/PropsSet.hxx +++ b/src/emucore/PropsSet.hxx @@ -48,11 +48,11 @@ class PropertiesSet Load properties from the specified file, and create an internal searchable list. - @param filename Full pathname of input file to use - @param save Indicates whether the properties should be saved - when the program exits + @param file The node representing the input file to use + @param save Indicates whether the properties should be saved + when the program exits */ - void load(const string& filename, bool save = true); + void load(const FilesystemNode& file, bool save = true); /** Save properties to the specified file.