1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#if defined(ZIP_SUPPORT)
19
20#include <set>
21
22#include "bspf.hxx"
23#include "Bankswitch.hxx"
24#include "OSystem.hxx"
25#include "FSNodeFactory.hxx"
26#include "FSNodeZIP.hxx"
27
28// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
29FilesystemNodeZIP::FilesystemNodeZIP()
30 : _error(zip_error::NOT_A_FILE),
31 _numFiles(0),
32 _isDirectory(false),
33 _isFile(false)
34{
35}
36
37// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
38FilesystemNodeZIP::FilesystemNodeZIP(const string& p)
39 : _error(zip_error::NONE),
40 _numFiles(0),
41 _isDirectory(false),
42 _isFile(false)
43{
44 // Extract ZIP file and virtual file (if specified)
45 size_t pos = BSPF::findIgnoreCase(p, ".zip");
46 if(pos == string::npos)
47 return;
48
49 _zipFile = p.substr(0, pos+4);
50
51 // Open file at least once to initialize the virtual file count
52 try
53 {
54 myZipHandler->open(_zipFile);
55 }
56 catch(const runtime_error&)
57 {
58 // TODO: Actually present the error passed in back to the user
59 // For now, we just indicate that no ROMs were found
60 _error = zip_error::NO_ROMS;
61 }
62 _numFiles = myZipHandler->romFiles();
63 if(_numFiles == 0)
64 {
65 _error = zip_error::NO_ROMS;
66 }
67
68 // We always need a virtual file/path
69 // Either one is given, or we use the first one
70 if(pos+5 < p.length())
71 {
72 _virtualPath = p.substr(pos+5);
73 _isFile = Bankswitch::isValidRomName(_virtualPath);
74 _isDirectory = !_isFile;
75 }
76 else if(_numFiles == 1)
77 {
78 bool found = false;
79 while(myZipHandler->hasNext() && !found)
80 {
81 const string& file = myZipHandler->next();
82 if(Bankswitch::isValidRomName(file))
83 {
84 _virtualPath = file;
85 _isFile = true;
86
87 found = true;
88 }
89 }
90 if(!found)
91 return;
92 }
93 else if(_numFiles > 1)
94 _isDirectory = true;
95
96 // Create a concrete FSNode to use
97 // This *must not* be a ZIP file; it must be a real FSNode object that
98 // has direct access to the actual filesystem (aka, a 'System' node)
99 // Behind the scenes, this node is actually a platform-specific object
100 // for whatever system we are running on
101 _realNode = FilesystemNodeFactory::create(_zipFile, FilesystemNodeFactory::Type::SYSTEM);
102
103 setFlags(_zipFile, _virtualPath, _realNode);
104}
105
106// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
107FilesystemNodeZIP::FilesystemNodeZIP(
108 const string& zipfile, const string& virtualpath,
109 AbstractFSNodePtr realnode, bool isdir)
110 : _error(zip_error::NONE),
111 _numFiles(0),
112 _isDirectory(isdir),
113 _isFile(!isdir)
114{
115 setFlags(zipfile, virtualpath, realnode);
116}
117
118// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119void FilesystemNodeZIP::setFlags(const string& zipfile,
120 const string& virtualpath,
121 AbstractFSNodePtr realnode)
122{
123 _zipFile = zipfile;
124 _virtualPath = virtualpath;
125 _realNode = realnode;
126
127 _path = _realNode->getPath();
128 _shortPath = _realNode->getShortPath();
129
130 // Is a file component present?
131 if(_virtualPath.size() != 0)
132 {
133 _path += ("/" + _virtualPath);
134 _shortPath += ("/" + _virtualPath);
135 }
136 _name = lastPathComponent(_path);
137
138 if(!_realNode->isFile())
139 _error = zip_error::NOT_A_FILE;
140 if(!_realNode->isReadable())
141 _error = zip_error::NOT_READABLE;
142}
143
144// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
145bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const
146{
147 // Files within ZIP archives don't contain children
148 if(!isDirectory() || _error != zip_error::NONE)
149 return false;
150
151 std::set<string> dirs;
152 myZipHandler->open(_zipFile);
153 while(myZipHandler->hasNext())
154 {
155 // Only consider entries that start with '_virtualPath'
156 // Ignore empty filenames and '__MACOSX' virtual directories
157 const string& next = myZipHandler->next();
158 if(BSPF::startsWithIgnoreCase(next, "__MACOSX") || next == EmptyString)
159 continue;
160 if(BSPF::startsWithIgnoreCase(next, _virtualPath))
161 {
162 // First strip off the leading directory
163 const string& curr = next.substr(_virtualPath == "" ? 0 : _virtualPath.size()+1);
164 // Only add sub-directory entries once
165 auto pos = curr.find_first_of("/\\");
166 if(pos != string::npos)
167 dirs.emplace(curr.substr(0, pos));
168 else
169 myList.emplace_back(new FilesystemNodeZIP(_zipFile, next, _realNode, false));
170 }
171 }
172 for(const auto& dir: dirs)
173 {
174 // Prepend previous path
175 const string& vpath = _virtualPath != "" ? _virtualPath + "/" + dir : dir;
176 myList.emplace_back(new FilesystemNodeZIP(_zipFile, vpath, _realNode, true));
177 }
178
179 return true;
180}
181
182// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
183uInt32 FilesystemNodeZIP::read(ByteBuffer& image) const
184{
185 switch(_error)
186 {
187 case zip_error::NONE: break;
188 case zip_error::NOT_A_FILE: throw runtime_error("ZIP file contains errors/not found");
189 case zip_error::NOT_READABLE: throw runtime_error("ZIP file not readable");
190 case zip_error::NO_ROMS: throw runtime_error("ZIP file doesn't contain any ROMs");
191 }
192
193 myZipHandler->open(_zipFile);
194
195 bool found = false;
196 while(myZipHandler->hasNext() && !found)
197 found = myZipHandler->next() == _virtualPath;
198
199 return found ? uInt32(myZipHandler->decompress(image)) : 0; // TODO: 64bit
200}
201
202// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
203AbstractFSNodePtr FilesystemNodeZIP::getParent() const
204{
205 if(_virtualPath == "")
206 return _realNode ? _realNode->getParent() : nullptr;
207
208 const char* start = _path.c_str();
209 const char* end = lastPathComponent(_path);
210
211 return make_shared<FilesystemNodeZIP>(string(start, end - start - 1));
212}
213
214// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
215unique_ptr<ZipHandler> FilesystemNodeZIP::myZipHandler = make_unique<ZipHandler>();
216
217#endif // ZIP_SUPPORT
218