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