1 /*
2 Unmass - unpacker for game archives.
3 Copyright (C) 2002-2007 Mirex
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20 #include <stdlib.h>
21 #include <assert.h>
22
23 #include "config.h"
24
25 #ifdef _WIN32
26 #include <direct.h>
27 #endif
28
29 #ifdef _UNIX
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #endif
33
34 #include "massfs.h"
35 #include "utools.h"
36
37 #include "ma.h"
38 #include "ma_lgp.h"
39 #include "ma_pak.h"
40 #include "ma_wad2.h"
41 #include "ma_ipwad.h"
42 #include "ma_grp.h"
43 #include "ma_lbx.h"
44 #include "ma_vol.h"
45 #include "ma_wtn.h"
46 #include "ma_dune2.h"
47 #include "ma_moor3.h"
48 #include "ma_swine.h"
49 #include "ma_roll.h"
50 #include "ma_umod.h"
51 #include "ma_pbo.h"
52 #include "ma_pbo.h"
53 #include "ma_crism.h"
54 #include "ma_ff8.h"
55 #include "ma_bif.h"
56 #include "ma_eth2.h"
57 #include "ma_oni_d.h"
58 #include "ma_ecou.h"
59 #include "ma_vf1bi.h"
60 #include "ma_mgmnl.h"
61 #include "ma_mgs.h"
62 #include "ma_mea.h"
63 #include "ma_gunme.h"
64 #include "ma_fpk.h"
65 #include "ma_dnp.h"
66
CMassFiles(void)67 CMassFiles::CMassFiles( void )
68 {
69 int i;
70
71 for( i=0; i<massftypes_count; i++ )
72 Archive[ i ] = NULL;
73
74 Archive[ massftype_lgp ] = new CMassLgp;
75 Archive[ massftype_pak ] = new CMassPak;
76 Archive[ massftype_wad2 ] = new CMassWad2;
77 Archive[ massftype_ipwad ] = new CMassIPwad;
78 Archive[ massftype_grp ] = new CMassGrp;
79 Archive[ massftype_lbx ] = new CMassLbx;
80 Archive[ massftype_vol ] = new CMassVol;
81 Archive[ massftype_wtn ] = new CMassWtn;
82 Archive[ massftype_dune2 ] = new CMassDune2;
83 Archive[ massftype_moor3 ] = new CMassMoor3;
84 Archive[ massftype_swine ] = new CMassSwine;
85 Archive[ massftype_roll ] = new CMassRoll;
86 Archive[ massftype_umod ] = new CMassUmod;
87 Archive[ massftype_pbo ] = new CMassPbo;
88 Archive[ massftype_crismon ] = new CMassCrismon;
89 Archive[ massftype_ff8 ] = new CMassFF8;
90 Archive[ massftype_bif ] = new CMassBif;
91 Archive[ massftype_eth2 ] = new CMassEth2;
92 Archive[ massftype_oni_dat ] = new CMassOniDat;
93 Archive[ massftype_ecou ] = new CMassEcou;
94 Archive[ massftype_vf1bin ] = new CMassVF1bin;
95 Archive[ massftype_megamanL ] = new CMassMegamanLegends;
96 Archive[ massftype_mgs ] = new CMassMgs;
97 Archive[ massftype_mea ] = new CMassMea;
98 Archive[ massftype_gunmetal ] = new CMassGunMetal;
99 Archive[ massftype_fpk ] = new CMassFpk;
100 Archive[ massftype_dnp ] = new CMassDnp;
101
102
103 for( i=0; i<massftypes_count; i++ )
104 if ( Archive[ i ] == NULL ) {
105 SetErrorStr( "Could not alloc !" );
106 }
107
108 memset( &MassfInfo, 0, sizeof( MassfInfo ) );
109 MassfInfo.type = massftype_unknown;
110 }
111
~CMassFiles()112 CMassFiles::~CMassFiles()
113 {
114 int i;
115
116 Close();
117
118 for( i=0; i<massftypes_count; i++ )
119 if ( Archive[ i ] != NULL )
120 delete Archive[ i ];
121 }
122
123 // 0 = no error
Open(const char * MassFileName)124 int CMassFiles::Open( const char *MassFileName )
125 {
126 int i;
127 FILE *f;
128
129 //cleanup
130 Close();
131
132 //check filename
133 if ( strlen( MassFileName ) >= FileNameWithPathMaxLen ) {
134 SetErrorStr( "Open: filename too long" );
135 return 0;
136 }
137
138 //try if it exists
139 f = fopen( MassFileName, "rb" );
140 if ( f == NULL ) {
141 SetErrorStr( "Open: could not open file" );
142 return 0;
143 }
144 fclose( f );
145
146 //check out the file type and open it
147 for( i=0; i<massftypes_count; i++ ) {
148
149 Archive[ i ]->OpenFile( MassFileName );
150 if ( Archive[ i ]->CheckFileType() )
151 break;
152 Archive[ i ]->CloseFile();
153 }
154
155 if ( i == massftypes_count ) {
156 SetErrorStr( "Open: unknown filetype" );
157 return 0;
158 }
159 MassfInfo.type = i;
160
161
162 //=== get archive info ===
163 strncpy( MassfInfo.filenm, MassFileName, FileNameWithPathMaxLen );
164
165 //i'll split file name into filename and file extension
166 //find last slash (or start) and last dot
167 int dot, slash;
168
169 slash = strlen( MassFileName ) - 1;
170 dot = -1;
171 while ( slash > -1 ) {
172 if (( MassFileName[ slash ] == '\\' ) ||
173 ( MassFileName[ slash ] == '/' ))
174 break;
175 if (( MassFileName[ slash ] == '.' ) && ( dot == -1 ))
176 dot = slash;
177 slash --;
178 } ;
179 if ( dot == -1 ) {
180 strncpy( MassfInfo.fileextension, "", 19 );
181 dot = strlen( MassFileName );
182 } else
183 strncpy( MassfInfo.fileextension, &MassFileName[ dot + 1 ], 19 );
184
185 memcpy( MassfInfo.filedir, MassFileName, FileNameWithPathMaxLen );
186 MassfInfo.filedir[ slash+1 ] = 0;
187 memcpy( MassfInfo.filename, &MassFileName[ slash + 1 ],
188 dot - slash - 1 );
189 MassfInfo.filename[ dot - slash - 1 ] = '\0';
190 UnmassTools::_strlwr( MassfInfo.filename );
191 UnmassTools::_strlwr( MassfInfo.fileextension );
192
193
194 Archive[ MassfInfo.type ]->GetFileMainInfo();
195 strncpy( MassfInfo.typestring, Archive[ MassfInfo.type ]->GetIdent(), FileNameWithPathMaxLen );
196
197 MassfInfo.files_count = Archive[ MassfInfo.type ]->GetFilesCount();
198 MassfInfo.flags = Archive[ MassfInfo.type ]->GetFlags();
199 MassfInfo.set = 1;
200
201 return 1;
202 }
203
Close(void)204 void CMassFiles::Close( void )
205 {
206 int i;
207
208 for( i=0; i<massftypes_count; i++ )
209 Archive[ i ]->CloseFile();
210
211 MassfInfo.type = massftype_unknown;
212
213 memset( &MassfInfo, 0, sizeof( MassfInfo ) );
214 MassfInfo.set = 0;
215 }
216
GetTypeIdentString(int type)217 char *CMassFiles::GetTypeIdentString( int type )
218 {
219 static char ident[ 256 ];
220
221 if (( type < 0 ) || ( type >= massftypes_count )) {
222 ident[0] = 0;
223 return ident;
224 }
225
226 strncpy( ident, Archive[ type ]->GetIdent(), 255 );
227
228 return ident;
229 }
230
GetTypeExt(int type)231 char *CMassFiles::GetTypeExt( int type )
232 {
233 static char ext[ 256 ];
234
235 if (( type < 0 ) || ( type >= massftypes_count )) {
236 ext[0] = 0;
237 return ext;
238 }
239
240 strncpy( ext, Archive[ type ]->GetExtension(), 255 );
241
242 return ext;
243 }
244
GetRec(long num)245 int CMassFiles::GetRec( long num )
246 {
247 if ( MassfInfo.type == massftype_unknown )
248 return 0;
249
250 Archive[ MassfInfo.type ]->FileRec.Reset();
251
252 Archive[ MassfInfo.type ]->GetRec( num );
253
254 FileRec = Archive[ MassfInfo.type ]->FileRec;
255
256 return FileRec.set;
257 }
258
ReadRecords(void)259 int CMassFiles::ReadRecords( void )
260 {
261 return Archive[ MassfInfo.type ]->ReadRecords();
262 }
263
ReadAllRecords(void)264 int CMassFiles::ReadAllRecords( void )
265 {
266 int res = 1;
267
268 for( int i=0; i<MassfInfo.files_count; i++ )
269 res &= Archive[ MassfInfo.type ]->ReadRecords();
270
271 return res;
272 }
273
Extract(void)274 int CMassFiles::Extract( void )
275 {
276 enum { Massfs_BufSize = 20000 };
277 int ei, i;
278 char newname[ FileNameWithPathMaxLen ],
279 newdir[ FileNameWithPathMaxLen ];
280 FILE *newf;
281 char str[ FileNameWithPathMaxLen ];
282 unsigned char buf[ Massfs_BufSize ];
283 unsigned long pos, size;
284
285 if ( MassfInfo.type == massftype_unknown ) {
286 SetErrorStr( "Unknown file type" );
287 return 0;
288 }
289
290 Archive[ MassfInfo.type ]->FileRec = FileRec;
291
292 if ( m_outputDirectory.empty() )
293 snprintf( newname, FileNameWithPathMaxLen-1, "%s", FileRec.name );
294 else
295 snprintf( newname, FileNameWithPathMaxLen-1, "%s/%s", m_outputDirectory.c_str(), FileRec.name );
296
297 for( ei=0; ei<(int)strlen( newname ); ei++ ) {
298 if ( newname[ ei ] == '\\' )
299 newname[ ei ] = '/';
300 #ifdef none
301 if ( newname[ ei ] == ' ' )
302 newname[ ei ] = '_';
303 #endif
304 }
305
306 //dir
307 strncpy( newdir, newname, FileNameWithPathMaxLen-1 );
308 ei = strlen( newdir );
309 while (( newdir[ ei ] != '/' ) && ( ei > 0 ))
310 ei--;
311 newdir[ ei ] = '\0';
312 if ( strlen( newdir ) != 0 )
313 MakeDir( newdir );
314
315 //create new file
316 sprintf( str, "Creating [%s] ...", newname );
317 // Msg( str );
318 newf = fopen( newname, "wb" ); //?? check for existing !!
319 if ( newf == NULL ) {
320 sprintf( error, "Error creating [%s].", newname );
321 return 0;
322 }
323
324 if ( MassfInfo.flags & CMassArchive::ma_flag_extract_only_whole_file ) {
325 i = Archive[ MassfInfo.type ]->ExtractWholeFile( newf );
326 if ( i == 0 ) {
327 fclose( newf );
328 return 0;
329 }
330 }
331 else {
332
333 pos = 0; size = Massfs_BufSize;
334 while ( pos < FileRec.size ) {
335
336 if ( pos + size > FileRec.size )
337 size = FileRec.size - pos;
338
339
340 i = Archive[ MassfInfo.type ]->Extract
341 ( pos, size, buf );
342
343 if ( i == 0 ) {
344 fclose( newf );
345 return 0;
346 }
347
348 pos += i;
349
350 fwrite( buf, i, 1, newf );
351 }
352 }
353
354 fclose( newf );
355
356
357 sprintf( str, "Extracted [%s]", newname );
358
359 return 1;
360 }//Extract
361
362 //if there is no [mpath] directory, proc creates it
363 //watch out, it will return error on double slashes, so avoid them !
MakeDir(char * path)364 void CMassFiles::MakeDir( char *path )
365 {
366 char oldPath[301]; //drive:\path before mk, chdir ...
367 char str[301];
368 int mi, mj;
369 int pos; //pointer into mpath, where am i in process
370 int oldDisk = -1;
371 char c;
372
373 if ( path[0] == '\0' ) //if no dir
374 return;
375 //#ifdef WIN32
376 getcwd( oldPath, 300 );
377 //#else
378 // getcurdir( 0, str );
379 //#endif
380
381 // sprintf( oldPath, "\\%s", str );
382 //try first, if it is not present already
383 if ( chdir( path ) == 0 ) { //fine, path is present
384 chdir( oldPath );
385 return;
386 }
387 //if not, create path.
388 sprintf( str, "Creating dir [%s]", path );
389 // Msg( str );
390
391 pos = 0;
392
393 #ifdef _WIN32
394 // you can change drives on WIN32 or DOS systems
395 //check if drive is included
396 if ( path[1] == ':' ) {
397 oldDisk = _getdrive();
398 c = path[0];
399 if (( 'a' <= c ) && ( c <= 'z' ))
400 c += 'A' - 'a';
401
402 mi = c - 'A' + 1;
403 // mj = setdrive( mi );
404 mj = _chdrive( mi );
405 if ( mj == -1 ) {
406 sprintf( str, "Bad drive [%c%c]", path[0], path[1] );
407 // Msg( str );
408 }
409 pos = 2;
410 }
411 #endif
412
413 //change to root
414 if ( path[pos] == '/') {
415 chdir( "/" );
416 pos++;
417 }
418
419 //go as far as possible
420 do {
421 mj = pos; //start of dir = now
422 while ( (pos < (int)strlen(path)) &&
423 (path[pos] != '/') && (path[pos] != '\\') )
424 pos++;
425 strncpy( str, &path[mj], 300 );
426 str[pos-mj] = '\0';
427 mi = chdir( str );
428 pos++;
429 } while ( mi == 0 ); //do while chdir is ok
430
431 //can't go further, create. Allways have to create something, if im here
432 pos = mj; //pos to first bad dir
433 do {
434 mj = pos; //start of dir = now
435 while ( (pos < (int)strlen(path)) &&
436 (path[pos] != '/' ) && ( path[pos] != '\\' ))
437 pos++;
438 strncpy( str, &path[mj], 300 );
439 str[pos-mj] = '\0';
440 #ifdef _WIN32
441 if ( mkdir( str ) != 0 )
442 return;
443 #endif
444 #ifdef _UNIX
445 // 0777 = octal representation of 'everyone has access to newly created directory'
446 if ( mkdir( str, 0777 ) != 0 )
447 return;
448 #endif
449 // Msg( "MakeDir: Creating dir" );
450 chdir( str );
451 pos++;
452 } while ( pos < (int)strlen( path ) );
453 // Msg( "Created." );
454
455 #ifdef _WIN32
456 if ( oldDisk != -1 )
457 //setdisk( oldDisk );
458 _chdrive( oldDisk );
459 #endif
460 // if ( chdir( oldPath ) != 0 )
461 // Msg( "MakeDir: error change old path" );
462
463 }//MakeDir
464
465 //return index of added file, -1 on error
AddFile(char * filename,char * archive_name)466 int CMassFiles::AddFile( char* filename, char* archive_name )
467 {
468 FILE *fi = NULL;
469 unsigned long fs, p, bs;
470 char *buffer = NULL;
471 int res;
472
473 #define BUFFER_SIZE 64000
474
475 if ( MassfInfo.type == massftype_unknown ) {
476 SetErrorStr( "Unknown file type" );
477 return 0;
478 }
479
480 if (( MassfInfo.flags & CMassArchive::ma_flag_add_file ) == 0 ) {
481 assert( false );
482 SetErrorStr( "This filetype does not support adding files" );
483 goto AddFile_ret_error;
484 }
485
486 buffer = (char*) malloc( BUFFER_SIZE );
487 if ( buffer == NULL ) {
488 SetErrorStr( "Not enough memory" );
489 goto AddFile_ret_error;
490 }
491
492 fi = fopen( filename, "rb" );
493 if ( fi == NULL ) {
494 SetErrorStr( "File not found" );
495 goto AddFile_ret_error;
496 }
497
498 fseek( fi, 0, SEEK_END );
499 fs = ftell( fi );
500 fseek( fi, 0, SEEK_SET );
501
502 strncpy( Archive[ MassfInfo.type ]->FileRec.name, archive_name, CMassArchive::FileNameWithPathMaxLen-1 );
503 Archive[ MassfInfo.type ]->FileRec.size = fs;
504
505 res = Archive[ MassfInfo.type ]->AddFileRecord();
506 if ( res == 0 ) {
507 SetErrorStr( "Error adding file record" );
508 goto AddFile_ret_error;
509 }
510
511 MassfInfo.files_count++;
512
513 bs = BUFFER_SIZE;
514 p = 0;
515
516 while( p < fs ) {
517
518 if ( p + bs > fs )
519 bs = fs - p;
520
521 fread( buffer, bs, 1, fi );
522 Archive[ MassfInfo.type ]->AddFileData( p, bs, buffer );
523
524 p += bs;
525
526 }
527
528 p = Archive[ MassfInfo.type ]->FileRec.index;
529
530 AddFile_ret:
531
532 if ( fi != NULL )
533 fclose( fi );
534
535 if ( buffer != NULL )
536 free( buffer );
537
538 return p;
539
540 AddFile_ret_error:
541
542 p = -1;
543
544 goto AddFile_ret;
545 }
546
ReopenForWriting(void)547 int CMassFiles::ReopenForWriting( void )
548 {
549 if ( MassfInfo.type == massftype_unknown )
550 return 0;
551
552 return Archive[ MassfInfo.type ]->ReopenForWriting();
553 }
554
UnlinkFile(unsigned long index)555 int CMassFiles::UnlinkFile( unsigned long index )
556 {
557 if ( MassfInfo.type == massftype_unknown ) {
558 SetErrorStr( "Unknown file type" );
559 return 0;
560 }
561
562 if ( index >= MassfInfo.files_count ) {
563 assert( false );
564 SetErrorStr( "Index out of bounds" );
565 return 0;
566 }
567
568 if ( Archive[ MassfInfo.type ]->UnlinkFile( index ) == 0 ) {
569 SetErrorStr( Archive[ MassfInfo.type ]->GetErrorStr() );
570 return 0;
571 }
572
573 MassfInfo.files_count--;
574
575 return 1;
576 }
577
CreateNewArchive(char * newfilename,int type)578 int CMassFiles::CreateNewArchive( char* newfilename, int type )
579 {
580 FILE *nf;
581 int i;
582
583 if (( type < 0 ) || ( type >= massftypes_count )) {
584 SetErrorStr( "type out of bounds" );
585 return 0;
586 }
587
588 nf = fopen( newfilename, "wb" );
589 if ( nf == NULL ) {
590 SetErrorStr( "Could not create new file" );
591 return 0;
592 }
593
594 i = Archive[ type ]->CreateNewArchive( nf );
595
596 if ( i == 0 )
597 SetErrorStr( Archive[ type ]->GetErrorStr() );
598
599 fclose( nf );
600
601 return i;
602 }
603
TypeExtToWinDlgExt(int type)604 const char* CMassFiles::TypeExtToWinDlgExt( int type )
605 {
606 if (( type < 0 ) || ( type >= massftypes_count )) {
607 SetErrorStr( "type out of bounds" );
608 return NULL;
609 }
610
611 char s[ 1024 ], *chp;
612 int i, sl, sp;
613
614 //get ext
615 chp = Archive[ type ]->GetExtension();
616 sl = strlen( chp );
617
618 strncpy( s, chp, 1000 );
619
620 i = 0;
621 sp = 0;
622
623 do {
624 strncpy( &str[ sp ], "*.", 1000 - sp );
625 sp += 2;
626
627 while (( s[ i ] != 0 ) && ( s[ i ] != ',' )) {
628 str[ sp++ ] = s[ i++ ];
629 }
630
631 if ( s[ i ] == ',' )
632 strcat( str, "; " );
633 } while ( s[ i ] != 0 );
634
635 str[ sp++ ] = 0;
636
637 return str;
638 }
639
GetTypeFirstExt(unsigned long type)640 const char* CMassFiles::GetTypeFirstExt( unsigned long type )
641 {
642 if ( type >= massftypes_count ) {
643 SetErrorStr( "type out of bounds" );
644 return NULL;
645 }
646
647 char *chp;
648 int i, sl, sp;
649
650 chp = Archive[ type ]->GetExtension();
651 sl = strlen( chp );
652 strncpy( str, chp, 1023 );
653
654 i = 0;
655 while (( str[ i ] != 0 ) && ( str[ i ] != ',' ))
656 i++;
657
658 str[ i ] = 0;
659
660 return str;
661 }
662
SetOutputDirectory(const char * s)663 void CMassFiles::SetOutputDirectory( const char* s )
664 {
665 m_outputDirectory = s;
666 }
667