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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
29 | FilesystemNodeZIP::FilesystemNodeZIP() |
30 | : _error(zip_error::NOT_A_FILE), |
31 | _numFiles(0), |
32 | _isDirectory(false), |
33 | _isFile(false) |
34 | { |
35 | } |
36 | |
37 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
38 | FilesystemNodeZIP::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
107 | FilesystemNodeZIP::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
119 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
145 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
183 | uInt32 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
203 | AbstractFSNodePtr 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
215 | unique_ptr<ZipHandler> FilesystemNodeZIP::myZipHandler = make_unique<ZipHandler>(); |
216 | |
217 | #endif // ZIP_SUPPORT |
218 | |