FS2_Open
Open source remastering of the Freespace 2 engine
cfileextractor.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2005 Taylor Richards
3  *
4 */
5 
6 
7 
8 #include "cfile/cfile.h"
9 #include "globalincs/pstypes.h"
10 
11 #include <vector>
12 #include <iostream>
13 #include <cstdlib>
14 #include <cstdio>
15 #include <cstring>
16 #include <ctime>
17 
18 #ifdef _WIN32
19 #include <direct.h>
20 #else
21 #include <unistd.h>
22 #endif
23 
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <errno.h>
27 
28 
29 // /////////////////////////////////////////////////////////////////////////////
30 //
31 // GLOBAL FUNCTIONS AND VARIABLES
32 //
33 
34 // right out of pstypes.h but we don't want to use the INTEL_INT macro here
35 // since it would require SDL which isn't used on WIN32 platforms
36 #if BYTE_ORDER == BIG_ENDIAN
37 #define INT_SWAP(x) ( \
38  (x << 24) | \
39  (((ulong)x) >> 24) | \
40  ((x & 0x0000ff00) << 8) | \
41  ((x & 0x00ff0000) >> 8) \
42  )
43 #else
44 #define INT_SWAP(x) (x)
45 #endif
46 
47 FILE *fp_in = NULL;
48 FILE *fp_out = NULL;
49 
50 #ifndef MAX_PATH
51 #define MAX_PATH 255
52 #endif
53 
55 
56 #define BLOCK_SIZE (1024*1024)
57 
58 char tmp_data[BLOCK_SIZE]; // 1 MB
59 
60 static int have_index = 0;
61 static int have_header = 0;
62 
63 #define ERR_NO_FP_IN 0
64 #define ERR_NO_FP_OUT 1
65 #define ERR_INVALID_VP 2
66 #define ERR_NO_INDEX 3
67 #define ERR_NO_HEADER 4
68 #define ERR_PATH_TOO_LONG 5
69 
70 typedef struct vp_header {
71  char id[4]; // 'VPVP'
72  int version;
73  int index_offset; // offset where the file index starts, also a total file data size count
74  int num_files; // number of files, including each step in the directory structure
75 } vp_header;
76 
78 
79 typedef struct vp_fileinfo {
80  // stuff that is actually written to the VP
81  int offset; // offset of where this file is in the VP
82  int file_size; // size of file
83  char file_name[CF_MAX_FILENAME_LENGTH]; // filename
84  _fs_time_t write_time; // date/time in _fs_time_t type (a 32-bit version of time_t)
85 
86  // not in the VP
87  char file_path[CF_MAX_PATHNAME_LENGTH]; // file path, generated here and not actually in the VP on a per file basis
88 
90  offset = 0;
91  file_size = 0;
92  file_name[0] = '\0';
93  file_path[0] = '\0';
94  write_time = 0;
95  }
96 
98 } vp_fileinfo;
99 
101 
102 // just like strlwr() but without actually including it here
103 void lowercase(char *s)
104 {
105  if (s == NULL)
106  return;
107 
108  while (*s) {
109  *s = tolower(*s);
110  s++;
111  }
112 }
113 
114 //
115 // /////////////////////////////////////////////////////////////////////////////
116 
117 // /////////////////////////////////////////////////////////////////////////////
118 //
119 // The useful stuff...
120 //
121 
122 void print_error(int err)
123 {
124  switch (err) {
125  case ERR_NO_FP_IN:
126  printf("ERROR: The input file (fp_in) is NULL! Exiting...\n");
127  break;
128  case ERR_NO_FP_OUT:
129  printf("ERROR: The output file (fp_out) is NULL! Exiting...\n");
130  break;
131  case ERR_INVALID_VP:
132  printf("ERROR: The specified VP file is invalid! Exiting...\n");
133  break;
134  case ERR_NO_INDEX:
135  printf("ERROR: No file index is available! Exiting...\n");
136  break;
137  case ERR_NO_HEADER:
138  printf("ERROR: No file header is available! Exiting...\n");
139  break;
140  case ERR_PATH_TOO_LONG:
141  printf("ERROR: Path to output directory is too long! Exiting...\n");
142  break;
143  default:
144  break;
145  }
146 
147  exit(1);
148 }
149 
151 {
152  fseek(fp_in, 0, SEEK_SET);
153  fread(&VP_Header.id, 1, 4, fp_in);
154  fread(&VP_Header.version, 1, sizeof(int), fp_in);
155  fread(&VP_Header.index_offset, 1, sizeof(int), fp_in);
156  fread(&VP_Header.num_files, 1, sizeof(int), fp_in);
157 
158  VP_Header.version = INT_SWAP(VP_Header.version);
159  VP_Header.index_offset = INT_SWAP(VP_Header.index_offset);
160  VP_Header.num_files = INT_SWAP(VP_Header.num_files);
161 
162  // check for a valid id (VPVP)
163  if ( (VP_Header.id[0] != 'V') && (VP_Header.id[1] != 'P') && (VP_Header.id[2] != 'V') && (VP_Header.id[3] != 'P') )
165 
166  // the index_offset needs to be greater than the size of vp_header at the very least and there should be at least one file
167  if ( !(VP_Header.index_offset > (int)sizeof(vp_header)) || !(VP_Header.num_files > 1) )
169 
170 
171  have_header = 1;
172 }
173 
174 void read_index(int lc = 0)
175 {
176  if (fp_in == NULL)
178  else if (!have_header)
180 
181  int i;
183 
184  fseek(fp_in, VP_Header.index_offset, SEEK_SET);
185 
186  memset(path, 0, CF_MAX_PATHNAME_LENGTH);
187 
188  for ( i = 0; i < VP_Header.num_files; i++) {
189  vp_fileinfo vpinfo;
190 
191  fread(&vpinfo.offset, 1, sizeof(int), fp_in);
192  fread(&vpinfo.file_size, 1, sizeof(int), fp_in);
193  fread(&vpinfo.file_name, 1, CF_MAX_FILENAME_LENGTH, fp_in);
194  fread(&vpinfo.write_time, 1, sizeof(_fs_time_t), fp_in);
195 
196  vpinfo.offset = INT_SWAP(vpinfo.offset);
197  vpinfo.file_size = INT_SWAP(vpinfo.file_size);
198  vpinfo.write_time = INT_SWAP(vpinfo.write_time);
199 
200  // check if it's a directory and if so then create a path to use for files
201  if (vpinfo.file_size == 0) {
202  // if we get a ".." then drop down in the path
203  if (!strcmp(vpinfo.file_name, "..")) {
204  char *s = strrchr(path, DIR_SEPARATOR_CHAR);
205 
206  if (s)
207  *s = '\0';
208 
209  // skip, we don't want to add the ".." to the path
210  continue;
211  }
212 
213  // don't add the very first directory separator character, easier to clean later
214  if (path[0] != 0)
215  strcat( path, DIR_SEPARATOR_STR );
216 
217  // always make directory names lower case for ease of use
218  lowercase( vpinfo.file_name );
219 
220  strcat( path, vpinfo.file_name );
221  } // it's a file then
222  else {
223  // lowercase the filename if wanted
224  if (lc == 1)
225  lowercase( vpinfo.file_name );
226 
227  strcpy_s( vpinfo.file_path, path );
228  VP_FileInfo.push_back(vpinfo);
229  }
230  }
231 
232  have_index = 1;
233 }
234 
235 void extract_all_files(char *file)
236 {
237  if (fp_in == NULL)
239  else if (!have_header)
241  else if (!have_index)
243 
244  int status, m_error, nbytes, nbytes_remaining;
245  char path[CF_MAX_PATHNAME_LENGTH+CF_MAX_FILENAME_LENGTH+1]; // path length + filename length + extra NULL
246  char path2[CF_MAX_PATHNAME_LENGTH+CF_MAX_FILENAME_LENGTH+1]; // path length + filename length + extra NULL
247  char *c;
248  ubyte have_outdir = 0;
249 
250  int out_len = strlen(out_dir);
251 
252  if (out_len > 0)
253  have_outdir = 1;
254 
255  printf("VP file extractor - version 0.6\n");
256  printf("\n");
257 
258  if (have_outdir) {
259  printf("Output directory: \"%s\"\n", out_dir);
260  }
261 
262  printf("Extracting: %s...\n", file);
263 
264  for (size_t i = 0; i < VP_FileInfo.size(); i++) {
265  // save the file path to a temp location and recursively make the needed directories
266  if (have_outdir) {
267  if ( (out_len + 1 + strlen(path)) > MAX_PATH-1 )
269 
270  sprintf(path, "%s%s%s%s", out_dir, DIR_SEPARATOR_STR, VP_FileInfo[i].file_path, DIR_SEPARATOR_STR);
271  } else {
272  sprintf(path, "%s%s", VP_FileInfo[i].file_path, DIR_SEPARATOR_STR);
273  }
274 
275  c = &path[0];
276 
277  while (c++) {
278  c = strchr(c, DIR_SEPARATOR_CHAR);
279 
280  if (c) {
281  *c = '\0'; // NULL at DIR_SEP char
282 
283 #ifdef _WIN32
284  status = _mkdir(path);
285 #else
286  status = mkdir(path, 0777);
287 #endif
288 
289  m_error = errno;
290 
291  if (status && (m_error != EEXIST) ) {
292  printf("Cannot mkdir %s: %s\n", VP_FileInfo[i].file_path, strerror(m_error));
293  continue;
294  }
295 
296  *c = DIR_SEPARATOR_CHAR; // replace DIR_SEP char
297  }
298  }
299 
301 
302  // start writing out the file
303  fseek(fp_in, VP_FileInfo[i].offset, SEEK_SET);
304 
305  sprintf(path, "%s%s%s", VP_FileInfo[i].file_path, DIR_SEPARATOR_STR, VP_FileInfo[i].file_name);
306 
307  // this is cheap, I know.
308  if (have_outdir) {
309  if ( (out_len + 1 + strlen(path)) > MAX_PATH-1 )
311 
312  sprintf(path2, "%s%s%s", out_dir, DIR_SEPARATOR_STR, path);
313  } else {
314  strcpy_s(path2, path);
315  }
316 
317  fp_out = fopen(path2, "wb");
318 
319  printf(" %s ... ", path);
320 
321  if (fp_out == NULL) {
322  printf("can't create file!\n");
323  continue;
324  }
325 
326  nbytes_remaining = VP_FileInfo[i].file_size;
327 
328  while ( nbytes_remaining > 0 ) {
329  nbytes = fread( tmp_data, 1, MIN(BLOCK_SIZE, nbytes_remaining), fp_in );
330 
331  if (nbytes > 0) {
332  fwrite( tmp_data, 1, nbytes, fp_out );
333  nbytes_remaining -= nbytes;
334  }
335  }
336 
337  printf("done!\n");
338 
339  fclose(fp_out);
340  fp_out = NULL;
341  }
342 
343  printf("\n");
344 }
345 
346 // TODO: the formatting of this is a bit loco but I don't care enough to make it better...
347 void list_all_files(char *file)
348 {
349  char out_time[20];
350  float one_k = 1024.0f;
351  float one_m = 1048576.0f;
352  char m;
353  float div_by;
354  time_t plat_time;
355 
356  // if we don't have an index then bail
357  if (!have_index)
359 
360 
361  printf("VP file extractor - version 0.6\n");
362  printf("\n");
363  printf("%s:\n", file);
364  printf("\n");
365 
366  printf(" Name Size Offset Date/Time Path\n");
367  printf("-------------------------------------------------------------------------------\n");
368  for (size_t i = 0; i < VP_FileInfo.size(); i++) {
369  plat_time = VP_FileInfo[i].write_time; // gets rid of some platform strangeness this way
370  strftime(out_time, 32, "%F %H:%M", localtime(&plat_time)); // YYYY-mm-dd HH:mm (ISO 8601 date format, 24-hr time)
371 
372  if (VP_FileInfo[i].file_size > (int)one_m) {
373  m = 'M';
374  div_by = one_m;
375  } else {
376  m = 'K';
377  div_by = one_k;
378  }
379 
380  int len = strlen( VP_FileInfo[i].file_name );
381  printf("%s %*.1f%c %10i %18s %3s%s\n", VP_FileInfo[i].file_name, 33 - len, (float)VP_FileInfo[i].file_size/div_by, m, VP_FileInfo[i].offset, out_time, DIR_SEPARATOR_STR, VP_FileInfo[i].file_path);
382  }
383 
384  printf("\n");
385 
386  // we use the vector size here since VP_Header.num_files would include each entry
387  // in the directory tree as well as individual files and that artificially inflates
388  // the number of files that we show or would extract
389  printf("Total files: %i\n", (int)VP_FileInfo.size());
390 
391  // yeah, I'm just that cheap
392  if (VP_Header.index_offset > (int)one_m) {
393  m = 'M';
394  div_by = one_m;
395  } else {
396  m = 'K';
397  div_by = one_k;
398  }
399 
400  printf("Total size: %.1f%c\n", (float)VP_Header.index_offset/div_by, m);
401 
402  printf("\n");
403 }
404 
405 void help()
406 {
407  printf("VP file extractor - version 0.6\n");
408  printf("\n");
409  printf("Usage: cfileextractor [-x | -l] [-L] [-o <dir>] <vp_filename>\n");
410  printf("\n");
411  printf(" Commands (only one at the time):\n");
412  printf(" -x | --extract Extract all files into current directory.\n");
413  printf(" -l | --list List all files in VP archive.\n");
414  printf(" -h | --help Show this help text.\n");
415  printf("\n");
416  printf(" Options:\n");
417  printf(" -L | --lowercase Force all filenames to be lower case.\n");
418  printf(" -o <dir> Extract files to <dir> instead of current directory.\n");
419  printf(" NOTE: No spaces allowed, enclose path in \" \" if needed.\n");
420  printf("\n");
421  printf(" (No command specified will list all files in the VP archive.)\n");
422  printf("\n");
423 }
424 
425 // we end up #include'ing SDL.h which on Windows and Mac will redefine main() which is something
426 // that we don't want since we don't actually link against SDL, this solves the problem...
427 #ifdef main
428 #undef main
429 #endif
430 
431 int main(int argc, char *argv[])
432 {
433  int extract = 0, lc = 0, list = 0;
434 
435  if (argc < 2) {
436  help();
437  return 0;
438  }
439 
440  if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
441  help();
442  return 0;
443  }
444 
445  // the last option should always be the VP file that we want
446  fp_in = fopen(argv[argc-1], "rb");
447 
448  if (fp_in == NULL)
450 
451  // if the header is invalid then read_header() should exit us out
452  read_header();
453 
454  memset(out_dir, 0, MAX_PATH);
455 
456  // TODO: add filter based extraction
457  for (int i = 1; i < argc-1; i++) {
458  if (!strcmp(argv[i], "-x") || !strcmp(argv[i], "--extract")) {
459  if (list) {
460  help();
461  exit(0);
462  }
463  extract = 1;
464  } else if (!strcmp(argv[i], "-L") || !strcmp(argv[i], "--lowercase")) {
465  lc = 1;
466  } else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--list")) {
467  if (extract) {
468  help();
469  exit(0);
470  }
471  list = 1;
472  } else if ( !strcmp(argv[i], "-o") && (i+1 < argc) && (argv[i+1][0] != '-') ) {
473  strncpy(out_dir, argv[i+1], MAX_PATH-1);
474  i++; // have to increment "i" past the output directory
475  } else {
476  help();
477  return 0;
478  }
479  }
480 
481  // read the file index, make all filenames lowercase if wanted
482  read_index( lc );
483 
484  if (extract) {
485  extract_all_files( argv[argc-1] );
486  return 0;
487  } else if (list) {
488  list_all_files( argv[argc-1] );
489  return 0;
490  } else {
491  // if only -L is specified or no command is specified then just list files
492  list_all_files( argv[argc-1] );
493  return 0;
494  }
495 
496  return 0;
497 }
#define ERR_NO_FP_IN
int i
Definition: multi_pxo.cpp:466
#define ERR_PATH_TOO_LONG
#define MIN(a, b)
Definition: pstypes.h:296
#define MAX_PATH
#define DIR_SEPARATOR_CHAR
Definition: pstypes.h:43
long _fs_time_t
Definition: pstypes.h:55
struct vp_fileinfo vp_fileinfo
char file_path[CF_MAX_PATHNAME_LENGTH]
char tmp_data[BLOCK_SIZE]
void print_error(int err)
char id[4]
GLintptr offset
Definition: Glext.h:5497
#define CF_MAX_PATHNAME_LENGTH
Definition: cfile.h:40
int _mkdir(const char *path)
FILE * fp_in
#define DIR_SEPARATOR_STR
Definition: pstypes.h:44
sprintf(buf,"(%f,%f,%f)", v3->xyz.x, v3->xyz.y, v3->xyz.z)
GLdouble s
Definition: Glext.h:5321
#define ERR_INVALID_VP
#define ERR_NO_HEADER
unsigned char ubyte
Definition: pstypes.h:62
_fs_time_t write_time
void list_all_files(char *file)
FILE * fp_out
SCP_vector< vp_fileinfo > VP_FileInfo
void help()
GLsizei const GLchar ** path
Definition: Glext.h:6795
void read_index(int lc=0)
#define ERR_NO_FP_OUT
#define INT_SWAP(x)
char out_dir[MAX_PATH]
#define CF_MAX_FILENAME_LENGTH
Definition: cfile.h:39
const GLfloat * m
Definition: Glext.h:10319
void read_header()
int main(int argc, char *argv[])
void extract_all_files(char *file)
char file_name[CF_MAX_FILENAME_LENGTH]
GLenum GLsizei len
Definition: Glext.h:6283
#define BLOCK_SIZE
void lowercase(char *s)
struct vp_header vp_header
vp_header VP_Header
const GLubyte * c
Definition: Glext.h:8376
#define ERR_NO_INDEX
#define strcpy_s(...)
Definition: safe_strings.h:67