Evdev joystick plugin
[lsnes.git] / generic / zip.cpp
blob545398cc37b4a56aaf207c323c20c6d463294b47
1 #include "lsnes.hpp"
2 #include "zip.hpp"
3 #include <cstdint>
4 #include <cstring>
5 #include <iostream>
6 #include <stdexcept>
7 #include <boost/iostreams/categories.hpp>
8 #include <boost/iostreams/copy.hpp>
9 #include <boost/iostreams/stream.hpp>
10 #include <boost/iostreams/stream_buffer.hpp>
11 #include <boost/iostreams/filter/symmetric.hpp>
12 #include <boost/iostreams/filter/zlib.hpp>
13 #include <boost/iostreams/filtering_stream.hpp>
14 #include <boost/iostreams/device/back_inserter.hpp>
16 namespace
18 uint32_t read32(const unsigned char* buf, unsigned offset = 0, unsigned modulo = 4) throw()
20 return (uint32_t)buf[offset % modulo] |
21 ((uint32_t)buf[(offset + 1) % modulo] << 8) |
22 ((uint32_t)buf[(offset + 2) % modulo] << 16) |
23 ((uint32_t)buf[(offset + 3) % modulo] << 24);
26 uint16_t read16(const unsigned char* buf) throw()
28 return (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
31 void write16(unsigned char* buf, uint16_t value) throw()
33 buf[0] = (value) & 0xFF;
34 buf[1] = (value >> 8) & 0xFF;
37 void write32(unsigned char* buf, uint32_t value) throw()
39 buf[0] = (value) & 0xFF;
40 buf[1] = (value >> 8) & 0xFF;
41 buf[2] = (value >> 16) & 0xFF;
42 buf[3] = (value >> 24) & 0xFF;
45 class file_input
47 public:
48 typedef char char_type;
49 typedef boost::iostreams::source_tag category;
50 file_input(std::ifstream& _stream, size_t* _refcnt)
51 : stream(_stream), stream_refcnt(*_refcnt)
53 stream_refcnt++;
54 position = stream.tellg();
55 left_unlimited = true;
58 file_input(std::ifstream& _stream, uint32_t size, size_t* _refcnt)
59 : stream(_stream), stream_refcnt(*_refcnt)
61 stream_refcnt++;
62 position = stream.tellg();
63 left_unlimited = false;
64 left = size;
67 void close()
71 std::streamsize read(char* s, std::streamsize n)
73 stream.clear();
74 stream.seekg(position, std::ios_base::beg);
75 if(stream.fail())
76 throw std::runtime_error("Can't seek ZIP file");
77 if(!left_unlimited && left == 0)
78 return -1;
79 if(!left_unlimited && n > left)
80 n = left;
81 stream.read(s, n);
82 std::streamsize r = stream.gcount();
83 if(r == 0 && stream.fail())
84 throw std::runtime_error("Can't read compressed data from ZIP file");
85 if(!stream && r == 0)
86 return -1;
87 position += r;
88 left -= r;
89 return r;
92 ~file_input()
94 if(!--stream_refcnt) {
95 delete &stream;
96 delete &stream_refcnt;
100 file_input(const file_input& f)
101 : stream(f.stream), stream_refcnt(f.stream_refcnt)
103 stream_refcnt++;
104 position = f.position;
105 left_unlimited = f.left_unlimited;
106 left = f.left;
108 protected:
109 std::ifstream& stream;
110 size_t& stream_refcnt;
111 std::streamoff position;
112 bool left_unlimited;
113 uint32_t left;
114 private:
115 file_input& operator=(const file_input& f);
118 class vector_output
120 public:
121 typedef char char_type;
122 typedef boost::iostreams::sink_tag category;
123 vector_output(std::vector<char>& _stream)
124 : stream(_stream)
128 void close()
132 std::streamsize write(const char* s, std::streamsize n)
134 size_t oldsize = stream.size();
135 stream.resize(oldsize + n);
136 memcpy(&stream[oldsize], s, n);
137 return n;
139 protected:
140 std::vector<char>& stream;
143 class size_and_crc_filter_impl
145 public:
146 typedef char char_type;
148 size_and_crc_filter_impl()
150 dsize = 0;
151 crc = ::crc32(0, NULL, 0);
154 void close()
158 bool filter(const char*& src_begin, const char* src_end, char*& dest_begin, char* dest_end,
159 bool flush)
161 ptrdiff_t amount = src_end - src_begin;
162 if(flush && amount == 0)
163 return false;
164 if(amount > dest_end - dest_begin)
165 amount = dest_end - dest_begin;
166 dsize += amount;
167 crc = ::crc32(crc, reinterpret_cast<const unsigned char*>(src_begin), amount);
168 memcpy(dest_begin, src_begin, amount);
169 src_begin += amount;
170 dest_begin += amount;
171 return true;
174 uint32_t size()
176 return dsize;
179 uint32_t crc32()
181 return crc;
183 private:
184 uint32_t dsize;
185 uint32_t crc;
188 class size_and_crc_filter : public boost::iostreams::symmetric_filter<size_and_crc_filter_impl,
189 std::allocator<char>>
191 typedef symmetric_filter<size_and_crc_filter_impl, std::allocator<char>> base_type;
192 public:
193 typedef typename base_type::char_type char_type;
194 typedef typename base_type::category category;
195 size_and_crc_filter(int bsize)
196 : base_type(bsize)
200 uint32_t size()
202 return filter().size();
205 uint32_t crc32()
207 return filter().crc32();
211 struct zipfile_member_info
213 bool central_directory_special; //Central directory, not real member.
214 uint16_t version_needed;
215 uint16_t flags;
216 uint16_t compression;
217 uint16_t mtime_time;
218 uint16_t mtime_day;
219 uint32_t crc;
220 uint32_t compressed_size;
221 uint32_t uncompressed_size;
222 std::string filename;
223 uint32_t header_offset;
224 uint32_t data_offset;
225 uint32_t next_offset;
228 //Parse member starting from current offset.
229 zipfile_member_info parse_member(std::ifstream& file)
231 zipfile_member_info info;
232 info.central_directory_special = false;
233 info.header_offset = file.tellg();
234 //The file header is 30 bytes (this could also hit central header, but that's even larger).
235 unsigned char buffer[30];
236 if(!(file.read(reinterpret_cast<char*>(buffer), 30)))
237 throw std::runtime_error("Can't read file header from ZIP file");
238 uint32_t magic = read32(buffer);
239 if(magic == 0x02014b50) {
240 info.central_directory_special = true;
241 return info;
243 if(magic != 0x04034b50)
244 throw std::runtime_error("ZIP archive corrupt: Expected file or central directory magic");
245 info.version_needed = read16(buffer + 4);
246 info.flags = read16(buffer + 6);
247 info.compression = read16(buffer + 8);
248 info.mtime_time = read16(buffer + 10);
249 info.mtime_day = read16(buffer + 12);
250 info.crc = read32(buffer + 14);
251 info.compressed_size = read32(buffer + 18);
252 info.uncompressed_size = read32(buffer + 22);
253 uint16_t filename_len = read16(buffer + 26);
254 uint16_t extra_len = read16(buffer + 28);
255 if(!filename_len)
256 throw std::runtime_error("Unsupported ZIP feature: Empty filename not allowed");
257 if(info.version_needed > 20) {
258 throw std::runtime_error("Unsupported ZIP feature: Only ZIP versions up to 2.0 supported");
260 if(info.flags & 0x2001)
261 throw std::runtime_error("Unsupported ZIP feature: Encryption is not supported");
262 if(info.flags & 0x8)
263 throw std::runtime_error("Unsupported ZIP feature: Indeterminate length not supported");
264 if(info.flags & 0x20)
265 throw std::runtime_error("Unsupported ZIP feature: Binary patching is not supported");
266 if(info.compression != 0 && info.compression != 8)
267 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
268 if(info.compression == 0 && info.compressed_size != info.uncompressed_size)
269 throw std::runtime_error("ZIP archive corrupt: csize ≠ usize for stored member");
270 std::vector<unsigned char> filename_storage;
271 filename_storage.resize(filename_len);
272 if(!(file.read(reinterpret_cast<char*>(&filename_storage[0]), filename_len)))
273 throw std::runtime_error("Can't read file name from zip file");
274 info.filename = std::string(reinterpret_cast<char*>(&filename_storage[0]), filename_len);
275 info.data_offset = info.header_offset + 30 + filename_len + extra_len;
276 info.next_offset = info.data_offset + info.compressed_size;
277 return info;
281 bool zip_reader::has_member(const std::string& name) throw()
283 return (offsets.count(name) > 0);
286 std::string zip_reader::find_first() throw(std::bad_alloc)
288 if(offsets.empty())
289 return "";
290 else
291 return offsets.begin()->first;
294 std::string zip_reader::find_next(const std::string& name) throw(std::bad_alloc)
296 auto i = offsets.upper_bound(name);
297 if(i == offsets.end())
298 return "";
299 else
300 return i->first;
303 std::istream& zip_reader::operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error)
305 if(!offsets.count(name))
306 throw std::runtime_error("No such file '" + name + "' in zip archive");
307 zipstream->clear();
308 zipstream->seekg(offsets[name], std::ios::beg);
309 zipfile_member_info info = parse_member(*zipstream);
310 zipstream->clear();
311 zipstream->seekg(info.data_offset, std::ios::beg);
312 if(info.compression == 0) {
313 return *new boost::iostreams::stream<file_input>(*zipstream, info.uncompressed_size, refcnt);
314 } else if(info.compression == 8) {
315 boost::iostreams::filtering_istream* s = new boost::iostreams::filtering_istream();
316 boost::iostreams::zlib_params params;
317 params.noheader = true;
318 s->push(boost::iostreams::zlib_decompressor(params));
319 s->push(file_input(*zipstream, info.compressed_size, refcnt));
320 return *s;
321 } else
322 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
325 zip_reader::iterator zip_reader::begin() throw(std::bad_alloc)
327 return iterator(offsets.begin());
330 zip_reader::iterator zip_reader::end() throw(std::bad_alloc)
332 return iterator(offsets.end());
335 zip_reader::riterator zip_reader::rbegin() throw(std::bad_alloc)
337 return riterator(offsets.rbegin());
340 zip_reader::riterator zip_reader::rend() throw(std::bad_alloc)
342 return riterator(offsets.rend());
345 zip_reader::~zip_reader() throw()
347 if(!--*refcnt) {
348 delete zipstream;
349 delete refcnt;
353 zip_reader::zip_reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error)
355 zipfile_member_info info;
356 info.next_offset = 0;
357 zipstream = new std::ifstream;
358 zipstream->open(zipfile.c_str(), std::ios::binary);
359 refcnt = new size_t;
360 *refcnt = 1;
361 if(!*zipstream)
362 throw std::runtime_error("Can't open zipfile '" + zipfile + "' for reading");
363 do {
364 zipstream->clear();
365 zipstream->seekg(info.next_offset);
366 if(zipstream->fail())
367 throw std::runtime_error("Can't seek ZIP file");
368 info = parse_member(*zipstream);
369 if(info.central_directory_special)
370 break;
371 offsets[info.filename] = info.header_offset;
372 } while(1);
375 zip_writer::zip_writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error)
377 compression = _compression;
378 zipfile_path = zipfile;
379 temp_path = zipfile + ".tmp";
380 zipstream.open(temp_path.c_str(), std::ios::binary);
381 if(!zipstream)
382 throw std::runtime_error("Can't open zipfile '" + temp_path + "' for writing");
383 committed = false;
386 zip_writer::~zip_writer() throw()
388 if(!committed)
389 remove(temp_path.c_str());
392 void zip_writer::commit() throw(std::bad_alloc, std::logic_error, std::runtime_error)
394 if(committed)
395 throw std::logic_error("Can't commit twice");
396 if(open_file != "")
397 throw std::logic_error("Can't commit with file open");
398 std::vector<unsigned char> directory_entry;
399 uint32_t cdirsize = 0;
400 uint32_t cdiroff = zipstream.tellp();
401 if(cdiroff == (uint32_t)-1)
402 throw std::runtime_error("Can't read current ZIP stream position");
403 for(auto i : files) {
404 cdirsize += (46 + i.first.length());
405 directory_entry.resize(46 + i.first.length());
406 write32(&directory_entry[0], 0x02014b50);
407 write16(&directory_entry[4], 3);
408 write16(&directory_entry[6], 20);
409 write16(&directory_entry[8], 8);
410 write16(&directory_entry[10], compression ? 8 : 0);
411 write16(&directory_entry[12], 0);
412 write16(&directory_entry[14], 10273);
413 write32(&directory_entry[16], i.second.crc);
414 write32(&directory_entry[20], i.second.compressed_size);
415 write32(&directory_entry[24], i.second.uncompressed_size);
416 write16(&directory_entry[28], i.first.length());
417 write16(&directory_entry[30], 0);
418 write16(&directory_entry[32], 0);
419 write16(&directory_entry[34], 0);
420 write16(&directory_entry[36], 0);
421 write32(&directory_entry[38], 0);
422 write32(&directory_entry[42], i.second.offset);
423 memcpy(&directory_entry[46], i.first.c_str(), i.first.length());
424 zipstream.write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
425 if(!zipstream)
426 throw std::runtime_error("Failed to write central directory entry to output file");
428 directory_entry.resize(22);
429 write32(&directory_entry[0], 0x06054b50);
430 write16(&directory_entry[4], 0);
431 write16(&directory_entry[6], 0);
432 write16(&directory_entry[8], files.size());
433 write16(&directory_entry[10], files.size());
434 write32(&directory_entry[12], cdirsize);
435 write32(&directory_entry[16], cdiroff);
436 write16(&directory_entry[20], 0);
437 zipstream.write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
438 if(!zipstream)
439 throw std::runtime_error("Failed to write central directory end marker to output file");
440 zipstream.close();
441 std::string backup = zipfile_path + ".backup";
442 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
443 //Grumble, Windows seemingly can't do this atomically.
444 remove(backup.c_str());
445 #endif
446 rename(zipfile_path.c_str(), backup.c_str());
447 if(rename(temp_path.c_str(), zipfile_path.c_str()) < 0)
448 throw std::runtime_error("Can't rename '" + temp_path + "' -> '" + zipfile_path + "'");
449 committed = true;
452 std::ostream& zip_writer::create_file(const std::string& name) throw(std::bad_alloc, std::logic_error,
453 std::runtime_error)
455 if(open_file != "")
456 throw std::logic_error("Can't open file with file open");
457 if(name == "")
458 throw std::runtime_error("Bad member name");
459 current_compressed_file.resize(0);
460 s = new boost::iostreams::filtering_ostream();
461 s->push(size_and_crc_filter(4096));
462 if(compression) {
463 boost::iostreams::zlib_params params;
464 params.noheader = true;
465 s->push(boost::iostreams::zlib_compressor(params));
467 s->push(vector_output(current_compressed_file));
468 open_file = name;
469 return *s;
472 void zip_writer::close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error)
474 if(open_file == "")
475 throw std::logic_error("Can't close file with no file open");
476 uint32_t ucs, cs, crc32;
477 boost::iostreams::close(*s);
478 size_and_crc_filter& f = *s->component<size_and_crc_filter>(0);
479 cs = current_compressed_file.size();
480 ucs = f.size();
481 crc32 = f.crc32();
482 delete s;
484 base_offset = zipstream.tellp();
485 if(base_offset == (uint32_t)-1)
486 throw std::runtime_error("Can't read current ZIP stream position");
487 unsigned char header[30];
488 memset(header, 0, 30);
489 write32(header, 0x04034b50);
490 header[4] = 20;
491 header[6] = 0;
492 header[8] = compression ? 8 : 0;
493 header[12] = 33;
494 header[13] = 40;
495 write32(header + 14, crc32);
496 write32(header + 18, cs);
497 write32(header + 22, ucs);
498 write16(header + 26, open_file.length());
499 zipstream.write(reinterpret_cast<char*>(header), 30);
500 zipstream.write(open_file.c_str(), open_file.length());
501 zipstream.write(&current_compressed_file[0], current_compressed_file.size());
502 if(!zipstream)
503 throw std::runtime_error("Can't write member to ZIP file");
504 current_compressed_file.resize(0);
505 zip_file_info info;
506 info.crc = crc32;
507 info.uncompressed_size = ucs;
508 info.compressed_size = cs;
509 info.offset = base_offset;
510 files[open_file] = info;
511 open_file = "";
514 namespace
516 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
517 const char* path_splitters = "\\/";
518 bool drives_allowed = true;
519 #else
520 //Assume Unix(-like) system.
521 const char* path_splitters = "/";
522 bool drives_allowed = false;
523 #endif
525 const char* str_index(const char* str, int ch)
527 while(*str)
528 if(*str == ch)
529 return str;
530 return NULL;
533 bool ispathsep(char ch)
535 return (str_index(path_splitters, static_cast<int>(static_cast<unsigned char>(ch))) != NULL);
538 bool isroot(const std::string& path)
540 if(path.length() == 1 && ispathsep(path[0]))
541 return true;
542 if(!drives_allowed)
543 //NO more cases for this.
544 return false;
545 if(path.length() == 3 && path[0] >= 'A' && path[0] <= 'Z' && path[1] == ':' && ispathsep(path[2]))
546 return true;
547 //UNC.
548 if(path.length() <= 3 || !ispathsep(path[0]) || !ispathsep(path[1]) ||
549 !ispathsep(path[path.length() - 1]))
550 return false;
551 return (path.find_first_of(path_splitters, 2) == path.length() - 1);
554 std::string walk(const std::string& path, const std::string& component)
556 if(component == "" || component == ".")
557 //Current directory.
558 return path;
559 else if(component == "..") {
560 //Parent directory.
561 if(path == "" || isroot(path))
562 throw std::runtime_error("Can't rise to containing directory");
563 std::string _path = path;
564 size_t split = _path.find_last_of(path_splitters);
565 if(split < _path.length())
566 return _path.substr(0, split);
567 else
568 return "";
569 } else if(path == "" || ispathsep(path[path.length() - 1]))
570 return path + component;
571 else
572 return path + "/" + component;
575 std::string combine_path(const std::string& _name, const std::string& _referencing_path)
577 std::string name = _name;
578 std::string referencing_path = _referencing_path;
579 size_t x = referencing_path.find_last_of(path_splitters);
580 if(x < referencing_path.length())
581 referencing_path = referencing_path.substr(0, x);
582 else
583 return name;
584 //Check if name is absolute.
585 if(ispathsep(name[0]))
586 return name;
587 if(drives_allowed && name.length() >= 3 && name[0] >= 'A' && name[0] <= 'Z' && name[1] == ':' &&
588 ispathsep(name[2]))
589 return name;
590 //It is not absolute.
591 std::string path = referencing_path;
592 size_t pindex = 0;
593 while(true) {
594 size_t split = name.find_first_of(path_splitters, pindex);
595 std::string c;
596 if(split < name.length())
597 c = name.substr(pindex, split - pindex);
598 else
599 c = name.substr(pindex);
600 path = walk(path, c);
601 if(split < name.length())
602 pindex = split + 1;
603 else
604 break;
606 //If path becomes empty, assume it means current directory.
607 if(path == "")
608 path = ".";
609 return path;
613 std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
614 std::runtime_error)
616 return combine_path(name, referencing_path);
619 std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
620 std::runtime_error)
622 std::string path_to_open = combine_path(name, referencing_path);
623 std::string final_path = path_to_open;
624 //Try to open this from the main OS filesystem.
625 std::ifstream* i = new std::ifstream(path_to_open.c_str(), std::ios::binary);
626 if(i->is_open()) {
627 return *i;
629 delete i;
630 //Didn't succeed. Try to open as ZIP archive.
631 std::string membername;
632 while(true) {
633 size_t split = path_to_open.find_last_of("/");
634 if(split >= path_to_open.length())
635 throw std::runtime_error("Can't open '" + final_path + "'");
636 //Move a component to member name.
637 if(membername != "")
638 membername = path_to_open.substr(split + 1) + "/" + membername;
639 else
640 membername = path_to_open.substr(split + 1);
641 path_to_open = path_to_open.substr(0, split);
642 try {
643 zip_reader r(path_to_open);
644 return r[membername];
645 } catch(std::bad_alloc& e) {
646 throw;
647 } catch(std::runtime_error& e) {
652 std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
653 std::runtime_error)
655 std::vector<char> out;
656 std::istream& s = open_file_relative(name, referencing_path);
657 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
658 boost::iostreams::copy(s, rd);
659 delete &s;
660 return out;