Add lua functions to manipulate emulator settings
[lsnes.git] / zip.cpp
blob7f94c25de5407a1eba1c3c758618d8d000ca70d1
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.begin(); i != files.end(); ++i) {
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 #if defined(_WIN32) || defined(_WIN64)
442 //Grumble, Windows seemingly can't do this atomically.
443 remove(zipfile_path.c_str());
444 #endif
445 if(rename(temp_path.c_str(), zipfile_path.c_str()) < 0)
446 throw std::runtime_error("Can't rename '" + temp_path + "' -> '" + zipfile_path + "'");
447 committed = true;
450 std::ostream& zip_writer::create_file(const std::string& name) throw(std::bad_alloc, std::logic_error,
451 std::runtime_error)
453 if(open_file != "")
454 throw std::logic_error("Can't open file with file open");
455 if(name == "")
456 throw std::runtime_error("Bad member name");
457 current_compressed_file.resize(0);
458 s = new boost::iostreams::filtering_ostream();
459 s->push(size_and_crc_filter(4096));
460 if(compression) {
461 boost::iostreams::zlib_params params;
462 params.noheader = true;
463 s->push(boost::iostreams::zlib_compressor(params));
465 s->push(vector_output(current_compressed_file));
466 open_file = name;
467 return *s;
470 void zip_writer::close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error)
472 if(open_file == "")
473 throw std::logic_error("Can't close file with no file open");
474 uint32_t ucs, cs, crc32;
475 boost::iostreams::close(*s);
476 size_and_crc_filter& f = *s->component<size_and_crc_filter>(0);
477 cs = current_compressed_file.size();
478 ucs = f.size();
479 crc32 = f.crc32();
480 delete s;
482 base_offset = zipstream.tellp();
483 if(base_offset == (uint32_t)-1)
484 throw std::runtime_error("Can't read current ZIP stream position");
485 unsigned char header[30];
486 memset(header, 0, 30);
487 write32(header, 0x04034b50);
488 header[4] = 20;
489 header[6] = 0;
490 header[8] = compression ? 8 : 0;
491 header[12] = 33;
492 header[13] = 40;
493 write32(header + 14, crc32);
494 write32(header + 18, cs);
495 write32(header + 22, ucs);
496 write16(header + 26, open_file.length());
497 zipstream.write(reinterpret_cast<char*>(header), 30);
498 zipstream.write(open_file.c_str(), open_file.length());
499 zipstream.write(&current_compressed_file[0], current_compressed_file.size());
500 if(!zipstream)
501 throw std::runtime_error("Can't write member to ZIP file");
502 current_compressed_file.resize(0);
503 zip_file_info info;
504 info.crc = crc32;
505 info.uncompressed_size = ucs;
506 info.compressed_size = cs;
507 info.offset = base_offset;
508 files[open_file] = info;
509 open_file = "";
512 namespace
514 #if defined(_WIN32) || defined(_WIN64)
515 const char* path_splitters = "\\/";
516 bool drives_allowed = true;
517 #else
518 //Assume Unix(-like) system.
519 const char* path_splitters = "/";
520 bool drives_allowed = false;
521 #endif
523 bool ispathsep(char ch)
525 return (index(path_splitters, static_cast<int>(static_cast<unsigned char>(ch))) != NULL);
528 bool isroot(const std::string& path)
530 if(path.length() == 1 && ispathsep(path[0]))
531 return true;
532 if(!drives_allowed)
533 //NO more cases for this.
534 return false;
535 if(path.length() == 3 && path[0] >= 'A' && path[0] <= 'Z' && path[1] == ':' && ispathsep(path[2]))
536 return true;
537 //UNC.
538 if(path.length() <= 3 || !ispathsep(path[0]) || !ispathsep(path[1]) ||
539 !ispathsep(path[path.length() - 1]))
540 return false;
541 return (path.find_first_of(path_splitters, 2) == path.length() - 1);
544 std::string walk(const std::string& path, const std::string& component)
546 if(component == "" || component == ".")
547 //Current directory.
548 return path;
549 else if(component == "..") {
550 //Parent directory.
551 if(path == "" || isroot(path))
552 throw std::runtime_error("Can't rise to containing directory");
553 std::string _path = path;
554 size_t split = _path.find_last_of(path_splitters);
555 if(split < _path.length())
556 return _path.substr(0, split);
557 else
558 return "";
559 } else if(path == "" || ispathsep(path[path.length() - 1]))
560 return path + component;
561 else
562 return path + "/" + component;
565 std::string combine_path(const std::string& _name, const std::string& _referencing_path)
567 std::string name = _name;
568 std::string referencing_path = _referencing_path;
569 size_t x = referencing_path.find_last_of(path_splitters);
570 if(x < referencing_path.length())
571 referencing_path = referencing_path.substr(0, x);
572 else
573 return name;
574 //Check if name is absolute.
575 if(ispathsep(name[0]))
576 return name;
577 if(drives_allowed && name.length() >= 3 && name[0] >= 'A' && name[0] <= 'Z' && name[1] == ':' &&
578 ispathsep(name[2]))
579 return name;
580 //It is not absolute.
581 std::string path = referencing_path;
582 size_t pindex = 0;
583 while(true) {
584 size_t split = name.find_first_of(path_splitters, pindex);
585 std::string c;
586 if(split < name.length())
587 c = name.substr(pindex, split - pindex);
588 else
589 c = name.substr(pindex);
590 path = walk(path, c);
591 if(split < name.length())
592 pindex = split + 1;
593 else
594 break;
596 //If path becomes empty, assume it means current directory.
597 if(path == "")
598 path = ".";
599 return path;
603 std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
604 std::runtime_error)
606 return combine_path(name, referencing_path);
609 std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
610 std::runtime_error)
612 std::string path_to_open = combine_path(name, referencing_path);
613 std::string final_path = path_to_open;
614 //Try to open this from the main OS filesystem.
615 std::ifstream* i = new std::ifstream(path_to_open.c_str(), std::ios::binary);
616 if(i->is_open()) {
617 return *i;
619 delete i;
620 //Didn't succeed. Try to open as ZIP archive.
621 std::string membername;
622 while(true) {
623 size_t split = path_to_open.find_last_of("/");
624 if(split >= path_to_open.length())
625 throw std::runtime_error("Can't open '" + final_path + "'");
626 //Move a component to member name.
627 if(membername != "")
628 membername = path_to_open.substr(split + 1) + "/" + membername;
629 else
630 membername = path_to_open.substr(split + 1);
631 path_to_open = path_to_open.substr(0, split);
632 try {
633 zip_reader r(path_to_open);
634 return r[membername];
635 } catch(std::bad_alloc& e) {
636 throw;
637 } catch(std::runtime_error& e) {
642 std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
643 std::runtime_error)
645 std::vector<char> out;
646 std::istream& s = open_file_relative(name, referencing_path);
647 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
648 boost::iostreams::copy(s, rd);
649 delete &s;
650 return out;