Don't call subframe poll on first poll (only if needed)
[lsnes.git] / src / library / zip.cpp
blobc06323416d5ad51ff34a3f755f3e092fabc52965
1 #include "lsnes.hpp"
3 #include "library/zip.hpp"
5 #include <cstdint>
6 #include <cstring>
7 #include <iostream>
8 #include <stdexcept>
9 #include <boost/iostreams/categories.hpp>
10 #include <boost/iostreams/copy.hpp>
11 #include <boost/iostreams/stream.hpp>
12 #include <boost/iostreams/stream_buffer.hpp>
13 #include <boost/iostreams/filter/symmetric.hpp>
14 #include <boost/iostreams/filter/zlib.hpp>
15 #include <boost/iostreams/filtering_stream.hpp>
16 #include <boost/iostreams/device/back_inserter.hpp>
18 namespace
20 uint32_t read32(const unsigned char* buf, unsigned offset = 0, unsigned modulo = 4) throw()
22 return (uint32_t)buf[offset % modulo] |
23 ((uint32_t)buf[(offset + 1) % modulo] << 8) |
24 ((uint32_t)buf[(offset + 2) % modulo] << 16) |
25 ((uint32_t)buf[(offset + 3) % modulo] << 24);
28 uint16_t read16(const unsigned char* buf) throw()
30 return (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
33 void write16(unsigned char* buf, uint16_t value) throw()
35 buf[0] = (value) & 0xFF;
36 buf[1] = (value >> 8) & 0xFF;
39 void write32(unsigned char* buf, uint32_t value) throw()
41 buf[0] = (value) & 0xFF;
42 buf[1] = (value >> 8) & 0xFF;
43 buf[2] = (value >> 16) & 0xFF;
44 buf[3] = (value >> 24) & 0xFF;
47 class file_input
49 public:
50 typedef char char_type;
51 typedef boost::iostreams::source_tag category;
52 file_input(std::ifstream& _stream, size_t* _refcnt)
53 : stream(_stream), stream_refcnt(*_refcnt)
55 stream_refcnt++;
56 position = stream.tellg();
57 left_unlimited = true;
60 file_input(std::ifstream& _stream, uint32_t size, size_t* _refcnt)
61 : stream(_stream), stream_refcnt(*_refcnt)
63 stream_refcnt++;
64 position = stream.tellg();
65 left_unlimited = false;
66 left = size;
69 void close()
73 std::streamsize read(char* s, std::streamsize n)
75 stream.clear();
76 stream.seekg(position, std::ios_base::beg);
77 if(stream.fail())
78 throw std::runtime_error("Can't seek ZIP file");
79 if(!left_unlimited && left == 0)
80 return -1;
81 if(!left_unlimited && n > left)
82 n = left;
83 stream.read(s, n);
84 std::streamsize r = stream.gcount();
85 if(r == 0 && stream.fail())
86 throw std::runtime_error("Can't read compressed data from ZIP file");
87 if(!stream && r == 0)
88 return -1;
89 position += r;
90 left -= r;
91 return r;
94 ~file_input()
96 if(!--stream_refcnt) {
97 delete &stream;
98 delete &stream_refcnt;
102 file_input(const file_input& f)
103 : stream(f.stream), stream_refcnt(f.stream_refcnt)
105 stream_refcnt++;
106 position = f.position;
107 left_unlimited = f.left_unlimited;
108 left = f.left;
110 protected:
111 std::ifstream& stream;
112 size_t& stream_refcnt;
113 std::streamoff position;
114 bool left_unlimited;
115 uint32_t left;
116 private:
117 file_input& operator=(const file_input& f);
120 class vector_output
122 public:
123 typedef char char_type;
124 typedef boost::iostreams::sink_tag category;
125 vector_output(std::vector<char>& _stream)
126 : stream(_stream)
130 void close()
134 std::streamsize write(const char* s, std::streamsize n)
136 size_t oldsize = stream.size();
137 stream.resize(oldsize + n);
138 memcpy(&stream[oldsize], s, n);
139 return n;
141 protected:
142 std::vector<char>& stream;
145 class size_and_crc_filter_impl
147 public:
148 typedef char char_type;
150 size_and_crc_filter_impl()
152 dsize = 0;
153 crc = ::crc32(0, NULL, 0);
156 void close()
160 bool filter(const char*& src_begin, const char* src_end, char*& dest_begin, char* dest_end,
161 bool flush)
163 ptrdiff_t amount = src_end - src_begin;
164 if(flush && amount == 0)
165 return false;
166 if(amount > dest_end - dest_begin)
167 amount = dest_end - dest_begin;
168 dsize += amount;
169 crc = ::crc32(crc, reinterpret_cast<const unsigned char*>(src_begin), amount);
170 memcpy(dest_begin, src_begin, amount);
171 src_begin += amount;
172 dest_begin += amount;
173 return true;
176 uint32_t size()
178 return dsize;
181 uint32_t crc32()
183 return crc;
185 private:
186 uint32_t dsize;
187 uint32_t crc;
190 class size_and_crc_filter : public boost::iostreams::symmetric_filter<size_and_crc_filter_impl,
191 std::allocator<char>>
193 typedef symmetric_filter<size_and_crc_filter_impl, std::allocator<char>> base_type;
194 public:
195 typedef typename base_type::char_type char_type;
196 typedef typename base_type::category category;
197 size_and_crc_filter(int bsize)
198 : base_type(bsize)
202 uint32_t size()
204 return filter().size();
207 uint32_t crc32()
209 return filter().crc32();
213 struct zipfile_member_info
215 bool central_directory_special; //Central directory, not real member.
216 uint16_t version_needed;
217 uint16_t flags;
218 uint16_t compression;
219 uint16_t mtime_time;
220 uint16_t mtime_day;
221 uint32_t crc;
222 uint32_t compressed_size;
223 uint32_t uncompressed_size;
224 std::string filename;
225 uint32_t header_offset;
226 uint32_t data_offset;
227 uint32_t next_offset;
230 //Parse member starting from current offset.
231 zipfile_member_info parse_member(std::ifstream& file)
233 zipfile_member_info info;
234 info.central_directory_special = false;
235 info.header_offset = file.tellg();
236 //The file header is 30 bytes (this could also hit central header, but that's even larger).
237 unsigned char buffer[30];
238 if(!(file.read(reinterpret_cast<char*>(buffer), 30)))
239 throw std::runtime_error("Can't read file header from ZIP file");
240 uint32_t magic = read32(buffer);
241 if(magic == 0x02014b50) {
242 info.central_directory_special = true;
243 return info;
245 if(magic != 0x04034b50)
246 throw std::runtime_error("ZIP archive corrupt: Expected file or central directory magic");
247 info.version_needed = read16(buffer + 4);
248 info.flags = read16(buffer + 6);
249 info.compression = read16(buffer + 8);
250 info.mtime_time = read16(buffer + 10);
251 info.mtime_day = read16(buffer + 12);
252 info.crc = read32(buffer + 14);
253 info.compressed_size = read32(buffer + 18);
254 info.uncompressed_size = read32(buffer + 22);
255 uint16_t filename_len = read16(buffer + 26);
256 uint16_t extra_len = read16(buffer + 28);
257 if(!filename_len)
258 throw std::runtime_error("Unsupported ZIP feature: Empty filename not allowed");
259 if(info.version_needed > 20) {
260 throw std::runtime_error("Unsupported ZIP feature: Only ZIP versions up to 2.0 supported");
262 if(info.flags & 0x2001)
263 throw std::runtime_error("Unsupported ZIP feature: Encryption is not supported");
264 if(info.flags & 0x8)
265 throw std::runtime_error("Unsupported ZIP feature: Indeterminate length not supported");
266 if(info.flags & 0x20)
267 throw std::runtime_error("Unsupported ZIP feature: Binary patching is not supported");
268 if(info.compression != 0 && info.compression != 8)
269 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
270 if(info.compression == 0 && info.compressed_size != info.uncompressed_size)
271 throw std::runtime_error("ZIP archive corrupt: csize ≠ usize for stored member");
272 std::vector<unsigned char> filename_storage;
273 filename_storage.resize(filename_len);
274 if(!(file.read(reinterpret_cast<char*>(&filename_storage[0]), filename_len)))
275 throw std::runtime_error("Can't read file name from zip file");
276 info.filename = std::string(reinterpret_cast<char*>(&filename_storage[0]), filename_len);
277 info.data_offset = info.header_offset + 30 + filename_len + extra_len;
278 info.next_offset = info.data_offset + info.compressed_size;
279 return info;
283 bool zip_reader::has_member(const std::string& name) throw()
285 return (offsets.count(name) > 0);
288 std::string zip_reader::find_first() throw(std::bad_alloc)
290 if(offsets.empty())
291 return "";
292 else
293 return offsets.begin()->first;
296 std::string zip_reader::find_next(const std::string& name) throw(std::bad_alloc)
298 auto i = offsets.upper_bound(name);
299 if(i == offsets.end())
300 return "";
301 else
302 return i->first;
305 std::istream& zip_reader::operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error)
307 if(!offsets.count(name))
308 throw std::runtime_error("No such file '" + name + "' in zip archive");
309 zipstream->clear();
310 zipstream->seekg(offsets[name], std::ios::beg);
311 zipfile_member_info info = parse_member(*zipstream);
312 zipstream->clear();
313 zipstream->seekg(info.data_offset, std::ios::beg);
314 if(info.compression == 0) {
315 return *new boost::iostreams::stream<file_input>(*zipstream, info.uncompressed_size, refcnt);
316 } else if(info.compression == 8) {
317 boost::iostreams::filtering_istream* s = new boost::iostreams::filtering_istream();
318 boost::iostreams::zlib_params params;
319 params.noheader = true;
320 s->push(boost::iostreams::zlib_decompressor(params));
321 s->push(file_input(*zipstream, info.compressed_size, refcnt));
322 return *s;
323 } else
324 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
327 zip_reader::iterator zip_reader::begin() throw(std::bad_alloc)
329 return iterator(offsets.begin());
332 zip_reader::iterator zip_reader::end() throw(std::bad_alloc)
334 return iterator(offsets.end());
337 zip_reader::riterator zip_reader::rbegin() throw(std::bad_alloc)
339 return riterator(offsets.rbegin());
342 zip_reader::riterator zip_reader::rend() throw(std::bad_alloc)
344 return riterator(offsets.rend());
347 zip_reader::~zip_reader() throw()
349 if(!--*refcnt) {
350 delete zipstream;
351 delete refcnt;
355 zip_reader::zip_reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error)
357 zipfile_member_info info;
358 info.next_offset = 0;
359 zipstream = new std::ifstream;
360 zipstream->open(zipfile.c_str(), std::ios::binary);
361 refcnt = new size_t;
362 *refcnt = 1;
363 if(!*zipstream)
364 throw std::runtime_error("Can't open zipfile '" + zipfile + "' for reading");
365 do {
366 zipstream->clear();
367 zipstream->seekg(info.next_offset);
368 if(zipstream->fail())
369 throw std::runtime_error("Can't seek ZIP file");
370 info = parse_member(*zipstream);
371 if(info.central_directory_special)
372 break;
373 offsets[info.filename] = info.header_offset;
374 } while(1);
377 zip_writer::zip_writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error)
379 compression = _compression;
380 zipfile_path = zipfile;
381 temp_path = zipfile + ".tmp";
382 zipstream.open(temp_path.c_str(), std::ios::binary);
383 if(!zipstream)
384 throw std::runtime_error("Can't open zipfile '" + temp_path + "' for writing");
385 committed = false;
388 zip_writer::~zip_writer() throw()
390 if(!committed)
391 remove(temp_path.c_str());
394 void zip_writer::commit() throw(std::bad_alloc, std::logic_error, std::runtime_error)
396 if(committed)
397 throw std::logic_error("Can't commit twice");
398 if(open_file != "")
399 throw std::logic_error("Can't commit with file open");
400 std::vector<unsigned char> directory_entry;
401 uint32_t cdirsize = 0;
402 uint32_t cdiroff = zipstream.tellp();
403 if(cdiroff == (uint32_t)-1)
404 throw std::runtime_error("Can't read current ZIP stream position");
405 for(auto i : files) {
406 cdirsize += (46 + i.first.length());
407 directory_entry.resize(46 + i.first.length());
408 write32(&directory_entry[0], 0x02014b50);
409 write16(&directory_entry[4], 3);
410 write16(&directory_entry[6], 20);
411 write16(&directory_entry[8], 0);
412 write16(&directory_entry[10], compression ? 8 : 0);
413 write16(&directory_entry[12], 0);
414 write16(&directory_entry[14], 10273);
415 write32(&directory_entry[16], i.second.crc);
416 write32(&directory_entry[20], i.second.compressed_size);
417 write32(&directory_entry[24], i.second.uncompressed_size);
418 write16(&directory_entry[28], i.first.length());
419 write16(&directory_entry[30], 0);
420 write16(&directory_entry[32], 0);
421 write16(&directory_entry[34], 0);
422 write16(&directory_entry[36], 0);
423 write32(&directory_entry[38], 0);
424 write32(&directory_entry[42], i.second.offset);
425 memcpy(&directory_entry[46], i.first.c_str(), i.first.length());
426 zipstream.write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
427 if(!zipstream)
428 throw std::runtime_error("Failed to write central directory entry to output file");
430 directory_entry.resize(22);
431 write32(&directory_entry[0], 0x06054b50);
432 write16(&directory_entry[4], 0);
433 write16(&directory_entry[6], 0);
434 write16(&directory_entry[8], files.size());
435 write16(&directory_entry[10], files.size());
436 write32(&directory_entry[12], cdirsize);
437 write32(&directory_entry[16], cdiroff);
438 write16(&directory_entry[20], 0);
439 zipstream.write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
440 if(!zipstream)
441 throw std::runtime_error("Failed to write central directory end marker to output file");
442 zipstream.close();
443 std::string backup = zipfile_path + ".backup";
444 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
445 //Grumble, Windows seemingly can't do this atomically.
446 remove(backup.c_str());
447 #endif
448 rename(zipfile_path.c_str(), backup.c_str());
449 if(rename(temp_path.c_str(), zipfile_path.c_str()) < 0)
450 throw std::runtime_error("Can't rename '" + temp_path + "' -> '" + zipfile_path + "'");
451 committed = true;
454 std::ostream& zip_writer::create_file(const std::string& name) throw(std::bad_alloc, std::logic_error,
455 std::runtime_error)
457 if(open_file != "")
458 throw std::logic_error("Can't open file with file open");
459 if(name == "")
460 throw std::runtime_error("Bad member name");
461 current_compressed_file.resize(0);
462 s = new boost::iostreams::filtering_ostream();
463 s->push(size_and_crc_filter(4096));
464 if(compression) {
465 boost::iostreams::zlib_params params;
466 params.noheader = true;
467 s->push(boost::iostreams::zlib_compressor(params));
469 s->push(vector_output(current_compressed_file));
470 open_file = name;
471 return *s;
474 void zip_writer::close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error)
476 if(open_file == "")
477 throw std::logic_error("Can't close file with no file open");
478 uint32_t ucs, cs, crc32;
479 boost::iostreams::close(*s);
480 size_and_crc_filter& f = *s->component<size_and_crc_filter>(0);
481 cs = current_compressed_file.size();
482 ucs = f.size();
483 crc32 = f.crc32();
484 delete s;
486 base_offset = zipstream.tellp();
487 if(base_offset == (uint32_t)-1)
488 throw std::runtime_error("Can't read current ZIP stream position");
489 unsigned char header[30];
490 memset(header, 0, 30);
491 write32(header, 0x04034b50);
492 header[4] = 20;
493 header[6] = 0;
494 header[8] = compression ? 8 : 0;
495 header[12] = 33;
496 header[13] = 40;
497 write32(header + 14, crc32);
498 write32(header + 18, cs);
499 write32(header + 22, ucs);
500 write16(header + 26, open_file.length());
501 zipstream.write(reinterpret_cast<char*>(header), 30);
502 zipstream.write(open_file.c_str(), open_file.length());
503 zipstream.write(&current_compressed_file[0], current_compressed_file.size());
504 if(!zipstream)
505 throw std::runtime_error("Can't write member to ZIP file");
506 current_compressed_file.resize(0);
507 zip_file_info info;
508 info.crc = crc32;
509 info.uncompressed_size = ucs;
510 info.compressed_size = cs;
511 info.offset = base_offset;
512 files[open_file] = info;
513 open_file = "";
516 namespace
518 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
519 const char* path_splitters = "\\/";
520 bool drives_allowed = true;
521 #else
522 //Assume Unix(-like) system.
523 const char* path_splitters = "/";
524 bool drives_allowed = false;
525 #endif
527 const char* str_index(const char* str, int ch)
529 while(*str)
530 if(*str == ch)
531 return str;
532 return NULL;
535 bool ispathsep(char ch)
537 return (str_index(path_splitters, static_cast<int>(static_cast<unsigned char>(ch))) != NULL);
540 bool isroot(const std::string& path)
542 if(path.length() == 1 && ispathsep(path[0]))
543 return true;
544 if(!drives_allowed)
545 //NO more cases for this.
546 return false;
547 if(path.length() == 3 && path[0] >= 'A' && path[0] <= 'Z' && path[1] == ':' && ispathsep(path[2]))
548 return true;
549 //UNC.
550 if(path.length() <= 3 || !ispathsep(path[0]) || !ispathsep(path[1]) ||
551 !ispathsep(path[path.length() - 1]))
552 return false;
553 return (path.find_first_of(path_splitters, 2) == path.length() - 1);
556 std::string walk(const std::string& path, const std::string& component)
558 if(component == "" || component == ".")
559 //Current directory.
560 return path;
561 else if(component == "..") {
562 //Parent directory.
563 if(path == "" || isroot(path))
564 throw std::runtime_error("Can't rise to containing directory");
565 std::string _path = path;
566 size_t split = _path.find_last_of(path_splitters);
567 if(split < _path.length())
568 return _path.substr(0, split);
569 else
570 return "";
571 } else if(path == "" || ispathsep(path[path.length() - 1]))
572 return path + component;
573 else
574 return path + "/" + component;
577 std::string combine_path(const std::string& _name, const std::string& _referencing_path)
579 std::string name = _name;
580 std::string referencing_path = _referencing_path;
581 size_t x = referencing_path.find_last_of(path_splitters);
582 if(x < referencing_path.length())
583 referencing_path = referencing_path.substr(0, x);
584 else
585 return name;
586 //Check if name is absolute.
587 if(ispathsep(name[0]))
588 return name;
589 if(drives_allowed && name.length() >= 3 && name[0] >= 'A' && name[0] <= 'Z' && name[1] == ':' &&
590 ispathsep(name[2]))
591 return name;
592 //It is not absolute.
593 std::string path = referencing_path;
594 size_t pindex = 0;
595 while(true) {
596 size_t split = name.find_first_of(path_splitters, pindex);
597 std::string c;
598 if(split < name.length())
599 c = name.substr(pindex, split - pindex);
600 else
601 c = name.substr(pindex);
602 path = walk(path, c);
603 if(split < name.length())
604 pindex = split + 1;
605 else
606 break;
608 //If path becomes empty, assume it means current directory.
609 if(path == "")
610 path = ".";
611 return path;
615 std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
616 std::runtime_error)
618 return combine_path(name, referencing_path);
621 std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
622 std::runtime_error)
624 std::string path_to_open = combine_path(name, referencing_path);
625 std::string final_path = path_to_open;
626 //Try to open this from the main OS filesystem.
627 std::ifstream* i = new std::ifstream(path_to_open.c_str(), std::ios::binary);
628 if(i->is_open()) {
629 return *i;
631 delete i;
632 //Didn't succeed. Try to open as ZIP archive.
633 std::string membername;
634 while(true) {
635 size_t split = path_to_open.find_last_of("/");
636 if(split >= path_to_open.length())
637 throw std::runtime_error("Can't open '" + final_path + "'");
638 //Move a component to member name.
639 if(membername != "")
640 membername = path_to_open.substr(split + 1) + "/" + membername;
641 else
642 membername = path_to_open.substr(split + 1);
643 path_to_open = path_to_open.substr(0, split);
644 try {
645 zip_reader r(path_to_open);
646 return r[membername];
647 } catch(std::bad_alloc& e) {
648 throw;
649 } catch(std::runtime_error& e) {
654 std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
655 std::runtime_error)
657 std::vector<char> out;
658 std::istream& s = open_file_relative(name, referencing_path);
659 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
660 boost::iostreams::copy(s, rd);
661 delete &s;
662 return out;