]> icculus.org git repositories - taylor/freespace2.git/blob - src/cfileutil/cfileutil.cpp
first pass at proper gamepad support
[taylor/freespace2.git] / src / cfileutil / cfileutil.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 #define SDL_MAIN_HANDLED
10
11 #include "SDL_endian.h"
12
13 #include <cstdio>
14 #include <cstring>
15 #include <string>
16 #include <vector>
17 #include <ctime>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <cerrno>
21 #include <regex>
22 #include <algorithm>
23 #include <iostream>
24 #include <sstream>
25 #include <iomanip>
26
27 #ifdef PLAT_UNIX
28 #include <dirent.h>
29 #endif
30
31 // from cfile.h
32 #define CF_MAX_FILENAME_LENGTH  32      // Includes null terminater, so real length is 31
33
34 extern void help();
35
36
37 class vp {
38         public:
39                 enum action_t {
40                         INVALID = 0,
41                         LIST,
42                         CREATE,
43                         EXTRACT
44                 };
45
46                 vp();
47                 ~vp();
48
49                 void setFilename(const char *val);
50                 void setOutputDirectory(const char *val);
51                 void setSourceDirectory(const char *val);
52                 void setLowerCase() { m_lower_case = true; }
53                 void setAction(action_t val) { m_action = val; }
54                 void setRegex(const char *val);
55
56                 action_t getAction() { return m_action; }
57
58                 void list();
59                 void create();
60                 void extract();
61
62         private:
63                 typedef struct vp_header {
64                         int32_t id;                             // 0x50565056 : 'VPVP' (little endian)
65                         int32_t version;
66                         int32_t index_offset;   // offset where the file index starts and total file data size
67                         int32_t num_files;              // number of files, including each step in directory structure
68                 } vp_header;
69
70                 typedef struct vp_fileindex {
71                         // part of VP
72                         int32_t offset;
73                         int32_t file_size;
74                         int32_t write_time;
75                         std::string file_name;
76
77                         // *not* part of VP, only used here
78                         std::string file_path;
79                 } vp_fileindex;
80
81                 const int32_t VP_VERSION = 2;
82                 const int32_t VP_ID = 0x50565056;
83                 const int32_t VP_HEADER_SIZE = 16;
84
85                 FILE *archive;
86
87                 void Seek(int32_t offset, int where);
88                 int32_t ReadInt();
89                 char *ReadString(char *buf, const size_t length);
90                 void WriteInt(const int32_t val);
91                 void WriteString(const std::string &buf, const size_t length);
92
93                 bool CreatePath(const std::string &path);
94
95                 void pack_directory(const std::string *pack_dir = nullptr);
96                 void add_directory(const std::string &a_dir);
97                 void add_file(const std::string &a_file, const long fsize, const time_t ftime);
98
99                 vp_header m_header;
100                 std::vector<vp_fileindex> m_index;
101
102                 void read_header();
103                 void read_index();
104
105                 void write_header();
106                 void write_index();
107
108                 std::string m_filename;
109                 std::string m_outdir;
110                 std::string m_sourcedir;
111                 bool m_lower_case;
112                 bool m_filtering;
113                 std::regex m_regex;
114
115                 action_t m_action;
116 };
117
118 void vp::Seek(int32_t offset, int where)
119 {
120         int rval = fseek(archive, offset, where);
121
122         if (rval) {
123                 if (errno == EINVAL) {
124                         throw std::runtime_error("invalid value in Seek()");
125                 } else {
126                         throw std::runtime_error("stream error in Seek()");
127                 }
128         }
129 }
130
131 int32_t vp::ReadInt()
132 {
133         int32_t result = -1;
134
135         size_t rval = fread(&result, 1, sizeof(int32_t), archive);
136
137         if (rval != sizeof(int32_t)) {
138                 throw std::runtime_error("short read in ReadInt()");
139         }
140
141         return SDL_SwapLE32(result);
142 }
143
144 char *vp::ReadString(char *buf, const size_t length)
145 {
146         size_t rval = fread(buf, 1, length, archive);
147
148         if (rval != length) {
149                 throw std::runtime_error("short read in ReadString()");
150         }
151
152         return buf;
153 }
154
155 void vp::WriteInt(const int32_t val)
156 {
157         int32_t value = SDL_SwapLE32(val);
158
159         size_t rval = fwrite(&value, 1, sizeof(int32_t), archive);
160
161         if (rval != sizeof(int32_t)) {
162                 throw std::runtime_error("short write in WriteInt()");
163         }
164 }
165
166 void vp::WriteString(const std::string &buf, const size_t length)
167 {
168         if (buf.size() > length) {
169                 throw std::runtime_error("string length greater than write length in WriteString()");
170         }
171
172         size_t rval = fwrite(buf.c_str(), 1, buf.size(), archive);
173
174         if (rval != buf.size()) {
175                 throw std::runtime_error("short write in WriteString()");
176         }
177
178         // we need to write the full 'length', so zero fill any extra space
179         size_t fill = length - buf.size();
180
181         if (fill) {
182                 const char zero = '\0';
183                 rval = 0;
184
185                 for (size_t idx = 0; idx < fill; idx++) {
186                         rval += fwrite(&zero, 1, 1, archive);
187                 }
188
189                 if (rval != fill) {
190                         throw std::runtime_error("short fill write in WriteString()");
191                 }
192         }
193 }
194
195 bool vp::CreatePath(const std::string &path)
196 {
197         std::string sub_path;
198         std::string::size_type pos;
199
200         pos = path.find('/', 1);
201
202         while (pos != std::string::npos) {
203                 sub_path = path.substr(0, pos);
204
205                 int status = mkdir(sub_path.c_str(), 0755);
206
207                 if (status && (errno != EEXIST)) {
208                         return false;
209                 }
210
211                 pos = path.find('/', pos+1);
212         }
213
214         return true;
215 }
216
217 void vp::read_header()
218 {
219         if (archive == nullptr) {
220                 if ( m_filename.empty() ) {
221                         throw std::runtime_error("empty filename passed to vp::read_header()");
222                 }
223
224                 archive = fopen(m_filename.c_str(), "rb");
225
226                 if (archive == nullptr) {
227                         int err = errno;
228                         std::ostringstream errmsg;
229
230                         errmsg << "error opening '" << m_filename << "': " << std::strerror(err);
231                         throw std::runtime_error(errmsg.str());
232                 }
233         }
234
235         Seek(0, SEEK_SET);
236
237         m_header.id = ReadInt();
238         m_header.version = ReadInt();
239         m_header.index_offset = ReadInt();
240         m_header.num_files = ReadInt();
241
242         if (m_header.id != vp::VP_ID) {
243                 throw std::runtime_error("invalid VP ID");
244         }
245
246         if (m_header.version != vp::VP_VERSION) {
247                 throw std::runtime_error("invalid VP version");
248         }
249 }
250
251 void vp::write_header()
252 {
253         if (archive == nullptr) {
254                 if ( m_filename.empty() ) {
255                         throw std::runtime_error("empty filename passed to vp::write_header()");
256                 }
257
258                 archive = fopen(m_filename.c_str(), "wb");
259
260                 if (archive == nullptr) {
261                         int err = errno;
262                         std::ostringstream errmsg;
263
264                         errmsg << "error opening '" << m_filename << "': " << std::strerror(err);
265                         throw std::runtime_error(errmsg.str());
266                 }
267         }
268
269         Seek(0, SEEK_SET);
270
271         WriteInt(vp::VP_ID);
272         WriteInt(vp::VP_VERSION);
273         WriteInt(m_header.index_offset);
274         WriteInt(m_header.num_files);
275 }
276
277 void vp::read_index()
278 {
279         Seek(m_header.index_offset, SEEK_SET);
280
281         vp_fileindex vpinfo;
282         char filename_tmp[CF_MAX_FILENAME_LENGTH];
283         std::string path;
284
285         memset(filename_tmp, 0, sizeof(filename_tmp));
286
287         // pre-allocate max size
288         m_index.reserve(static_cast<size_t>(m_header.num_files));
289
290         for (int i = 0; i < m_header.num_files; i++) {
291                 vpinfo.offset = ReadInt();
292                 vpinfo.file_size = ReadInt();
293                 vpinfo.file_name = ReadString(filename_tmp, CF_MAX_FILENAME_LENGTH);
294                 vpinfo.write_time = ReadInt();
295
296                 if (m_lower_case) {
297                         std::transform(vpinfo.file_name.begin(), vpinfo.file_name.end(),
298                                                    vpinfo.file_name.begin(), ::tolower);
299                 }
300
301                 // check if it's a directory and if so then create a path to use for files
302                 if (vpinfo.file_size == 0) {
303                         // if we get a ".." then drop down in the path
304                         if ( !vpinfo.file_name.compare("..") ) {
305                                 std::string::size_type pos = path.find_last_of('/');
306
307                                 if (pos != std::string::npos) {
308                                         path.erase(pos);
309                                 }
310                         }
311                         // otherwise add it to the path
312                         else {
313                                 if ( !path.empty() ) {
314                                         path.push_back('/');
315                                 }
316
317                                 path.append(vpinfo.file_name);
318                         }
319                 }
320                 // otherwise it's a file
321                 else {
322                         vpinfo.file_path = path;
323
324                         m_index.push_back(vpinfo);
325                 }
326         }
327 }
328
329 void vp::write_index()
330 {
331         // index is appended to the end
332         Seek(0, SEEK_END);
333
334         std::vector<vp_fileindex>::iterator it;
335
336         for (it = m_index.begin(); it != m_index.end(); ++it) {
337                 WriteInt(it->offset);
338                 WriteInt(it->file_size);
339                 WriteString(it->file_name, CF_MAX_FILENAME_LENGTH);
340                 WriteInt(it->write_time);
341         }
342 }
343
344 void vp::pack_directory(const std::string *pack_dir)
345 {
346         DIR *dirp;
347         struct dirent *dir;
348         struct stat buf;
349         std::string source_path, path;
350
351         if (pack_dir != nullptr) {
352                 source_path = *pack_dir;
353         } else {
354                 source_path = m_sourcedir;
355         }
356
357         dirp = opendir(source_path.c_str());
358
359         if (dirp == nullptr) {
360                 int err = errno;
361                 std::ostringstream errmsg;
362
363                 errmsg << "error opening path '" << source_path << "': " << std::strerror(err);
364                 throw std::runtime_error(errmsg.str());
365         }
366
367         std::cout << "  Scanning '" << source_path << "' ...\n";
368
369         add_directory(source_path);
370
371         while ( (dir = readdir(dirp)) != nullptr ) {
372                 std::string name = dir->d_name;
373
374                 if ( !name.compare(".") || !name.compare("..") ) {
375                         continue;
376                 }
377
378                 if (name.length() >= CF_MAX_FILENAME_LENGTH) {
379                         size_t half_len = std::min(name.length() / 2, static_cast<size_t>(12));
380                         std::string first_half = name.substr(0, half_len);
381                         std::string last_half = name.substr(name.length() - half_len);
382
383                         std::cout << "  Skipping '" << source_path << "/" << first_half
384                                           << "..." << last_half << "' ... Name too long (> "
385                                           << CF_MAX_FILENAME_LENGTH-1 << " characters)\n";
386                         continue;
387                 } else if (name.length() > 2) {
388                         std::string ext = name.substr(name.length()-3);
389                         std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
390
391                         if ( !ext.compare(".vp") ) {
392                                 continue;
393                         }
394                 }
395
396                 path = source_path;
397
398                 path.push_back('/');
399                 path.append(dir->d_name);
400
401                 if ( stat(path.c_str(), &buf) == -1 ) {
402                         continue;
403                 }
404
405                 if (buf.st_size > INT32_MAX) {
406                         std::cout << "  Skipping '" << source_path << "/" << name
407                                           << "' ... Size is too large (> " << INT32_MAX << " bytes)\n";
408                         continue;
409                 }
410
411                 if ( S_ISDIR(buf.st_mode) ) {
412                         // recurse into new directory
413                         pack_directory(&path);
414                 } else {
415                         if (buf.st_size > 0) {
416                                 add_file(path, buf.st_size, buf.st_mtime);
417                         }
418                 }
419         }
420
421         closedir(dirp);
422
423         // add a final directory root
424         add_directory(std::string(".."));
425 }
426
427 void vp::add_directory(const std::string &a_dir)
428 {
429         vp_fileindex vpinfo;
430
431         vpinfo.offset = static_cast<int32_t>(ftell(archive));
432         vpinfo.file_size = 0;
433         vpinfo.write_time = 0;
434         vpinfo.file_name = a_dir;
435
436         // stip extra path from directory name
437         std::string::size_type pos = vpinfo.file_name.find_last_of('/');
438
439         if (pos != std::string::npos) {
440                 vpinfo.file_name = vpinfo.file_name.substr(pos+1);
441         }
442
443         m_index.push_back(vpinfo);
444 }
445
446 void vp::add_file(const std::string &a_file, const long fsize, const time_t ftime)
447 {
448         vp_fileindex vpinfo;
449
450         // add file info to index...
451         vpinfo.offset = static_cast<int32_t>(ftell(archive));
452         vpinfo.file_size = static_cast<int32_t>(fsize);
453         vpinfo.write_time = static_cast<int32_t>(ftime);
454         vpinfo.file_name = a_file;
455
456         // strip extra path from file name
457         std::string::size_type pos = vpinfo.file_name.find_last_of('/');
458
459         if (pos != std::string::npos) {
460                 vpinfo.file_name = vpinfo.file_name.substr(pos+1);
461         }
462
463         m_index.push_back(vpinfo);
464
465         // add the file data to archive...
466         FILE *infile = fopen(a_file.c_str(), "rb");
467
468         if (infile == nullptr) {
469                 int err = errno;
470                 std::ostringstream errmsg;
471
472                 errmsg << "error opening input file '" << a_file << "': " << std::strerror(err);
473                 throw std::runtime_error(errmsg.str());
474         }
475
476         std::cout << "    Adding '" << a_file << "' ... ";
477
478         const size_t BLOCK_SIZE = 1024*1024;
479
480         char data_block[BLOCK_SIZE];
481         size_t total_bytes = 0, num_bytes;
482
483         do {
484                 num_bytes = fread(data_block, 1, BLOCK_SIZE, infile);
485
486                 if (num_bytes > 0) {
487                         fwrite(data_block, 1, num_bytes, archive);
488                         total_bytes += num_bytes;
489                 }
490         } while (num_bytes > 0);
491
492         fclose(infile);
493
494         std::cout << total_bytes << " bytes\n";
495 }
496
497 vp::vp()
498 {
499         archive = nullptr;
500         m_lower_case = false;
501         m_filtering = false;
502         m_action = INVALID;
503
504         m_header.index_offset = VP_HEADER_SIZE; // size of vp_header (4 * sizeof(int32_t))
505         m_header.num_files = 0;
506 }
507
508 vp::~vp()
509 {
510         if (archive != nullptr) {
511                 fclose(archive);
512                 archive = nullptr;
513         }
514 }
515
516 void vp::setFilename(const char *val)
517 {
518         if ( !val ) {
519                 return;
520         }
521
522         m_filename = val;
523 }
524
525 void vp::setOutputDirectory(const char *val)
526 {
527         if ( !val ) {
528                 return;
529         }
530
531         m_outdir = val;
532
533         if (m_outdir.back() == '/') {
534                 m_outdir.pop_back();
535         }
536 }
537
538 void vp::setSourceDirectory(const char *val)
539 {
540         if ( !val ) {
541                 return;
542         }
543
544         m_sourcedir = val;
545
546         if (m_sourcedir.back() == '/') {
547                 m_sourcedir.pop_back();
548         }
549
550         // make sure that it's named 'data', case insensitive
551         std::string is_data;
552         std::string::size_type pos = m_sourcedir.find_last_of('/');
553
554         if (pos != std::string::npos) {
555                 is_data = m_sourcedir.substr(pos+1);
556         } else {
557                 is_data = m_sourcedir;
558         }
559
560         std::transform(is_data.begin(), is_data.end(), is_data.begin(), ::tolower);
561
562         if (is_data.compare("data") ) {
563                 throw std::runtime_error("source directory must be named 'data'");
564         }
565 }
566
567 void vp::setRegex(const char *val)
568 {
569         if ( !val ) {
570                 return;
571         }
572
573         try {
574                 m_regex = val;
575         } catch (const std::regex_error &e) {
576                 std::cout << "regex validation failure: " << e.what() << std::endl;
577         }
578
579         m_filtering = true;
580 }
581
582 void vp::list()
583 {
584         if (getAction() != vp::LIST) {
585                 return;
586         }
587
588         read_header();
589         read_index();
590
591         std::cout << "VP file archiver/extractor - version 1.0\n";
592         std::cout << std::endl;
593         std::cout << m_filename << " ...\n";
594         std::cout << std::endl;
595
596         std::cout << "   Size       Date      Time    Name\n";
597         std::cout << "---------- ---------- --------  ------------------------\n";
598
599         //char time_str[30];
600         time_t write_time;
601         std::vector<vp_fileindex>::iterator it;
602         std::string rel_path;
603         std::smatch match;
604         size_t matching_count = 0;
605
606         for (it = m_index.begin(); it != m_index.end(); ++it) {
607                 write_time = it->write_time;
608                 rel_path = it->file_path;
609
610                 rel_path.push_back('/');
611                 rel_path.append(it->file_name);
612
613                 if ( m_filtering && !std::regex_search(rel_path, match, m_regex) ) {
614                         continue;
615                 }
616
617                 ++matching_count;
618
619                 std::cout << std::setw(10) << it->file_size << " "
620                                   << std::put_time(std::gmtime(&write_time), "%F %H:%M:%S")
621                                   << "  " << rel_path << std::endl;
622         }
623
624         std::cout << "---------- ---------- --------  ------------------------\n";
625         std::cout << std::setw(10) << m_header.index_offset-VP_HEADER_SIZE;
626
627         if (m_filtering) {
628                 std::cout << "                      " << matching_count << " of " << m_index.size() << " file(s)\n";
629         } else {
630                 std::cout << "                      " << m_index.size() << " file(s)\n";
631         }
632
633         std::cout << std::endl;
634 }
635
636 void vp::create()
637 {
638         if (getAction() != vp::CREATE) {
639                 return;
640         }
641
642         std::string ext;
643
644         if (m_filename.length() > 3) {
645                 ext = m_filename.substr(m_filename.length()-3);
646                 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
647         }
648
649         if ( ext.compare(".vp") ) {
650                 m_filename.append(".vp");
651         }
652
653         std::cout << "VP file archiver/extractor - version 1.0\n";
654         std::cout << std::endl;
655         std::cout << "Creating archive '" << m_filename << "' ...\n";
656         std::cout << std::endl;
657
658         // TODO: verify directory
659
660         // write header place-holder
661         write_header();
662
663         // do files
664         pack_directory();
665
666         // update header with proper values
667         Seek(0, SEEK_END);
668
669         m_header.index_offset = static_cast<int32_t>(ftell(archive));
670         m_header.num_files = static_cast<int32_t>(m_index.size());
671
672         write_header();
673
674         // finalize with file index and summary
675         write_index();
676
677         size_t total_files = 0;
678         std::vector<vp_fileindex>::iterator it;
679
680         for (it = m_index.begin(); it != m_index.end(); ++it) {
681                 if (it->file_size > 0) {
682                         ++total_files;
683                 }
684         }
685
686         Seek(0, SEEK_END);
687
688         std::cout << std::endl;
689         std::cout << "  Added " << total_files << " files\n";
690         std::cout << "  Total size: " << ftell(archive) << " bytes\n";
691         std::cout << std::endl;
692 }
693
694 void vp::extract()
695 {
696         if (getAction() != vp::EXTRACT) {
697                 return;
698         }
699
700         read_header();
701         read_index();
702
703         std::cout << "VP file archiver/extractor - version 1.0\n";
704         std::cout << std::endl;
705
706         if ( !m_outdir.empty() ) {
707                 std::cout << "Output directory: " << m_outdir << std::endl << std::endl;
708         }
709
710         std::cout << "Extracting '" << m_filename << "' ...\n";
711
712         const size_t BLOCK_SIZE = 1024*1024;
713
714         std::vector<vp_fileindex>::iterator it;
715         std::string path, rel_path;
716         FILE *outfile;
717         int32_t bytes_remaining;
718         size_t rval;
719         char data_block[BLOCK_SIZE];
720         size_t ex_count = 0;
721         std::smatch match;
722
723         for (it = m_index.begin(); it != m_index.end(); ++it) {
724                 rel_path = it->file_path;
725
726                 rel_path.push_back('/');
727                 rel_path.append(it->file_name);
728
729                 if ( m_filtering && !std::regex_search(rel_path, match, m_regex) ) {
730                         continue;
731                 }
732
733                 std::cout << "  " << rel_path << " ... ";
734
735                 path = m_outdir;
736
737                 if ( !m_outdir.empty() ) {
738                         path.push_back('/');
739                 }
740
741                 path.append(rel_path);
742
743                 if ( !CreatePath(path) ) {
744                         std::cout << "ERROR: cannot create path!\n";
745                         continue;
746                 }
747
748                 outfile = fopen(path.c_str(), "wb");
749
750                 if (outfile == nullptr) {
751                         std::cout << "ERROR: cannot create file!\n";
752                         continue;
753                 }
754
755                 Seek(it->offset, SEEK_SET);
756
757                 bytes_remaining = it->file_size;
758
759                 while (bytes_remaining > 0) {
760                         rval = fread(data_block, 1, std::min(BLOCK_SIZE, static_cast<size_t>(bytes_remaining)), archive);
761
762                         if (rval > 0) {
763                                 fwrite(data_block, 1, rval, outfile);
764                                 bytes_remaining -= rval;
765                         }
766                 }
767
768                 fclose(outfile);
769
770                 ++ex_count;
771
772                 std::cout << "done!\n";
773         }
774
775         std::cout << std::endl;
776         std::cout << "  " << ex_count << (m_filtering ? " matching" : "") << " files extracted\n";
777         std::cout << std::endl;
778 }
779
780 static vp VP;
781
782
783 void help()
784 {
785         std::cout << "VP file archiver/extractor - version 1.0\n";
786         std::cout << std::endl;
787         std::cout << "Usage: cfileutil c <vp_filename> <source_dir>\n";
788         std::cout << "       cfileutil x [-L] [-o <dir>] [-f <regex>] <vp_filename>\n";
789         std::cout << "       cfileutil l [-f <regex>] <vp_filename>\n";
790         std::cout << std::endl;
791         std::cout << " Commands:\n";
792         std::cout << "  c           Create VP archive from <source_dir>\n";
793         std::cout << "  x           Extract all files into current directory\n";
794         std::cout << "  l           List all files in VP archive\n";
795         std::cout << std::endl;
796         std::cout << " Extraction options:\n";
797         std::cout << "  -L          Force all directory and file names to be lower case\n";
798         std::cout << "  -o <dir>    Extract into <dir> rather than current directory\n";
799         std::cout << "  -f <regex>  Only extract files matching regex\n";
800         std::cout << std::endl;
801         std::cout << " List options:\n";
802         std::cout << "  -f <regex>  Only list files matching regex\n";
803         std::cout << std::endl;
804
805         exit(EXIT_SUCCESS);
806 }
807
808 #define MAYBE_HELP(x) if (argc - idx < (x)) { help(); }
809
810 void parse_args(int argc, char *argv[])
811 {
812         for (int idx = 1; idx < argc; idx++) {
813                 if ( !std::strcmp(argv[idx], "-h") || !std::strcmp(argv[idx], "--help") ) {
814                         help();
815                 } else if ( !std::strcmp(argv[idx], "c") ) {
816                         idx++;
817
818                         MAYBE_HELP(2);
819
820                         if (VP.getAction() != vp::INVALID) {
821                                 help();
822                         }
823
824                         VP.setFilename(argv[idx++]);
825                         VP.setSourceDirectory(argv[idx]);
826                         VP.setAction(vp::CREATE);
827                 } else if ( !std::strcmp(argv[idx], "l") ) {
828                         idx++;
829
830                         MAYBE_HELP(1);
831
832                         if (VP.getAction() != vp::INVALID) {
833                                 help();
834                         }
835
836                         for ( ; idx < argc; idx++) {
837                                 if ( !std::strcmp(argv[idx], "-f") ) {
838                                         idx++;
839
840                                         MAYBE_HELP(1);
841
842                                         VP.setRegex(argv[idx]);
843                                 } else {
844                                         VP.setFilename(argv[idx]);
845                                         VP.setAction(vp::LIST);
846                                 }
847                         }
848                 } else if ( !std::strcmp(argv[idx], "x") ) {
849                         idx++;
850
851                         MAYBE_HELP(1);
852
853                         if (VP.getAction() != vp::INVALID) {
854                                 help();
855                         }
856
857                         for ( ; idx < argc; idx++) {
858                                 if ( !std::strcmp(argv[idx], "-L") ) {
859                                         MAYBE_HELP(1);
860
861                                         VP.setLowerCase();
862                                 } else if ( !std::strcmp(argv[idx], "-o") ) {
863                                         idx++;
864
865                                         MAYBE_HELP(1);
866
867                                         VP.setOutputDirectory(argv[idx]);
868                                 } else if ( !std::strcmp(argv[idx], "-f") ) {
869                                         idx++;
870
871                                         MAYBE_HELP(1);
872
873                                         VP.setRegex(argv[idx]);
874                                 } else {
875                                         VP.setFilename(argv[idx]);
876                                         VP.setAction(vp::EXTRACT);
877                                 }
878                         }
879                 } else {
880                         help();
881                 }
882         }
883 }
884
885 extern "C"
886 int main(int argc, char *argv[])
887 {
888         if (argc < 3) {
889                 help();
890         }
891
892         try {
893                 parse_args(argc, argv);
894         } catch (const std::regex_error &e) {
895                 std::cerr << "Error validating regex => " << e.what() << std::endl;
896                 return EXIT_FAILURE;
897         } catch (const std::exception &e) {
898                 std::cerr << "Error parsing arguments => " << e.what() << std::endl;
899                 return EXIT_FAILURE;
900         }
901
902         switch ( VP.getAction() ) {
903                 case vp::LIST: {
904                         try {
905                                 VP.list();
906                         } catch (const std::exception &e) {
907                                 std::cerr << "Error listing archive => " << e.what() << std::endl;
908                                 return EXIT_FAILURE;
909                         }
910
911                         break;
912                 }
913
914                 case vp::CREATE: {
915                         try {
916                                 VP.create();
917                         } catch (const std::exception &e) {
918                                 std::cerr << "Error creating archive => " << e.what() << std::endl;
919                                 return EXIT_FAILURE;
920                         }
921
922                         break;
923                 }
924
925                 case vp::EXTRACT: {
926                         try {
927                                 VP.extract();
928                         } catch(const std::exception &e) {
929                                 std::cerr << "Error extracting archive => " << e.what() << std::endl;
930                                 return EXIT_FAILURE;
931                         }
932
933                         break;
934                 }
935
936                 default:
937                         help();
938                         break;
939         }
940
941         return EXIT_SUCCESS;
942 }