Almost all file I/O now goes through FilesystemNode::read/write, instead of raw C++ fstreams.

This allows data to be stored in a ZIP archive and for Stella to use it as if it were a normal file.
Still TODO is add ZIP write support.
This commit is contained in:
Stephen Anthony 2020-07-25 12:57:12 -02:30
parent 02176d1c7e
commit 155839fb0b
28 changed files with 301 additions and 253 deletions

View File

@ -22,9 +22,14 @@
* Allow taking snapshots from within Time Machine dialog
* 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.
* Added ability to access most files that Stella uses from within a ZIP
file. This includes the following:
- Per-ROM properties file (so one can distribute a ROM and its
associated properties).
- Debugger symbol (.sym) and list (.lst) files, etc.
- Several others, as we extend the support.
Basically, you are now able to put many files that Stella uses inside
one ZIP file, and distribute just that file.
* Replaced "Re-disassemble" with "Disassemble @ current line" in debugger

View File

@ -213,10 +213,9 @@ void CheatManager::enable(const string& code, bool enable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CheatManager::loadCheatDatabase()
{
const string& cheatfile = myOSystem.cheatFile();
ifstream in(cheatfile);
if(!in)
return;
stringstream in;
try { myOSystem.cheatFile().read(in); }
catch(...) { return; }
string line, md5, cheat;
string::size_type one, two, three, four;
@ -253,13 +252,12 @@ void CheatManager::saveCheatDatabase()
if(!myListIsDirty)
return;
const string& cheatfile = myOSystem.cheatFile();
ofstream out(cheatfile);
if(!out)
return;
stringstream out;
for(const auto& iter: myCheatMap)
out << "\"" << iter.first << "\" " << "\"" << iter.second << "\"" << endl;
try { myOSystem.cheatFile().write(out); }
catch(...) { return; }
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -52,7 +52,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
throw runtime_error(s);
};
ifstream in(filename, std::ios_base::binary);
std::ifstream in(filename, std::ios_base::binary);
if(!in.is_open())
loadImageERROR("No snapshot found");
@ -125,7 +125,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::saveImage(const string& filename, const VariantList& comments)
{
ofstream out(filename, std::ios_base::binary);
std::ofstream out(filename, std::ios_base::binary);
if(!out.is_open())
throw runtime_error("ERROR: Couldn't create snapshot file");
@ -156,7 +156,7 @@ void PNGLibrary::saveImage(const string& filename, const VariantList& comments)
void PNGLibrary::saveImage(const string& filename, const FBSurface& surface,
const Common::Rect& rect, const VariantList& comments)
{
ofstream out(filename, std::ios_base::binary);
std::ofstream out(filename, std::ios_base::binary);
if(!out.is_open())
throw runtime_error("ERROR: Couldn't create snapshot file");
@ -182,7 +182,7 @@ void PNGLibrary::saveImage(const string& filename, const FBSurface& surface,
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::saveImageToDisk(ofstream& out, const vector<png_bytep>& rows,
void PNGLibrary::saveImageToDisk(std::ofstream& out, const vector<png_bytep>& rows,
png_uint_32 width, png_uint_32 height, const VariantList& comments)
{
png_structp png_ptr = nullptr;
@ -297,7 +297,7 @@ void PNGLibrary::takeSnapshot(uInt32 number)
// Figure out the correct snapshot name
string filename;
bool showmessage = number == 0;
string sspath = myOSystem.snapshotSaveDir() +
string sspath = myOSystem.snapshotSaveDir().getPath() +
(myOSystem.settings().getString("snapname") != "int" ?
myOSystem.romFile().getNameWithExt("")
: myOSystem.console().properties().get(PropType::Cart_Name));
@ -456,21 +456,21 @@ void PNGLibrary::writeComments(png_structp png_ptr, png_infop info_ptr,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::png_read_data(png_structp ctx, png_bytep area, png_size_t size)
{
(static_cast<ifstream*>(png_get_io_ptr(ctx)))->read(
(static_cast<std::ifstream*>(png_get_io_ptr(ctx)))->read(
reinterpret_cast<char *>(area), size);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::png_write_data(png_structp ctx, png_bytep area, png_size_t size)
{
(static_cast<ofstream*>(png_get_io_ptr(ctx)))->write(
(static_cast<std::ofstream*>(png_get_io_ptr(ctx)))->write(
reinterpret_cast<const char *>(area), size);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::png_io_flush(png_structp ctx)
{
(static_cast<ofstream*>(png_get_io_ptr(ctx)))->flush();
(static_cast<std::ofstream*>(png_get_io_ptr(ctx)))->flush();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -165,7 +165,7 @@ class PNGLibrary
@param height The height of the PNG image
@param comments The text comments to add to the PNG image
*/
void saveImageToDisk(ofstream& out, const vector<png_bytep>& rows,
void saveImageToDisk(std::ofstream& out, const vector<png_bytep>& rows,
png_uint_32 width, png_uint_32 height,
const VariantList& comments);

View File

@ -85,11 +85,13 @@ void PaletteHandler::showAdjustableMessage()
{
const ConsoleTiming timing = myOSystem.console().timing();
const bool isNTSC = timing == ConsoleTiming::ntsc;
const float value = myOSystem.console().timing() == ConsoleTiming::pal ? myPhasePAL : myPhaseNTSC;
const float value =
myOSystem.console().timing() == ConsoleTiming::pal ? myPhasePAL : myPhaseNTSC;
buf << std::fixed << std::setprecision(1) << value << DEGREE;
myOSystem.frameBuffer().showMessage("Palette phase shift", buf.str(), value,
(isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) - MAX_SHIFT,
(isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) + MAX_SHIFT);
myOSystem.frameBuffer().showMessage(
"Palette phase shift", buf.str(), value,
(isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) - MAX_SHIFT,
(isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) + MAX_SHIFT);
}
else
{
@ -328,29 +330,25 @@ void PaletteHandler::loadUserPalette()
if (!myOSystem.checkUserPalette(true))
return;
const string& palette = myOSystem.paletteFile();
ifstream in(palette, std::ios::binary);
ByteBuffer in;
try { myOSystem.paletteFile().read(in); }
catch(...) { return; }
// Now that we have valid data, create the user-defined palettes
std::array<uInt8, 3> pixbuf; // Temporary buffer for one 24-bit pixel
for(int i = 0; i < 128; i++) // NTSC palette
uInt8* pixbuf = in.get();
for(int i = 0; i < 128; i++, pixbuf += 3) // NTSC palette
{
in.read(reinterpret_cast<char*>(pixbuf.data()), 3);
const uInt32 pixel = (int(pixbuf[0]) << 16) + (int(pixbuf[1]) << 8) + int(pixbuf[2]);
ourUserNTSCPalette[(i<<1)] = pixel;
}
for(int i = 0; i < 128; i++) // PAL palette
for(int i = 0; i < 128; i++, pixbuf += 3) // PAL palette
{
in.read(reinterpret_cast<char*>(pixbuf.data()), 3);
const uInt32 pixel = (int(pixbuf[0]) << 16) + (int(pixbuf[1]) << 8) + int(pixbuf[2]);
ourUserPALPalette[(i<<1)] = pixel;
}
std::array<uInt32, 16> secam; // All 8 24-bit pixels, plus 8 colorloss pixels
for(int i = 0; i < 8; i++) // SECAM palette
for(int i = 0; i < 8; i++, pixbuf += 3) // SECAM palette
{
in.read(reinterpret_cast<char*>(pixbuf.data()), 3);
const uInt32 pixel = (int(pixbuf[0]) << 16) + (int(pixbuf[1]) << 8) + int(pixbuf[2]);
secam[(i<<1)] = pixel;
secam[(i<<1)+1] = 0;

View File

@ -64,8 +64,6 @@ using std::istream;
using std::ostream;
using std::fstream;
using std::iostream;
using std::ifstream;
using std::ofstream;
using std::ostringstream;
using std::istringstream;
using std::stringstream;

View File

@ -28,8 +28,8 @@ namespace {
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const string& filename)
: myFilename(filename)
KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const FilesystemNode& file)
: myFile(file)
{
}
@ -41,10 +41,14 @@ std::map<string, Variant> KeyValueRepositoryConfigfile::load()
string line, key, value;
string::size_type equalPos, garbage;
ifstream in(myFilename);
if(!in || !in.is_open()) {
Logger::error("ERROR: Couldn't load from settings file " + myFilename);
stringstream in;
try
{
myFile.read(in);
}
catch(...)
{
Logger::error("ERROR: Couldn't load from settings file " + myFile.getShortPath());
return values;
}
@ -79,13 +83,7 @@ std::map<string, Variant> KeyValueRepositoryConfigfile::load()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyValueRepositoryConfigfile::save(const std::map<string, Variant>& values)
{
ofstream out(myFilename);
if(!out || !out.is_open()) {
Logger::error("ERROR: Couldn't save to settings file " + myFilename);
return;
}
stringstream out;
out << "; Stella configuration file" << endl
<< ";" << endl
<< "; Lines starting with ';' are comments and are ignored." << endl
@ -104,4 +102,13 @@ void KeyValueRepositoryConfigfile::save(const std::map<string, Variant>& values)
// Write out each of the key and value pairs
for(const auto& pair: values)
out << pair.first << " = " << pair.second << endl;
try
{
myFile.write(out);
}
catch(...)
{
Logger::error("ERROR: Couldn't save to settings file " + myFile.getShortPath());
}
}

View File

@ -18,13 +18,14 @@
#ifndef KEY_VALUE_REPOSITORY_CONFIGFILE_HXX
#define KEY_VALUE_REPOSITORY_CONFIGFILE_HXX
#include "FSNode.hxx"
#include "KeyValueRepository.hxx"
class KeyValueRepositoryConfigfile : public KeyValueRepository
{
public:
explicit KeyValueRepositoryConfigfile(const string& filename);
explicit KeyValueRepositoryConfigfile(const FilesystemNode& file);
std::map<string, Variant> load() override;
@ -34,7 +35,7 @@ class KeyValueRepositoryConfigfile : public KeyValueRepository
private:
const string& myFilename;
FilesystemNode myFile;
};
#endif // KEY_VALUE_REPOSITORY_CONFIGFILE_HXX

View File

@ -747,7 +747,7 @@ string CartDebug::loadListFile()
try
{
if(lst.read(in) == 0)
return DebuggerParser::red("list file '" + lst.getShortPath() + "' not readable");
return DebuggerParser::red("list file '" + lst.getShortPath() + "' not found");
}
catch(...)
{
@ -811,7 +811,7 @@ string CartDebug::loadSymbolFile()
try
{
if(sym.read(in) == 0)
return DebuggerParser::red("symbol file '" + sym.getShortPath() + "' not readable");
return DebuggerParser::red("symbol file '" + sym.getShortPath() + "' not found");
}
catch(...)
{
@ -862,15 +862,14 @@ string CartDebug::loadConfigFile()
// on the actual ROM filename
FilesystemNode romNode(myOSystem.romFile().getPathWithExt(".cfg"));
FilesystemNode cfg(myOSystem.cfgDir() + romNode.getName());
FilesystemNode cfg = myOSystem.cfgDir(); cfg /= romNode.getName();
if(!cfg.isReadable())
return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not found");
stringstream in;
try
{
if(cfg.read(in) == 0)
return "Unable to load directives from " + cfg.getPath();
cfg.read(in);
}
catch(...)
{
@ -1002,12 +1001,11 @@ string CartDebug::saveConfigFile()
try
{
FilesystemNode romNode(myOSystem.romFile().getPathWithExt(".cfg"));
FilesystemNode cfg(myOSystem.cfgDir() + romNode.getName());
FilesystemNode cfg = myOSystem.cfgDir(); cfg /= romNode.getName();
if(!cfg.getParent().isWritable())
return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not writable");
size_t size = cfg.write(out);
if(size == 0)
if(cfg.write(out) == 0)
return "Unable to save directives to " + cfg.getShortPath();
if(myConsole.cartridge().romBankCount() > 1)
@ -1016,7 +1014,7 @@ string CartDebug::saveConfigFile()
}
catch(const runtime_error& e)
{
retVal << e.what();
retVal << "Unable to save directives: " << e.what();
}
return retVal.str();
}
@ -1331,24 +1329,21 @@ string CartDebug::saveDisassembly()
// And finally, output the disassembly
out << buf.str();
const string& propsname =
myConsole.properties().get(PropType::Cart_Name) + ".asm";
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + propsname);
stringstream retVal;
try
{
const string& propsname =
myConsole.properties().get(PropType::Cart_Name) + ".asm";
FilesystemNode node(myOSystem.defaultSaveDir() + propsname);
size_t size = node.write(out);
if(size == 0)
return "Unable to save disassembly to " + node.getShortPath();
node.write(out);
if(myConsole.cartridge().romBankCount() > 1)
retVal << DebuggerParser::red("disassembly for multi-bank ROM not fully supported\n");
retVal << "saved " << node.getShortPath() << " OK";
}
catch(const runtime_error& e)
catch(...)
{
retVal << e.what();
retVal << "Unable to save disassembly to " << node.getShortPath();
}
return retVal.str();
}
@ -1358,7 +1353,7 @@ string CartDebug::saveRom()
{
const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26";
FilesystemNode node(myOSystem.defaultSaveDir() + rom);
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + rom);
if(myConsole.cartridge().saveROM(node))
return "saved ROM as " + node.getShortPath();
else
@ -1376,15 +1371,13 @@ string CartDebug::saveAccessFile()
try
{
const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".csv";
FilesystemNode node(myOSystem.defaultSaveDir() + rom);
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + rom);
size_t size = node.write(out);
if(size > 0)
return "saved access counters as " + node.getShortPath();
node.write(out);
return "saved access counters as " + node.getShortPath();
}
catch(const runtime_error& e)
catch(...)
{
return e.what();
}
return DebuggerParser::red("failed to save access counters file");
}

View File

@ -167,7 +167,7 @@ string Debugger::autoExec(StringList* history)
ostringstream buf;
// autoexec.script is always run
FilesystemNode autoexec(myOSystem.baseDir() + "autoexec.script");
FilesystemNode autoexec(myOSystem.baseDir().getPath() + "autoexec.script");
buf << "autoExec():" << endl
<< myParser->exec(autoexec, history) << endl;
@ -304,7 +304,7 @@ int Debugger::step(bool save)
myOSystem.console().tia().updateScanlineByStep().flushLineCache();
lockSystem();
if(save)
if(save)
addState("step");
return int(mySystem.cycles() - startCycle);
}

View File

@ -132,9 +132,9 @@ string DebuggerParser::exec(const FilesystemNode& file, StringList* history)
{
if(file.exists())
{
ifstream in(file.getPath());
if(!in.is_open())
return red("script file \'" + file.getShortPath() + "\' not found");
stringstream in;
try { file.read(in); }
catch(...) { return red("script file \'" + file.getShortPath() + "\' not found"); }
ostringstream buf;
int count = 0;
@ -633,11 +633,7 @@ string DebuggerParser::saveScriptFile(string file)
if(file.find_last_of('.') == string::npos)
file += ".script";
FilesystemNode node(debugger.myOSystem.defaultSaveDir() + file);
ofstream out(node.getPath());
if(!out.is_open())
return "Unable to save script to " + node.getShortPath();
stringstream out;
Debugger::FunctionDefMap funcs = debugger.getFunctionDefMap();
for(const auto& f: funcs)
if (!debugger.isBuiltinFunction(f.first))
@ -678,6 +674,16 @@ string DebuggerParser::saveScriptFile(string file)
out << endl;
}
FilesystemNode node(debugger.myOSystem.defaultSaveDir().getPath() + file);
try
{
node.write(out);
}
catch(...)
{
return "Unable to save script to " + node.getShortPath();
}
return "saved " + node.getShortPath() + " OK";
}
@ -1140,7 +1146,7 @@ void DebuggerParser::executeDump()
file << ".dump";
FilesystemNode node(file.str());
// cout << "dump " << args[0] << "-" << args[1] << " to " << file.str() << endl;
ofstream ofs(node.getPath(), ofstream::out | ofstream::app);
std::ofstream ofs(node.getPath(), std::ofstream::out | std::ofstream::app);
if(!ofs.is_open())
{
outputCommandError("Unable to append dump to file " + node.getShortPath(), myCommand);
@ -1221,9 +1227,8 @@ void DebuggerParser::executeExec()
if(file.find_last_of('.') == string::npos)
file += ".script";
FilesystemNode node(file);
if (!node.exists()) {
node = FilesystemNode(debugger.myOSystem.defaultSaveDir() + file);
}
if (!node.exists())
node = FilesystemNode(debugger.myOSystem.defaultSaveDir().getPath() + file);
if (argCount == 2) {
execPrefix = argStrings[1];

View File

@ -22,7 +22,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AtariVox::AtariVox(Jack jack, const Event& event, const System& system,
const string& portname, const string& eepromfile,
const string& portname, const FilesystemNode& eepromfile,
const onMessageCallback& callback)
: SaveKey(jack, event, system, eepromfile, callback, Controller::Type::AtariVox)
{

View File

@ -20,6 +20,7 @@
class OSystem;
class SerialPort;
class FilesystemNode;
#include "Control.hxx"
#include "SaveKey.hxx"
@ -47,7 +48,7 @@ class AtariVox : public SaveKey
@param callback Called to pass messages back to the parent controller
*/
AtariVox(Jack jack, const Event& event, const System& system,
const string& portname, const string& eepromfile,
const string& portname, const FilesystemNode& eepromfile,
const onMessageCallback& callback);
virtual ~AtariVox();

View File

@ -192,7 +192,7 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
myConsoleInfo.BankSwitch = myCart->about();
// Some carts have an associated nvram file
myCart->setNVRamFile(myOSystem.nvramDir(), myConsoleInfo.CartName);
myCart->setNVRamFile(myOSystem.nvramDir().getPath(), myConsoleInfo.CartName);
// Let the other devices know about the new console
mySystem->consoleChanged(myConsoleTiming);
@ -824,7 +824,8 @@ unique_ptr<Controller> Console::getControllerPort(const Controller::Type type,
case Controller::Type::AtariVox:
{
const string& nvramfile = myOSystem.nvramDir() + "atarivox_eeprom.dat";
FilesystemNode nvramfile = myOSystem.nvramDir();
nvramfile /= "atarivox_eeprom.dat";
Controller::onMessageCallback callback = [&os = myOSystem](const string& msg) {
bool devSettings = os.settings().getBool("dev.settings");
if(os.settings().getBool(devSettings ? "dev.eepromaccess" : "plr.eepromaccess"))
@ -836,7 +837,8 @@ unique_ptr<Controller> Console::getControllerPort(const Controller::Type type,
}
case Controller::Type::SaveKey:
{
const string& nvramfile = myOSystem.nvramDir() + "savekey_eeprom.dat";
FilesystemNode nvramfile = myOSystem.nvramDir();
nvramfile /= "savekey_eeprom.dat";
Controller::onMessageCallback callback = [&os = myOSystem](const string& msg) {
bool devSettings = os.settings().getBool("dev.settings");
if(os.settings().getBool(devSettings ? "dev.eepromaccess" : "plr.eepromaccess"))

View File

@ -334,8 +334,9 @@ void EventHandler::handleSystemEvent(SystemEvent e, int, int)
#endif
#if 0
case SystemEvent::WINDOW_MINIMIZED:
if(myState == EventHandlerState::EMULATION) enterMenuMode(EventHandlerState::OPTIONSMENU);
break;
if(myState == EventHandlerState::EMULATION)
enterMenuMode(EventHandlerState::OPTIONSMENU);
break;
#endif
default: // handle other events as testing requires
// cerr << "handleSystemEvent: " << e << endl;
@ -343,7 +344,6 @@ void EventHandler::handleSystemEvent(SystemEvent e, int, int)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EventHandler::AdjustGroup EventHandler::getAdjustGroup()
{

View File

@ -25,15 +25,49 @@ FilesystemNode::FilesystemNode(const AbstractFSNodePtr& realNode)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesystemNode::FilesystemNode(const string& p)
FilesystemNode::FilesystemNode(const string& path)
{
setPath(path);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FilesystemNode::setPath(const string& path)
{
// Only create a new object when necessary
if (path == getPath())
return;
// Is this potentially a ZIP archive?
#if defined(ZIP_SUPPORT)
if (BSPF::containsIgnoreCase(p, ".zip"))
_realNode = FilesystemNodeFactory::create(p, FilesystemNodeFactory::Type::ZIP);
if (BSPF::containsIgnoreCase(path, ".zip"))
_realNode = FilesystemNodeFactory::create(path, FilesystemNodeFactory::Type::ZIP);
else
#endif
_realNode = FilesystemNodeFactory::create(p, FilesystemNodeFactory::Type::SYSTEM);
_realNode = FilesystemNodeFactory::create(path, FilesystemNodeFactory::Type::SYSTEM);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesystemNode& FilesystemNode::operator/=(const string& path)
{
// This part could probably be put in a virtual function, but it seems like
// a waste since almost every system uses the same separator, except Windows
#ifdef BSPF_WINDOWS
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
if (path != EmptyString)
{
string newPath = getPath();
if (newPath != EmptyString && newPath[newPath.length()-1] != PATH_SEPARATOR)
newPath += PATH_SEPARATOR;
newPath += path;
setPath(newPath);
}
return *this;
#undef PATH_SEPARATOR
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -234,7 +268,7 @@ size_t FilesystemNode::read(ByteBuffer& buffer) const
return sizeRead;
// Otherwise, the default behaviour is to read from a normal C++ ifstream
ifstream in(getPath(), std::ios::binary);
std::ifstream in(getPath(), std::ios::binary);
if (in)
{
in.seekg(0, std::ios::end);
@ -268,7 +302,7 @@ size_t FilesystemNode::read(stringstream& buffer) const
// Otherwise, the default behaviour is to read from a normal C++ ifstream
// and convert to a stringstream
ifstream in(getPath(), std::ios::binary);
std::ifstream in(getPath(), std::ios::binary);
if (in)
{
in.seekg(0, std::ios::end);
@ -296,7 +330,7 @@ size_t FilesystemNode::write(const ByteBuffer& buffer, size_t size) const
return sizeWritten;
// Otherwise, the default behaviour is to write to a normal C++ ofstream
ofstream out(getPath(), std::ios::binary);
std::ofstream out(getPath(), std::ios::binary);
if (out)
{
out.write(reinterpret_cast<const char*>(buffer.get()), size);
@ -321,7 +355,7 @@ size_t FilesystemNode::write(const stringstream& buffer) const
return sizeWritten;
// Otherwise, the default behaviour is to write to a normal C++ ofstream
ofstream out(getPath(), std::ios::binary);
std::ofstream out(getPath(), std::ios::binary);
if (out)
{
out << buffer.rdbuf();

View File

@ -84,13 +84,19 @@ class FilesystemNode
/**
* Compare the name of this node to the name of another, testing for
* equality,
* equality.
*/
inline bool operator==(const FilesystemNode& node) const
{
return BSPF::compareIgnoreCase(getName(), node.getName()) == 0;
}
/**
* Append the given path to the node, adding a directory separator
* when necessary. Modelled on the C++17 fs::path API.
*/
FilesystemNode& operator/=(const string& path);
/**
* By default, the output operator simply outputs the fully-qualified
* pathname of the node.
@ -269,6 +275,7 @@ class FilesystemNode
private:
AbstractFSNodePtr _realNode;
explicit FilesystemNode(const AbstractFSNodePtr& realNode);
void setPath(const string& path);
};

View File

@ -43,27 +43,23 @@
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MT24LC256::MT24LC256(const string& filename, const System& system,
MT24LC256::MT24LC256(const FilesystemNode& eepromfile, const System& system,
const Controller::onMessageCallback& callback)
: mySystem(system),
myCallback(callback),
myDataFile(filename)
myDataFile(eepromfile)
{
// Load the data from an external file (if it exists)
ifstream in(myDataFile, std::ios_base::binary);
if(in.is_open())
try
{
// Get length of file; it must be 32768
in.seekg(0, std::ios::end);
if(uInt32(in.tellg()) == FLASH_SIZE)
{
in.seekg(0, std::ios::beg);
in.read(reinterpret_cast<char*>(myData.data()), myData.size());
if(myDataFile.read(myData) == FLASH_SIZE)
myDataFileExists = true;
}
}
else
catch(...)
{
myDataFileExists = false;
}
// Then initialize the I2C state
jpee_init();
@ -77,9 +73,8 @@ MT24LC256::~MT24LC256()
// Save EEPROM data to external file only when necessary
if(!myDataFileExists || myDataChanged)
{
ofstream out(myDataFile, std::ios_base::binary);
if(out.is_open())
out.write(reinterpret_cast<char*>(myData.data()), myData.size());
try { myDataFile.write(myData, FLASH_SIZE); }
catch(...) { }
}
}
@ -139,7 +134,7 @@ void MT24LC256::eraseAll()
// Work around a bug in XCode 11.2 with -O0 and -O1
const uInt8 initialValue = INITIAL_VALUE;
myData.fill(initialValue);
std::fill_n(myData.get(), FLASH_SIZE, initialValue);
myDataChanged = true;
}
@ -153,7 +148,7 @@ void MT24LC256::eraseCurrent()
{
if(myPageHit[page])
{
std::fill_n(myData.begin() + page * PAGE_SIZE, PAGE_SIZE, initialValue);
std::fill_n(myData.get() + page * PAGE_SIZE, PAGE_SIZE, initialValue);
myDataChanged = true;
}
}
@ -182,7 +177,7 @@ void MT24LC256::jpee_init()
jpee_smallmode = 0;
jpee_logmode = -1;
if(!myDataFileExists)
myData.fill(initialValue);
std::fill_n(myData.get(), FLASH_SIZE, initialValue);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -21,6 +21,7 @@
class System;
#include "Control.hxx"
#include "FSNode.hxx"
#include "bspf.hxx"
/**
@ -36,11 +37,11 @@ class MT24LC256
/**
Create a new 24LC256 with its data stored in the given file
@param filename Data file containing the EEPROM data
@param system The system using the controller of this device
@param callback Called to pass messages back to the parent controller
@param eepromfile Data file containing the EEPROM data
@param system The system using the controller of this device
@param callback Called to pass messages back to the parent controller
*/
MT24LC256(const string& filename, const System& system,
MT24LC256(const FilesystemNode& eepromfile, const System& system,
const Controller::onMessageCallback& callback);
~MT24LC256();
@ -92,7 +93,7 @@ class MT24LC256
Controller::onMessageCallback myCallback;
// The EEPROM data
std::array<uInt8, FLASH_SIZE> myData;
ByteBuffer myData;
// Track which pages are used
std::array<bool, PAGE_NUM> myPageHit;
@ -110,7 +111,7 @@ class MT24LC256
uInt64 myCyclesWhenSDASet{0}, myCyclesWhenSCLSet{0};
// The file containing the EEPROM data
string myDataFile;
FilesystemNode myDataFile;
// Indicates if a valid EEPROM data file exists/was successfully loaded
bool myDataFileExists{false};

View File

@ -121,22 +121,22 @@ bool OSystem::create()
<< " Features: " << myFeatures << endl
<< " " << myBuildInfo << endl << endl
<< "Base directory: '"
<< FilesystemNode(myBaseDir).getShortPath() << "'" << endl
<< myBaseDir.getShortPath() << "'" << endl
<< "State directory: '"
<< FilesystemNode(myStateDir).getShortPath() << "'" << endl
<< myStateDir.getShortPath() << "'" << endl
<< "NVRam directory: '"
<< FilesystemNode(myNVRamDir).getShortPath() << "'" << endl;
<< myNVRamDir.getShortPath() << "'" << endl;
if(!myConfigFile.empty())
if(myConfigFile.getPath() != EmptyString)
buf << "Configuration file: '"
<< FilesystemNode(myConfigFile).getShortPath() << "'" << endl;
<< myConfigFile.getShortPath() << "'" << endl;
buf << "Game properties: '"
<< myPropertiesFile.getShortPath() << "'" << endl
<< "Cheat file: '"
<< FilesystemNode(myCheatFile).getShortPath() << "'" << endl
<< myCheatFile.getShortPath() << "'" << endl
<< "Palette file: '"
<< FilesystemNode(myPaletteFile).getShortPath() << "'" << endl;
<< myPaletteFile.getShortPath() << "'" << endl;
Logger::info(buf.str());
// NOTE: The framebuffer MUST be created before any other object!!!
@ -193,30 +193,27 @@ void OSystem::loadConfig(const Settings::Options& options)
{
// Get base directory and config file from derived class
// It will decide whether it can override its default location
getBaseDirAndConfig(myBaseDir, myConfigFile,
myDefaultSaveDir, myDefaultLoadDir,
ourOverrideBaseDirWithApp, ourOverrideBaseDir);
string baseDir, cfgFile, defSaveDir, defLoadDir;
getBaseDirAndConfig(baseDir, cfgFile, defSaveDir, defLoadDir,
ourOverrideBaseDirWithApp, ourOverrideBaseDir);
// Get fully-qualified pathnames, and make directories when needed
FilesystemNode node(myBaseDir);
if(!node.isDirectory())
node.makeDir();
myBaseDir = node.getPath();
if(!myConfigFile.empty())
myConfigFile = FilesystemNode(myConfigFile).getPath();
myBaseDir = FilesystemNode(baseDir);
if(!myBaseDir.isDirectory())
myBaseDir.makeDir();
if(!cfgFile.empty())
myConfigFile = FilesystemNode(cfgFile);
FilesystemNode save(myDefaultSaveDir);
if(!save.isDirectory())
save.makeDir();
myDefaultSaveDir = save.getShortPath();
myDefaultSaveDir = FilesystemNode(defSaveDir);
if(!myDefaultSaveDir.isDirectory())
myDefaultSaveDir.makeDir();
FilesystemNode load(myDefaultLoadDir);
if(!load.isDirectory())
load.makeDir();
myDefaultLoadDir = load.getShortPath();
myDefaultLoadDir = FilesystemNode(defLoadDir);
if(!myDefaultLoadDir.isDirectory())
myDefaultLoadDir.makeDir();
#ifdef SQLITE_SUPPORT
mySettingsDb = make_shared<SettingsDb>(myBaseDir, "settings");
mySettingsDb = make_shared<SettingsDb>(myBaseDir.getPath(), "settings");
if(!mySettingsDb->initialize())
mySettingsDb.reset();
#endif
@ -259,38 +256,48 @@ void OSystem::saveConfig()
void OSystem::setConfigPaths()
{
// Make sure all required directories actually exist
auto buildDirIfRequired = [](string& path, const string& pathToBuild)
auto buildDirIfRequired = [](FilesystemNode& path,
const FilesystemNode& initialPath,
const string& pathToAppend = EmptyString)
{
FilesystemNode node(pathToBuild);
if(!node.isDirectory())
node.makeDir();
path = node.getPath();
path = initialPath;
if(pathToAppend != EmptyString)
path /= pathToAppend;
if(!path.isDirectory())
path.makeDir();
};
buildDirIfRequired(myStateDir, myBaseDir + "state");
buildDirIfRequired(myNVRamDir, myBaseDir + "nvram");
buildDirIfRequired(myStateDir, myBaseDir, "state");
buildDirIfRequired(myNVRamDir, myBaseDir, "nvram");
#ifdef DEBUGGER_SUPPORT
buildDirIfRequired(myCfgDir, myBaseDir + "cfg");
buildDirIfRequired(myCfgDir, myBaseDir, "cfg");
#endif
#ifdef PNG_SUPPORT
mySnapshotSaveDir = mySettings->getString("snapsavedir");
if(mySnapshotSaveDir == "") mySnapshotSaveDir = defaultSaveDir();
buildDirIfRequired(mySnapshotSaveDir, mySnapshotSaveDir);
const string& ssSaveDir = mySettings->getString("snapsavedir");
if(ssSaveDir == EmptyString)
mySnapshotSaveDir = defaultSaveDir();
else
mySnapshotSaveDir = FilesystemNode(ssSaveDir);
if(!mySnapshotSaveDir.isDirectory())
mySnapshotSaveDir.makeDir();
mySnapshotLoadDir = mySettings->getString("snaploaddir");
if(mySnapshotLoadDir == "") mySnapshotLoadDir = defaultLoadDir();
buildDirIfRequired(mySnapshotLoadDir, mySnapshotLoadDir);
const string& ssLoadDir = mySettings->getString("snaploaddir");
if(ssLoadDir == EmptyString)
mySnapshotLoadDir = defaultLoadDir();
else
mySnapshotLoadDir = FilesystemNode(ssLoadDir);
if(!mySnapshotLoadDir.isDirectory())
mySnapshotLoadDir.makeDir();
#endif
myCheatFile = FilesystemNode(myBaseDir + "stella.cht").getPath();
myPaletteFile = FilesystemNode(myBaseDir + "stella.pal").getPath();
myPropertiesFile = FilesystemNode(myBaseDir + "stella.pro");
myCheatFile = myBaseDir; myCheatFile /= "stella.cht";
myPaletteFile = myBaseDir; myPaletteFile /= "stella.pal";
myPropertiesFile = myBaseDir; myPropertiesFile /= "stella.pro";
#if 0
// Debug code
auto dbgPath = [](const string& desc, const string& location)
auto dbgPath = [](const string& desc, const FilesystemNode& location)
{
cerr << desc << ": " << location << endl;
};
@ -310,21 +317,24 @@ void OSystem::setConfigPaths()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool OSystem::checkUserPalette(bool outputError) const
{
const string& palette = paletteFile();
ifstream in(palette, std::ios::binary);
if (!in)
return false;
// Make sure the contains enough data for the NTSC, PAL and SECAM palettes
// This means 128 colours each for NTSC and PAL, at 3 bytes per pixel
// and 8 colours for SECAM at 3 bytes per pixel
in.seekg(0, std::ios::end);
std::streampos length = in.tellg();
in.seekg(0, std::ios::beg);
if (length < 128 * 3 * 2 + 8 * 3)
try
{
ByteBuffer palette;
size_t size = paletteFile().read(palette);
// Make sure the contains enough data for the NTSC, PAL and SECAM palettes
// This means 128 colours each for NTSC and PAL, at 3 bytes per pixel
// and 8 colours for SECAM at 3 bytes per pixel
if(size != 128 * 3 * 2 + 8 * 3)
{
if(outputError)
cerr << "ERROR: invalid palette file " << paletteFile() << endl;
return false;
}
}
catch(...)
{
if (outputError)
cerr << "ERROR: invalid palette file " << palette << endl;
return false;
}
return true;
@ -457,8 +467,11 @@ string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum,
myConsole->cartridge().detectedType() + ", loading ROM" + id);
}
buf << "Game console created:" << endl
<< " ROM file: " << myRomFile.getShortPath() << endl << endl
<< getROMInfo(*myConsole);
<< " ROM file: " << myRomFile.getShortPath() << endl;
FilesystemNode propsFile(myRomFile.getPathWithExt(".pro"));
if(propsFile.exists())
buf << " PRO file: " << propsFile.getShortPath() << endl;
buf << endl << getROMInfo(*myConsole);
Logger::info(buf.str());
myFrameBuffer->setCursorState();
@ -821,7 +834,7 @@ shared_ptr<KeyValueRepository> OSystem::createSettingsRepository()
? shared_ptr<KeyValueRepository>(mySettingsDb, &mySettingsDb->settingsRepository())
: make_shared<KeyValueRepositoryNoop>();
#else
if (myConfigFile.empty())
if (myConfigFile.getPath() == EmptyString)
return make_shared<KeyValueRepositoryNoop>();
return make_shared<KeyValueRepositoryConfigfile>(myConfigFile);

View File

@ -246,53 +246,48 @@ class OSystem
void setConfigPaths();
/**
Return the default full/complete directory name for storing data.
Return the default full/complete path name for storing data.
*/
const string& baseDir() const { return myBaseDir; }
const FilesystemNode& baseDir() const { return myBaseDir; }
/**
Return the full/complete directory name for storing state files.
Return the full/complete path name for storing state files.
*/
const string& stateDir() const { return myStateDir; }
const FilesystemNode& stateDir() const { return myStateDir; }
/**
Return the full/complete directory name for storing nvram
Return the full/complete path name for storing nvram
(flash/EEPROM) files.
*/
const string& nvramDir() const { return myNVRamDir; }
const FilesystemNode& nvramDir() const { return myNVRamDir; }
#ifdef CHEATCODE_SUPPORT
/**
This method should be called to get the full path of the cheat file.
@return String representing the full path of the cheat filename.
Return the full/complete path name of the cheat file.
*/
const string& cheatFile() const { return myCheatFile; }
const FilesystemNode& cheatFile() const { return myCheatFile; }
#endif
#ifdef DEBUGGER_SUPPORT
/**
Return the full/complete directory name for storing Distella cfg files.
Return the full/complete path name for storing Distella cfg files.
*/
const string& cfgDir() const { return myCfgDir; }
const FilesystemNode& cfgDir() const { return myCfgDir; }
#endif
#ifdef PNG_SUPPORT
/**
Return the full/complete directory name for saving and loading
Return the full/complete path name for saving and loading
PNG snapshots.
*/
const string& snapshotSaveDir() const { return mySnapshotSaveDir; }
const string& snapshotLoadDir() const { return mySnapshotLoadDir; }
const FilesystemNode& snapshotSaveDir() const { return mySnapshotSaveDir; }
const FilesystemNode& snapshotLoadDir() const { return mySnapshotLoadDir; }
#endif
/**
This method should be called to get the full path of the
(optional) palette file.
@return String representing the full path of the properties filename.
Return the full/complete path name of the (optional) palette file.
*/
const string& paletteFile() const { return myPaletteFile; }
const FilesystemNode& paletteFile() const { return myPaletteFile; }
/**
Checks if a valid a user-defined palette file exists.
@ -300,10 +295,7 @@ class OSystem
bool checkUserPalette(bool outputError = false) const;
/**
This method should be called to get the full path of the currently
loaded ROM.
@return FSNode object representing the ROM file.
Return the full/complete path name of the currently loaded ROM.
*/
const FilesystemNode& romFile() const { return myRomFile; }
@ -311,8 +303,8 @@ class OSystem
The default locations for saving and loading various files that
don't already have a specific location.
*/
const string& defaultSaveDir() const { return myDefaultSaveDir; }
const string& defaultLoadDir() const { return myDefaultLoadDir; }
const FilesystemNode& defaultSaveDir() const { return myDefaultSaveDir; }
const FilesystemNode& defaultLoadDir() const { return myDefaultLoadDir; }
/**
Open the given ROM and return an array containing its contents.
@ -533,22 +525,10 @@ class OSystem
bool myQuitLoop{false};
private:
string myBaseDir;
string myStateDir;
string mySnapshotSaveDir;
string mySnapshotLoadDir;
string myNVRamDir;
string myCfgDir;
string myDefaultSaveDir;
string myDefaultLoadDir;
string myCheatFile;
string myConfigFile;
string myPaletteFile;
FilesystemNode myPropertiesFile;
FilesystemNode myRomFile;
string myRomMD5;
FilesystemNode myBaseDir, myStateDir, mySnapshotSaveDir, mySnapshotLoadDir,
myNVRamDir, myCfgDir, myDefaultSaveDir, myDefaultLoadDir;
FilesystemNode myCheatFile, myConfigFile, myPaletteFile, myPropertiesFile;
FilesystemNode myRomFile; string myRomMD5;
string myFeatures;
string myBuildInfo;

View File

@ -22,7 +22,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SaveKey::SaveKey(Jack jack, const Event& event, const System& system,
const string& eepromfile, const onMessageCallback& callback,
const FilesystemNode& eepromfile, const onMessageCallback& callback,
Type type)
: Controller(jack, event, system, type),
myEEPROM(make_unique<MT24LC256>(eepromfile, system, callback))
@ -33,7 +33,7 @@ SaveKey::SaveKey(Jack jack, const Event& event, const System& system,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SaveKey::SaveKey(Jack jack, const Event& event, const System& system,
const string& eepromfile, const onMessageCallback& callback)
const FilesystemNode& eepromfile, const onMessageCallback& callback)
: SaveKey(jack, event, system, eepromfile, callback, Controller::Type::SaveKey)
{
}

View File

@ -20,6 +20,7 @@
class MT24LC256;
class OSystem;
class FilesystemNode;
#include "Control.hxx"
@ -45,7 +46,7 @@ class SaveKey : public Controller
@param callback Called to pass messages back to the parent controller
*/
SaveKey(Jack jack, const Event& event, const System& system,
const string& eepromfile, const onMessageCallback& callback);
const FilesystemNode& eepromfile, const onMessageCallback& callback);
virtual ~SaveKey();
protected:
@ -54,7 +55,8 @@ class SaveKey : public Controller
that inherit from SaveKey (currently, AtariVox)
*/
SaveKey(Jack jack, const Event& event, const System& system,
const string& eepromfile, const onMessageCallback& callback, Type type);
const FilesystemNode& eepromfile,
const onMessageCallback& callback, Type type);
public:
using Controller::read;

View File

@ -809,17 +809,22 @@ void GameInfoDialog::eraseEEPROM()
void GameInfoDialog::saveCurrentPropertiesToDisk()
{
saveProperties();
stringstream out;
out << myGameProperties;
FilesystemNode propfile(instance().defaultSaveDir() + myGameFile.getNameWithExt(".pro"));
ofstream out(propfile.getPath());
if(out)
try
{
out << myGameProperties;
FilesystemNode propfile = instance().defaultSaveDir();
propfile /= myGameFile.getNameWithExt(".pro");
propfile.write(out);
instance().frameBuffer().showMessage("Properties saved to " +
propfile.getShortPath());
}
else
catch(...)
{
instance().frameBuffer().showMessage("Error saving properties");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -116,15 +116,18 @@ void LoggerDialog::saveConfig()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LoggerDialog::saveLogFile()
{
ostringstream path;
path << instance().defaultSaveDir() << "stella.log";
FilesystemNode node(path.str());
FilesystemNode node = instance().defaultSaveDir();
node /= "stella.log";
ofstream out(node.getPath());
if(out.is_open())
try
{
stringstream out;
out << Logger::instance().logMessages();
instance().frameBuffer().showMessage("Saving log file to " + path.str());
instance().frameBuffer().showMessage("Saving log file to " + node.getShortPath());
}
catch(...)
{
instance().frameBuffer().showMessage("Error savin log file to " + node.getShortPath());
}
}

View File

@ -104,7 +104,7 @@ void RomInfoWidget::parseProperties(const FilesystemNode& node)
#ifdef PNG_SUPPORT
// Get a valid filename representing a snapshot file for this rom
const string& filename = instance().snapshotLoadDir() +
const string& filename = instance().snapshotLoadDir().getPath() +
myProperties.get(PropType::Cart_Name) + ".png";
// Read the PNG file

View File

@ -131,7 +131,7 @@ void SnapshotDialog::saveConfig()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SnapshotDialog::setDefaults()
{
mySnapSavePath->setText(instance().defaultSaveDir());
mySnapSavePath->setText(instance().defaultSaveDir().getShortPath());
mySnapInterval->setValue(2);
mySnapName->setState(false);
mySnapSingle->setState(false);

View File

@ -507,7 +507,7 @@ void UIDialog::setDefaults()
myLauncherHeightSlider->setValue(h);
myLauncherFontPopup->setSelected("medium", "");
myRomViewerSize->setValue(35);
mySnapLoadPath->setText(instance().defaultLoadDir());
mySnapLoadPath->setText(instance().defaultLoadDir().getShortPath());
myLauncherExitWidget->setState(false);
break;
}