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 | #ifndef ZIP_HANDLER_HXX |
21 | #define ZIP_HANDLER_HXX |
22 | |
23 | #include "bspf.hxx" |
24 | |
25 | /** |
26 | This class implements a thin wrapper around the zip file management code |
27 | from the MAME project. |
28 | |
29 | @author Original code by Aaron Giles, ZipHandler wrapper class and heavy |
30 | modifications/refactoring by Stephen Anthony. |
31 | */ |
32 | class ZipHandler |
33 | { |
34 | public: |
35 | ZipHandler(); |
36 | |
37 | // Open ZIP file for processing |
38 | // An exception will be thrown on any errors |
39 | void open(const string& filename); |
40 | |
41 | // The following form an iterator for processing the filenames in the ZIP file |
42 | void reset(); // Reset iterator to first file |
43 | bool hasNext() const; // Answer whether there are more files present |
44 | const string& next(); // Get next file |
45 | |
46 | // Decompress the currently selected file and return its length |
47 | // An exception will be thrown on any errors |
48 | uInt64 decompress(ByteBuffer& image); |
49 | |
50 | // Answer the number of ROM files (with a valid extension) found |
51 | uInt16 romFiles() const { return myZip ? myZip->myRomfiles : 0; } |
52 | |
53 | private: |
54 | // Error types |
55 | enum class ZipError |
56 | { |
57 | NONE = 0, |
58 | OUT_OF_MEMORY, |
59 | FILE_ERROR, |
60 | BAD_SIGNATURE, |
61 | DECOMPRESS_ERROR, |
62 | FILE_TRUNCATED, |
63 | FILE_CORRUPT, |
64 | UNSUPPORTED, |
65 | LZMA_UNSUPPORTED, |
66 | BUFFER_TOO_SMALL |
67 | }; |
68 | |
69 | // Contains extracted file header information |
70 | struct ZipHeader |
71 | { |
72 | uInt16 versionCreated; // version made by |
73 | uInt16 versionNeeded; // version needed to extract |
74 | uInt16 bitFlag; // general purpose bit flag |
75 | uInt16 compression; // compression method |
76 | uInt16 fileTime; // last mod file time |
77 | uInt16 fileDate; // last mod file date |
78 | uInt32 crc; // crc-32 |
79 | uInt64 compressedLength; // compressed size |
80 | uInt64 uncompressedLength; // uncompressed size |
81 | uInt32 startDiskNumber; // disk number start |
82 | uInt64 localHeaderOffset; // relative offset of local header |
83 | string filename; // filename |
84 | |
85 | /** Constructor */ |
86 | ZipHeader(); |
87 | }; |
88 | |
89 | // Contains extracted end of central directory information |
90 | struct ZipEcd |
91 | { |
92 | uInt32 diskNumber; // number of this disk |
93 | uInt32 cdStartDiskNumber; // number of the disk with the start of the central directory |
94 | uInt64 cdDiskEntries; // total number of entries in the central directory on this disk |
95 | uInt64 cdTotalEntries; // total number of entries in the central directory |
96 | uInt64 cdSize; // size of the central directory |
97 | uInt64 cdStartDiskOffset; // offset of start of central directory with respect to the starting disk number |
98 | |
99 | /** Constructor */ |
100 | ZipEcd(); |
101 | }; |
102 | |
103 | // Describes an open ZIP file |
104 | struct ZipFile |
105 | { |
106 | string myFilename; // copy of ZIP filename (for caching) |
107 | fstream myStream; // C++ fstream file handle |
108 | uInt64 myLength; // length of zip file |
109 | uInt16 myRomfiles; // number of ROM files in central directory |
110 | |
111 | ZipEcd myEcd; // end of central directory |
112 | |
113 | ByteBuffer myCd; // central directory raw data |
114 | uInt64 myCdPos; // position in central directory |
115 | ZipHeader myHeader; // current file header |
116 | |
117 | ByteBuffer myBuffer; // buffer for decompression |
118 | |
119 | /** Constructor */ |
120 | explicit ZipFile(const string& filename); |
121 | |
122 | /** Open the file and set up the internal stream buffer*/ |
123 | bool open(); |
124 | |
125 | /** Read the ZIP contents from the internal stream buffer */ |
126 | void initialize(); |
127 | |
128 | /** Close previously opened internal stream buffer */ |
129 | void close(); |
130 | |
131 | /** Read the ECD data */ |
132 | void readEcd(); |
133 | |
134 | /** Read data from stream */ |
135 | bool readStream(ByteBuffer& out, uInt64 offset, uInt64 length, uInt64& actual); |
136 | |
137 | /** Return the next entry in the ZIP file */ |
138 | const ZipHeader* nextFile(); |
139 | |
140 | /** Decompress the most recently found file in the ZIP into target buffer */ |
141 | void decompress(ByteBuffer& out, uInt64 length); |
142 | |
143 | /** Return the offset of the compressed data */ |
144 | uInt64 getCompressedDataOffset(); |
145 | |
146 | /** Decompress type 0 data (which is uncompressed) */ |
147 | void decompressDataType0(uInt64 offset, ByteBuffer& out, uInt64 length); |
148 | |
149 | /** Decompress type 8 data (which is deflated) */ |
150 | void decompressDataType8(uInt64 offset, ByteBuffer& out, uInt64 length); |
151 | }; |
152 | using ZipFilePtr = unique_ptr<ZipFile>; |
153 | |
154 | /** Classes to parse the ZIP metadata in an abstracted way */ |
155 | class ReaderBase |
156 | { |
157 | protected: |
158 | explicit ReaderBase(const uInt8* const b) : myBuf(b) { } |
159 | |
160 | uInt8 read_byte(size_t offs) const |
161 | { |
162 | return myBuf[offs]; |
163 | } |
164 | uInt16 read_word(size_t offs) const |
165 | { |
166 | return (uInt16(myBuf[offs + 1]) << 8) | |
167 | (uInt16(myBuf[offs + 0]) << 0); |
168 | } |
169 | uInt32 read_dword(std::size_t offs) const |
170 | { |
171 | return (uInt32(myBuf[offs + 3]) << 24) | |
172 | (uInt32(myBuf[offs + 2]) << 16) | |
173 | (uInt32(myBuf[offs + 1]) << 8) | |
174 | (uInt32(myBuf[offs + 0]) << 0); |
175 | } |
176 | uInt64 read_qword(size_t offs) const |
177 | { |
178 | return (uInt64(myBuf[offs + 7]) << 56) | |
179 | (uInt64(myBuf[offs + 6]) << 48) | |
180 | (uInt64(myBuf[offs + 5]) << 40) | |
181 | (uInt64(myBuf[offs + 4]) << 32) | |
182 | (uInt64(myBuf[offs + 3]) << 24) | |
183 | (uInt64(myBuf[offs + 2]) << 16) | |
184 | (uInt64(myBuf[offs + 1]) << 8) | |
185 | (uInt64(myBuf[offs + 0]) << 0); |
186 | } |
187 | string read_string(size_t offs, size_t len = string::npos) const |
188 | { |
189 | return string(reinterpret_cast<char const *>(myBuf + offs), len); |
190 | } |
191 | |
192 | private: |
193 | const uInt8* const myBuf; |
194 | }; |
195 | |
196 | class LocalFileHeaderReader : public ReaderBase |
197 | { |
198 | public: |
199 | explicit LocalFileHeaderReader(const uInt8* const b) : ReaderBase(b) { } |
200 | |
201 | uInt32 signature() const { return read_dword(0x00); } |
202 | uInt8 versionNeeded() const { return read_byte(0x04); } |
203 | uInt8 osNeeded() const { return read_byte(0x05); } |
204 | uInt16 generalFlag() const { return read_word(0x06); } |
205 | uInt16 compressionMethod() const { return read_word(0x08); } |
206 | uInt16 modifiedTime() const { return read_word(0x0a); } |
207 | uInt16 modifiedDate() const { return read_word(0x0c); } |
208 | uInt32 crc32() const { return read_dword(0x0e); } |
209 | uInt32 compressedSize() const { return read_dword(0x12); } |
210 | uInt32 uncompressedSize() const { return read_dword(0x16); } |
211 | uInt16 filenameLength() const { return read_word(0x1a); } |
212 | uInt16 extraFieldLength() const { return read_word(0x1c); } |
213 | string filename() const { return read_string(0x1e, filenameLength()); } |
214 | |
215 | bool signatureCorrect() const { return signature() == 0x04034b50; } |
216 | |
217 | size_t totalLength() const { return minimumLength() + filenameLength() + extraFieldLength(); } |
218 | static size_t minimumLength() { return 0x1e; } |
219 | }; |
220 | |
221 | class CentralDirEntryReader : public ReaderBase |
222 | { |
223 | public: |
224 | explicit CentralDirEntryReader(const uInt8* const b) : ReaderBase(b) { } |
225 | |
226 | uInt32 signature() const { return read_dword(0x00); } |
227 | uInt8 versionCreated() const { return read_byte(0x04); } |
228 | uInt8 osCreated() const { return read_byte(0x05); } |
229 | uInt8 versionNeeded() const { return read_byte(0x06); } |
230 | uInt8 osNeeded() const { return read_byte(0x07); } |
231 | uInt16 generalFlag() const { return read_word(0x08); } |
232 | uInt16 compressionMethod() const { return read_word(0x0a); } |
233 | uInt16 modifiedTime() const { return read_word(0x0c); } |
234 | uInt16 modifiedDate() const { return read_word(0x0e); } |
235 | uInt32 crc32() const { return read_dword(0x10); } |
236 | uInt32 compressedSize() const { return read_dword(0x14); } |
237 | uInt32 uncompressedSize() const { return read_dword(0x18); } |
238 | uInt16 filenameLength() const { return read_word(0x1c); } |
239 | uInt16 extraFieldLength() const { return read_word(0x1e); } |
240 | uInt16 fileCommentLength() const { return read_word(0x20); } |
241 | uInt16 startDisk() const { return read_word(0x22); } |
242 | uInt16 intFileAttr() const { return read_word(0x24); } |
243 | uInt32 extFileAttr() const { return read_dword(0x26); } |
244 | uInt32 headerOffset() const { return read_dword(0x2a); } |
245 | string filename() const { return read_string(0x2e, filenameLength()); } |
246 | string fileComment() const { return read_string(0x2e + filenameLength() + extraFieldLength(), fileCommentLength()); } |
247 | |
248 | bool signatureCorrect() const { return signature() == 0x02014b50; } |
249 | |
250 | size_t totalLength() const { return minimumLength() + filenameLength() + extraFieldLength() + fileCommentLength(); } |
251 | static size_t minimumLength() { return 0x2e; } |
252 | }; |
253 | |
254 | class EcdReader : public ReaderBase |
255 | { |
256 | public: |
257 | explicit EcdReader(const uInt8* const b) : ReaderBase(b) { } |
258 | |
259 | uInt32 signature() const { return read_dword(0x00); } |
260 | uInt16 thisDiskNo() const { return read_word(0x04); } |
261 | uInt16 dirStartDisk() const { return read_word(0x06); } |
262 | uInt16 dirDiskEntries() const { return read_word(0x08); } |
263 | uInt16 dirTotalEntries() const { return read_word(0x0a); } |
264 | uInt32 dirSize() const { return read_dword(0x0c); } |
265 | uInt32 dirOffset() const { return read_dword(0x10); } |
266 | uInt16 commentLength() const { return read_word(0x14); } |
267 | string comment() const { return read_string(0x16, commentLength()); } |
268 | |
269 | bool signatureCorrect() const { return signature() == 0x06054b50; } |
270 | |
271 | size_t totalLength() const { return minimumLength() + commentLength(); } |
272 | static size_t minimumLength() { return 0x16; } |
273 | }; |
274 | |
275 | class GeneralFlagReader |
276 | { |
277 | public: |
278 | explicit GeneralFlagReader(uInt16 val) : myValue(val) { } |
279 | |
280 | bool encrypted() const { return bool(myValue & 0x0001); } |
281 | bool implode8kDict() const { return bool(myValue & 0x0002); } |
282 | bool implode3Trees() const { return bool(myValue & 0x0004); } |
283 | uInt32 deflateOption() const { return uInt32((myValue >> 1) & 0x0003); } |
284 | bool lzmaEosMark() const { return bool(myValue & 0x0002); } |
285 | bool useDescriptor() const { return bool(myValue & 0x0008); } |
286 | bool patchData() const { return bool(myValue & 0x0020); } |
287 | bool strongEncryption() const { return bool(myValue & 0x0040); } |
288 | bool utf8Encoding() const { return bool(myValue & 0x0800); } |
289 | bool directoryEncryption() const { return bool(myValue & 0x2000); } |
290 | |
291 | private: |
292 | uInt16 myValue; |
293 | }; |
294 | |
295 | private: |
296 | /** Get message for given ZipError enumeration */ |
297 | string errorMessage(ZipError err) const; |
298 | |
299 | /** Search cache for given ZIP file */ |
300 | ZipFilePtr findCached(const string& filename); |
301 | |
302 | /** Close a ZIP file and add it to the cache */ |
303 | void addToCache(); |
304 | |
305 | private: |
306 | static constexpr uInt32 DECOMPRESS_BUFSIZE = 16_KB; |
307 | static constexpr uInt32 CACHE_SIZE = 8; // number of open files to cache |
308 | |
309 | ZipFilePtr myZip; |
310 | std::array<ZipFilePtr, CACHE_SIZE> myZipCache; |
311 | |
312 | private: |
313 | // Following constructors and assignment operators not supported |
314 | ZipHandler(const ZipHandler&) = delete; |
315 | ZipHandler(ZipHandler&&) = delete; |
316 | ZipHandler& operator=(const ZipHandler&) = delete; |
317 | ZipHandler& operator=(ZipHandler&&) = delete; |
318 | }; |
319 | |
320 | #endif /* ZIP_HANDLER_HXX */ |
321 | |
322 | #endif /* ZIP_SUPPORT */ |
323 | |