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*/
32class 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