From 8b5c13feb4cf7e3d51cb7807daa69d67a0317a63 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Thu, 16 Jul 2020 21:20:50 -0230 Subject: [PATCH] Begin the process of converting all file open/close operations to be done in FSNode. This will eventually allow ZIP files (and any other compression scheme we use in the future) to read and write as if they were normal files. Basically an implementation of a mini-VFS. --- src/common/FSNodeZIP.cxx | 16 +++++++- src/common/FSNodeZIP.hxx | 2 + src/debugger/CartDebug.cxx | 3 +- src/emucore/Cart.cxx | 22 ++++++---- src/emucore/Cart.hxx | 5 ++- src/emucore/FSNode.cxx | 84 +++++++++++++++++++++++++++++--------- src/emucore/FSNode.hxx | 83 ++++++++++++++++++++++++------------- src/emucore/OSystem.cxx | 2 +- src/emucore/PropsSet.cxx | 34 +++++++++------ src/emucore/PropsSet.hxx | 4 +- 10 files changed, 179 insertions(+), 76 deletions(-) diff --git a/src/common/FSNodeZIP.cxx b/src/common/FSNodeZIP.cxx index 50e84ab3f..c49f8359f 100644 --- a/src/common/FSNodeZIP.cxx +++ b/src/common/FSNodeZIP.cxx @@ -181,7 +181,7 @@ size_t FilesystemNodeZIP::read(ByteBuffer& image) const while(myZipHandler->hasNext() && !found) found = myZipHandler->next() == _virtualPath; - return found ? uInt32(myZipHandler->decompress(image)) : 0; // TODO: 64bit + return found ? myZipHandler->decompress(image) : 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -197,6 +197,20 @@ size_t FilesystemNodeZIP::read(stringstream& image) const return size; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t FilesystemNodeZIP::write(const ByteBuffer& buffer, size_t size) const +{ + // TODO: Not yet implemented + throw runtime_error("ZIP file not writable"); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t FilesystemNodeZIP::write(const stringstream& buffer) const +{ + // TODO: Not yet implemented + throw runtime_error("ZIP file not writable"); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AbstractFSNodePtr FilesystemNodeZIP::getParent() const { diff --git a/src/common/FSNodeZIP.hxx b/src/common/FSNodeZIP.hxx index b2ae18f43..de8904315 100644 --- a/src/common/FSNodeZIP.hxx +++ b/src/common/FSNodeZIP.hxx @@ -64,6 +64,8 @@ class FilesystemNodeZIP : public AbstractFSNode size_t read(ByteBuffer& image) const override; size_t read(stringstream& image) const override; + size_t write(const ByteBuffer& buffer, size_t size) const override; + size_t write(const stringstream& buffer) const override; private: FilesystemNodeZIP(const string& zipfile, const string& virtualpath, diff --git a/src/debugger/CartDebug.cxx b/src/debugger/CartDebug.cxx index 3198eb9ea..228e7b5d0 100644 --- a/src/debugger/CartDebug.cxx +++ b/src/debugger/CartDebug.cxx @@ -1347,8 +1347,7 @@ string CartDebug::saveRom() const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26"; FilesystemNode node(myOSystem.defaultSaveDir() + rom); - ofstream out(node.getPath(), std::ios::binary); - if(out && myConsole.cartridge().saveROM(out)) + if(myConsole.cartridge().saveROM(node)) return "saved ROM as " + node.getShortPath(); else return DebuggerParser::red("failed to save ROM"); diff --git a/src/emucore/Cart.cxx b/src/emucore/Cart.cxx index 045d4c784..d4b5c5c22 100644 --- a/src/emucore/Cart.cxx +++ b/src/emucore/Cart.cxx @@ -15,6 +15,7 @@ // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ +#include "FSNode.hxx" #include "Settings.hxx" #include "System.hxx" #include "MD5.hxx" @@ -52,19 +53,24 @@ void Cartridge::setAbout(const string& about, const string& type, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool Cartridge::saveROM(ofstream& out) const +bool Cartridge::saveROM(const FilesystemNode& out) const { - size_t size = 0; - - const ByteBuffer& image = getImage(size); - if(size == 0) + try + { + size_t size = 0; + const ByteBuffer& image = getImage(size); + if(size == 0) + { + cerr << "save not supported" << endl; + return false; + } + out.write(image, size); + } + catch(...) { - cerr << "save not supported" << endl; return false; } - out.write(reinterpret_cast(image.get()), size); - return true; } diff --git a/src/emucore/Cart.hxx b/src/emucore/Cart.hxx index 8a2e4656e..008135a73 100644 --- a/src/emucore/Cart.hxx +++ b/src/emucore/Cart.hxx @@ -20,6 +20,7 @@ class Cartridge; class Properties; +class FilesystemNode; class CartDebugWidget; class CartRamWidget; class GuiObject; @@ -72,9 +73,9 @@ class Cartridge : public Device /** Save the internal (patched) ROM image. - @param out The output file stream to save the image + @param out The output file to save the image */ - bool saveROM(ofstream& out) const; + bool saveROM(const FilesystemNode& out) const; /** Lock/unlock bankswitching capability. The debugger will lock diff --git a/src/emucore/FSNode.cxx b/src/emucore/FSNode.cxx index 633eb6eaf..590705d03 100644 --- a/src/emucore/FSNode.cxx +++ b/src/emucore/FSNode.cxx @@ -13,12 +13,8 @@ // // See the file "License.txt" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// Based on code from ScummVM - Scumm Interpreter -// Copyright (C) 2002-2004 The ScummVM project //============================================================================ -#include "Cart.hxx" #include "FSNodeFactory.hxx" #include "FSNode.hxx" @@ -227,49 +223,48 @@ bool FilesystemNode::rename(const string& newfile) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - size_t FilesystemNode::read(ByteBuffer& buffer) const { - size_t size = 0; + size_t sizeRead = 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; + if (_realNode && (sizeRead = _realNode->read(buffer)) > 0) + return sizeRead; // Otherwise, the default behaviour is to read from a normal C++ ifstream - buffer = make_unique(Cartridge::maxSize()); ifstream in(getPath(), std::ios::binary); if (in) { in.seekg(0, std::ios::end); - std::streampos length = in.tellg(); + sizeRead = static_cast(in.tellg()); in.seekg(0, std::ios::beg); - if (length == 0) + if (sizeRead == 0) throw runtime_error("Zero-byte file"); - size = std::min(length, Cartridge::maxSize()); - in.read(reinterpret_cast(buffer.get()), size); + buffer = make_unique(sizeRead); + in.read(reinterpret_cast(buffer.get()), sizeRead); } else throw runtime_error("File open/read error"); - return size; + return sizeRead; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - size_t FilesystemNode::read(stringstream& buffer) const { - size_t size = 0; + size_t sizeRead = 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; + if (_realNode && (sizeRead = _realNode->read(buffer)) > 0) + return sizeRead; // Otherwise, the default behaviour is to read from a normal C++ ifstream // and convert to a stringstream @@ -277,17 +272,66 @@ size_t FilesystemNode::read(stringstream& buffer) const if (in) { in.seekg(0, std::ios::end); - std::streampos length = in.tellg(); + sizeRead = static_cast(in.tellg()); in.seekg(0, std::ios::beg); - if (length == 0) + if (sizeRead == 0) throw runtime_error("Zero-byte file"); - size = std::min(length, Cartridge::maxSize()); buffer << in.rdbuf(); } else throw runtime_error("File open/read error"); - return size; + return sizeRead; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t FilesystemNode::write(const ByteBuffer& buffer, size_t size) const +{ + size_t sizeWritten = 0; + + // First let the private subclass attempt to open the file + if (_realNode && (sizeWritten = _realNode->write(buffer, size)) > 0) + return sizeWritten; + + // Otherwise, the default behaviour is to write to a normal C++ ofstream + ofstream out(getPath(), std::ios::binary); + if (out) + { + out.write(reinterpret_cast(buffer.get()), size); + + out.seekp(0, std::ios::end); + sizeWritten = static_cast(out.tellp()); + out.seekp(0, std::ios::beg); + } + else + throw runtime_error("File open/write error"); + + return sizeWritten; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t FilesystemNode::write(const stringstream& buffer) const +{ + size_t sizeWritten = 0; + + // First let the private subclass attempt to open the file + if (_realNode && (sizeWritten = _realNode->write(buffer)) > 0) + return sizeWritten; + + // Otherwise, the default behaviour is to write to a normal C++ ofstream + ofstream out(getPath(), std::ios::binary); + if (out) + { + out << buffer.rdbuf(); + + out.seekp(0, std::ios::end); + sizeWritten = static_cast(out.tellp()); + out.seekp(0, std::ios::beg); + } + else + throw runtime_error("File open/write error"); + + return sizeWritten; } diff --git a/src/emucore/FSNode.hxx b/src/emucore/FSNode.hxx index 66a80ead1..f965eaa10 100644 --- a/src/emucore/FSNode.hxx +++ b/src/emucore/FSNode.hxx @@ -13,42 +13,24 @@ // // See the file "License.txt" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// Based on code from ScummVM - Scumm Interpreter -// Copyright (C) 2002-2004 The ScummVM project //============================================================================ #ifndef FS_NODE_HXX #define FS_NODE_HXX +#include + #include "bspf.hxx" /* * The API described in this header is meant to allow for file system browsing in a - * portable fashions. To this ends, multiple or single roots have to be supported + * portable fashion. To this end, multiple or single roots have to be supported * (compare Unix with a single root, Windows with multiple roots C:, D:, ...). * * To this end, we abstract away from paths; implementations can be based on - * paths (and it's left to them whether / or \ or : is the path separator :-); - * but it is also possible to use inodes or vrefs (MacOS 9) or anything else. - * - * You may ask now: "isn't this cheating? Why do we go through all this when we use - * a path in the end anyway?!?". - * Well, for once as long as we don't provide our own file open/read/write API, we - * still have to use fopen(). Since all our targets already support fopen(), it should - * be possible to get a fopen() compatible string for any file system node. - * - * Secondly, with this abstraction layer, we still avoid a lot of complications based on - * differences in FS roots, different path separators, or even systems with no real - * paths (MacOS 9 doesn't even have the notion of a "current directory"). - * And if we ever want to support devices with no FS in the classical sense (Palm...), - * we can build upon this. + * paths (and it's left to them whether / or \ or : is the path separator :-). */ -#include - -#include "bspf.hxx" - class FilesystemNode; class AbstractFSNode; using AbstractFSNodePtr = shared_ptr; @@ -233,7 +215,7 @@ class FilesystemNode /** * Read data (binary format) into the given buffer. * - * @param buffer The buffer to contain the data. + * @param buffer The buffer to contain the data (allocated in this method). * * @return The number of bytes read (0 in the case of failure) * This method can throw exceptions, and should be used inside @@ -252,6 +234,29 @@ class FilesystemNode */ size_t read(stringstream& buffer) const; + /** + * Write data (binary format) from the given buffer. + * + * @param buffer The buffer that contains the data. + * @param size The size of the buffer. + * + * @return The number of bytes written (0 in the case of failure) + * This method can throw exceptions, and should be used inside + * a try-catch block. + */ + size_t write(const ByteBuffer& buffer, size_t size) const; + + /** + * Write data (text format) from the given stream. + * + * @param buffer The buffer stream that contains the data. + * + * @return The number of bytes written (0 in the case of failure) + * This method can throw exceptions, and should be used inside + * a try-catch block. + */ + size_t write(const stringstream& buffer) const; + /** * The following methods are almost exactly the same as the various * getXXXX() methods above. Internally, they call the respective methods @@ -403,9 +408,8 @@ class AbstractFSNode /** * Read data (binary format) into the given buffer. * - * @param buffer The buffer to containing the data - * This will be allocated by the method, and must be - * freed by the caller. + * @param buffer The buffer to contain the data (allocated in this method). + * * @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. @@ -413,15 +417,38 @@ class AbstractFSNode virtual size_t read(ByteBuffer& buffer) const { return 0; } /** - * Read data (text format) into the given steam. + * Read data (text format) into the given stream. * - * @param buffer The buffer stream to containing the data + * @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. */ virtual size_t read(stringstream& buffer) const { return 0; } + + /** + * Write data (binary format) from the given buffer. + * + * @param buffer The buffer that contains the data. + * @param size The size of the buffer. + * + * @return The number of bytes written (0 in the case of failure) + * This method can throw exceptions, and should be used inside + * a try-catch block. + */ + virtual size_t write(const ByteBuffer& buffer, size_t size) const { return 0; } + + /** + * Write data (text format) from the given stream. + * + * @param buffer The buffer stream that contains the data. + * + * @return The number of bytes written (0 in the case of failure) + * This method can throw exceptions, and should be used inside + * a try-catch block. + */ + virtual size_t write(const stringstream& buffer) const { return 0; } }; #endif diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 8a5a16965..f6f13f05e 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -251,7 +251,7 @@ void OSystem::saveConfig() Logger::debug("Saving config options ..."); mySettings->save(); - if(myPropSet && myPropSet->save(myPropertiesFile.getPath())) + if(myPropSet && myPropSet->save(myPropertiesFile)) Logger::debug("Saving properties set ..."); } diff --git a/src/emucore/PropsSet.cxx b/src/emucore/PropsSet.cxx index ce5a34840..65bae06bd 100644 --- a/src/emucore/PropsSet.cxx +++ b/src/emucore/PropsSet.cxx @@ -19,6 +19,7 @@ #include "bspf.hxx" #include "FSNode.hxx" +#include "Logger.hxx" #include "DefProps.hxx" #include "Props.hxx" #include "PropsSet.hxx" @@ -42,22 +43,31 @@ void PropertiesSet::load(const FilesystemNode& file, bool save) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool PropertiesSet::save(const string& filename) const +bool PropertiesSet::save(const FilesystemNode& file) const { - // Only save properties when it won't create an empty file - FilesystemNode props(filename); - if(!props.exists() && myExternalProps.size() == 0) - return false; + try + { + // Only save properties when it won't create an empty file + if(!file.exists() && myExternalProps.size() == 0) + return false; - ofstream out(filename); - if(!out) - return false; + // Only save those entries in the external list + stringstream out; + for(const auto& i: myExternalProps) + out << i.second; - // Only save those entries in the external list - for(const auto& i: myExternalProps) - out << i.second; + file.write(out); + return true; + } + catch(const runtime_error& e) + { + Logger::error(e.what()); + } + catch(...) + { + } - return true; + return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/PropsSet.hxx b/src/emucore/PropsSet.hxx index 6ecd993b3..387b79bda 100644 --- a/src/emucore/PropsSet.hxx +++ b/src/emucore/PropsSet.hxx @@ -57,14 +57,14 @@ class PropertiesSet /** Save properties to the specified file. - @param filename Full pathname of output file to use + @param file The node representing the output file to use @return True on success, false on failure or save not needed Failure occurs if file couldn't be opened for writing, or if the file doesn't exist and a zero-byte file would be created */ - bool save(const string& filename) const; + bool save(const FilesystemNode& file) const; /** Get the property from the set with the given MD5.