mirror of https://github.com/stella-emu/stella.git
289 lines
8.6 KiB
C++
289 lines
8.6 KiB
C++
//============================================================================
|
|
//
|
|
// SSSS tt lll lll
|
|
// SS SS tt ll ll
|
|
// SS tttttt eeee ll ll aaaa
|
|
// SSSS tt ee ee ll ll aa
|
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
// SS SS tt ee ll ll aa aa
|
|
// SSSS ttt eeeee llll llll aaaaa
|
|
//
|
|
// Copyright (c) 1995-2025 by Bradford W. Mott, Stephen Anthony
|
|
// and the Stella Team
|
|
//
|
|
// See the file "License.txt" for information on usage and redistribution of
|
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
//============================================================================
|
|
|
|
#if defined(ZIP_SUPPORT)
|
|
|
|
#include <set>
|
|
|
|
#include "bspf.hxx"
|
|
#include "Bankswitch.hxx"
|
|
#include "FSNodeFactory.hxx"
|
|
#include "FSNodeZIP.hxx"
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FSNodeZIP::FSNodeZIP(string_view p)
|
|
{
|
|
// Extract ZIP file and virtual file (if specified)
|
|
const size_t pos = BSPF::findIgnoreCase(p, ".zip");
|
|
if(pos == string::npos)
|
|
return;
|
|
|
|
_zipFile = p.substr(0, pos+4);
|
|
|
|
// Expand '~' to the users 'home' directory
|
|
if(_zipFile[0] == '~')
|
|
{
|
|
#if defined(BSPF_UNIX) || defined(BSPF_MACOS)
|
|
const char* const home = std::getenv("HOME"); // NOLINT (not thread safe)
|
|
if(home != nullptr)
|
|
_zipFile.replace(0, 1, home);
|
|
#elif defined(BSPF_WINDOWS)
|
|
_zipFile.replace(0, 1, HomeFinder::getHomePath());
|
|
#endif
|
|
}
|
|
|
|
// cerr << " => p: " << p << '\n';
|
|
|
|
// Open file at least once to initialize the virtual file count
|
|
try
|
|
{
|
|
myZipHandler->open(_zipFile);
|
|
}
|
|
catch(const runtime_error&)
|
|
{
|
|
// TODO: Actually present the error passed in back to the user
|
|
// For now, we just indicate that no ROMs were found
|
|
_error = zip_error::NO_ROMS;
|
|
}
|
|
_numFiles = myZipHandler->romFiles();
|
|
if(_numFiles == 0)
|
|
{
|
|
_error = zip_error::NO_ROMS;
|
|
}
|
|
|
|
// We always need a virtual file/path
|
|
// Either one is given, or we use the first one
|
|
if(pos+5 < p.length()) // if something comes after '.zip'
|
|
{
|
|
_virtualPath = p.substr(pos+5);
|
|
_isFile = Bankswitch::isValidRomName(_virtualPath);
|
|
_isDirectory = !_isFile;
|
|
}
|
|
else if(_numFiles == 1)
|
|
{
|
|
bool found = false;
|
|
while(myZipHandler->hasNext() && !found)
|
|
{
|
|
const auto& [name, size] = myZipHandler->next();
|
|
if(Bankswitch::isValidRomName(name))
|
|
{
|
|
_virtualPath = name;
|
|
_size = size;
|
|
_isFile = true;
|
|
|
|
found = true;
|
|
}
|
|
}
|
|
if(!found)
|
|
return;
|
|
}
|
|
else if(_numFiles > 1)
|
|
_isDirectory = true;
|
|
|
|
// Create a concrete FSNode to use
|
|
// This *must not* be a ZIP file; it must be a real FSNode object that
|
|
// has direct access to the actual filesystem (aka, a 'System' node)
|
|
// Behind the scenes, this node is actually a platform-specific object
|
|
// for whatever system we are running on
|
|
_realNode = FSNodeFactory::create(_zipFile,
|
|
FSNodeFactory::Type::SYSTEM);
|
|
|
|
setFlags(_zipFile, _virtualPath, _realNode);
|
|
// cerr << "==============================================================\n";
|
|
// cerr << _name << ", file: " << _isFile << ", dir: " << _isDirectory << "\n\n";
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FSNodeZIP::FSNodeZIP(const string& zipfile, const string& virtualpath,
|
|
const AbstractFSNodePtr& realnode, size_t size, bool isdir)
|
|
: _size{size},
|
|
_isDirectory{isdir},
|
|
_isFile{!isdir}
|
|
{
|
|
// cerr << "=> c'tor 2: " << zipfile << ", " << virtualpath << ", " << isdir << '\n';
|
|
setFlags(zipfile, virtualpath, realnode);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FSNodeZIP::setFlags(const string& zipfile, const string& virtualpath,
|
|
const AbstractFSNodePtr& realnode)
|
|
{
|
|
_zipFile = zipfile;
|
|
_virtualPath = virtualpath;
|
|
_realNode = realnode;
|
|
|
|
_path = _realNode->getPath();
|
|
_shortPath = _realNode->getShortPath();
|
|
|
|
// Is a file component present?
|
|
if(!_virtualPath.empty())
|
|
{
|
|
_path += ("/" + _virtualPath);
|
|
_shortPath += ("/" + _virtualPath);
|
|
}
|
|
_name = lastPathComponent(_path);
|
|
|
|
if(!_realNode->isFile())
|
|
_error = zip_error::NOT_A_FILE;
|
|
if(!_realNode->isReadable())
|
|
_error = zip_error::NOT_READABLE;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FSNodeZIP::exists() const
|
|
{
|
|
if(_realNode && _realNode->exists())
|
|
{
|
|
// We need to inspect the actual path, not just the ZIP file itself
|
|
try
|
|
{
|
|
myZipHandler->open(_zipFile);
|
|
while(myZipHandler->hasNext())
|
|
{
|
|
const auto& [name, size] = myZipHandler->next();
|
|
if(BSPF::startsWithIgnoreCase(name, _virtualPath))
|
|
return true;
|
|
}
|
|
}
|
|
catch(const runtime_error&)
|
|
{
|
|
// TODO: Actually present the error passed in back to the user
|
|
cerr << "ERROR: FSNodeZIP::exists()\n";
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FSNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const
|
|
{
|
|
// Files within ZIP archives don't contain children
|
|
if(!isDirectory() || _error != zip_error::NONE)
|
|
return false;
|
|
|
|
std::set<string> dirs;
|
|
myZipHandler->open(_zipFile);
|
|
while(myZipHandler->hasNext())
|
|
{
|
|
// Only consider entries that start with '_virtualPath'
|
|
// Ignore empty filenames and '__MACOSX' virtual directories
|
|
const auto& [name, size] = myZipHandler->next();
|
|
if(BSPF::startsWithIgnoreCase(name, "__MACOSX") || name == EmptyString)
|
|
continue;
|
|
if(BSPF::startsWithIgnoreCase(name, _virtualPath))
|
|
{
|
|
// First strip off the leading directory
|
|
const string& curr = name.substr(
|
|
_virtualPath.empty() ? 0 : _virtualPath.size()+1);
|
|
|
|
// Only add sub-directory entries once
|
|
const auto pos = curr.find_first_of("/\\");
|
|
if(pos != string::npos)
|
|
dirs.emplace(curr.substr(0, pos));
|
|
else
|
|
myList.emplace_back(new FSNodeZIP(_zipFile, name, _realNode, size, false));
|
|
}
|
|
}
|
|
for(const auto& dir: dirs)
|
|
{
|
|
// Prepend previous path
|
|
const string& vpath = !_virtualPath.empty() ? _virtualPath + "/" + dir : dir;
|
|
myList.emplace_back(new FSNodeZIP(_zipFile, vpath, _realNode, 0, true));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
size_t FSNodeZIP::read(ByteBuffer& buffer, size_t) const
|
|
{
|
|
switch(_error)
|
|
{
|
|
using enum zip_error;
|
|
case NONE: break;
|
|
case NOT_A_FILE: throw runtime_error("ZIP file contains errors/not found");
|
|
case NOT_READABLE: throw runtime_error("ZIP file not readable");
|
|
case NO_ROMS: throw runtime_error("ZIP file doesn't contain any ROMs");
|
|
default: throw runtime_error("FSNodeZIP::read default case hit");
|
|
}
|
|
|
|
myZipHandler->open(_zipFile);
|
|
|
|
bool found = false;
|
|
while(myZipHandler->hasNext() && !found)
|
|
{
|
|
const auto& [name, size] = myZipHandler->next();
|
|
found = name == _virtualPath;
|
|
}
|
|
|
|
return found ? myZipHandler->decompress(buffer) : 0;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
size_t FSNodeZIP::read(stringstream& buffer) 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 read_buf;
|
|
const size_t size = read(read_buf, 0);
|
|
if(size > 0)
|
|
buffer.write(reinterpret_cast<char*>(read_buf.get()), size);
|
|
|
|
return size;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
size_t FSNodeZIP::write(const ByteBuffer& buffer, size_t) const
|
|
{
|
|
// TODO: Not yet implemented
|
|
throw runtime_error("ZIP file not writable");
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
size_t FSNodeZIP::write(const stringstream& buffer) const
|
|
{
|
|
// TODO: Not yet implemented
|
|
throw runtime_error("ZIP file not writable");
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
AbstractFSNodePtr FSNodeZIP::getParent() const
|
|
{
|
|
if(_virtualPath.empty())
|
|
return _realNode ? _realNode->getParent() : nullptr;
|
|
|
|
// TODO: For some reason, getting the stem for normal paths and zip paths
|
|
// behaves differently. For now, we'll use the old method here.
|
|
auto STEM_FOR_ZIP = [](string_view s) {
|
|
const char* const start = s.data();
|
|
const char* cur = start + s.size() - 2;
|
|
|
|
while (cur >= start && !(*cur == '/' || *cur == '\\'))
|
|
--cur;
|
|
|
|
return s.substr(0, (cur + 1) - start - 1);
|
|
};
|
|
|
|
return make_unique<FSNodeZIP>(STEM_FOR_ZIP(_path));
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
unique_ptr<ZipHandler> FSNodeZIP::myZipHandler = make_unique<ZipHandler>();
|
|
|
|
#endif // ZIP_SUPPORT
|