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 <stdarg.h>
21 #include <stdio.h>
22 
23 #include <string.h>
24 
25 #include "config.h"
26 
27 #ifdef _UNIX
28 	#include <ctype.h>
29 	#include <strings.h>
30 	#include <unistd.h>
31 	#include <sys/stat.h>
32 #endif
33 
34 #include "utools.h"
35 
36 #include <string>
37 
38 UnmassTools::s_params	UnmassTools::param_def[ UnmassTools::param_def_count ] =
39 {
40 	{ param_help,	"-help", 0 },
41 	{ param_help,	"--help", 0 },
42 	{ param_help,	"/?", 0 },
43 
44 	{ param_extract_files, "-e", -2 },
45 	{ param_list_of_modules, "-modules", 0 },
46 	{ param_print_file_list, "-list", 1 },
47 	{ param_print_file_list_full, "-list-full", 1 },
48 
49 
50 } ;
51 
52 char UnmassTools::version_string[ 20 ] = "0.9 console";
53 char UnmassTools::executable_name_string[ 20 ] = "unmass";
54 
55 
Log(int severity,const char * format,...)56 void UnmassTools::Log( int severity, const char* format, ... )
57 {
58 	va_list argptr;
59 	va_start( argptr, format );
60 
61 	vprintf( format, argptr );
62 
63 	va_end( argptr );
64 }
65 
Start(int argc,char * argv[])66 int UnmassTools::Start( int argc, char* argv[] )
67 {
68 	int		action = param_help;
69 	int		i, c, required_params_number;
70 	bool	or_more = false;
71 	char	str1[ 301 ];
72 
73 	UnmassTools::array_of_filenames		names_array;
74 
75 	if ( argc <= 1 ) {
76 		action = param_none;
77 	} else {
78 
79 		for( i=0; i<param_def_count; i++ )
80 			if ( _strcasecmp( argv[ 1 ], param_def[ i ].string ) == 0 ) {
81 				action = param_def[ i ].ident;
82 
83 				required_params_number = param_def[ i ].param_count;
84 				if ( required_params_number < 0 ) {
85 					required_params_number = - required_params_number;
86 					or_more = true;
87 				}
88 
89 				c = argc - 2;
90 
91 				if (( c > required_params_number ) && ( or_more == false )) {
92 					Log( 0, "Supplied more than enough parameters.\n" );
93 				}
94 
95 				if ( c < required_params_number ) {
96 					Log( 0, "Not enough parameters.\n" );
97 					action = param_none;
98 				}
99 
100 				break;
101 			}
102 
103 	}
104 
105 
106 	switch( action ) {
107 	case param_none:
108 		PrintProgramInfo();
109 		return 0;
110 
111 	case param_help:
112 		PrintHelp();
113 		return 0;
114 
115 	case param_list_of_modules:
116 		PrintModules();
117 		return 0;
118 
119 	case param_print_file_list:
120 
121 		return PrintMassFileList( argv[ 2 ], true );
122 
123 	case param_print_file_list_full:
124 
125 		return PrintMassFileList( argv[ 2 ], false );
126 
127 	case param_extract_files:
128 
129 		names_array.clear();
130 		for( i=0; i<c-1; i++ )
131 			names_array.push_back( argv[ 3 + i ] );
132 
133 		// set output directory - current working directory
134 		getcwd( str1, 300 );
135 		SetOutputDirectory( str1 );
136 
137 		return ExtractMultipleFiles( argv[ 2 ], names_array );
138 
139 	default:
140 		Log( 1, "Incorrect action\n" );
141 		break;
142 
143 	}
144 
145 	return 1;
146 }
147 
PrintProgramInfo(void)148 void UnmassTools::PrintProgramInfo( void )
149 {
150 	Log( 0, "-*= Unmass =*-\n" );
151 	Log( 0, "version %s\n\n", version_string );
152 	Log( 0, "Unmass is archive unpacker for game archives.\n" );
153 	Log( 0, "Use parameter /? to see help.\n" );
154 }
155 
PrintHelp(void)156 void UnmassTools::PrintHelp( void )
157 {
158 	PrintProgramInfo();
159 	Log( 0, "\n" );
160 
161 	int	i, j;
162 
163 	for( i=0; i<param_count; i++ ) {
164 
165 		for( j=0; j<param_def_count; j++ ) {
166 			if ( param_def[ j ].ident == i ) {
167 
168 				Log( 0, "  %s\n", param_def[ j ].string );
169 
170 			}
171 		}
172 
173 		switch( i ) {
174 		case param_help:
175 			Log( 0, "Prints out this help\n" );
176 			break;
177 		case param_list_of_modules:
178 			Log( 0, "Prints out list of modules for archive loading\n" );
179 			break;
180 		case param_print_file_list:
181 			Log( 0, "Parameters: <archive_file>\n" );
182 			Log( 0, "Opens the archive and prints list of the files inside.\n" );
183 			break;
184 		case param_print_file_list_full:
185 			Log( 0, "Parameters: <archive_file>\n" );
186 			Log( 0, "Opens the archive and prints list of the files inside with offsets, sizes and number of files.\n" );
187 			break;
188 		case param_extract_files:
189 			Log( 0, "Parameters: <archive file> <file name> [...<file name>]\n" );
190 			Log( 0, "Opens the archive and extract files.\n" );
191 			Log( 0, " file name can be specified also with * wildcard in one of these forms:\n");
192 			Log( 0, " 'name', 'prefix*', '*suffix' or 'prefix*suffix' \n");
193 			Log( 0, " so 'level*.txt' should work well.\n");
194 			Log( 0, " Enclose the filenames with wildcards into ' ' so unix system won't replace\n" );
195 			Log( 0, " those names with currently existing files names.\n" );
196 			break;
197 		}
198 
199 		Log( 0, "\n" );
200 	}
201 
202 	Log( 0, "Example:\n" );
203 	Log( 0, "  %s -list battle.lgp\n", executable_name_string );
204 	Log( 0, "    Displays list of files contained inside archive 'battle.lgp'\n\n" );
205 	Log( 0, "  %s -e battle.lgp aabc.txt *dat\n", executable_name_string );
206 	Log( 0, "    Opens 'battle.lgp' and extracts file 'aabc.txt' and all files ending\n" );
207 	Log( 0, "    with 'dat' into current directory\n\n" );
208 
209 	Log( 0, "\n" );
210 
211 	return;
212 }
213 
PrintModules(void)214 void UnmassTools::PrintModules( void )
215 {
216 	int		i, c;
217 	CMassArchive*	pArchive;
218 
219 	Log( 0, "Plugins and their supported types:\n" );
220 
221 	for( i=0; i<CMassFiles::massftypes_count; i++ ) {
222 		pArchive = massfs.Archive[ i ];
223 		if ( pArchive == NULL )
224 			Log( 0, "null\n" );
225 		else {
226 			Log( 0, "%10s %s\n", pArchive->GetExtension(), pArchive->GetIdent() );
227 		}
228 	}
229 
230 }
231 
PrintMassFileList(const char * mass_filename,bool filenames_only)232 int UnmassTools::PrintMassFileList( const char* mass_filename, bool filenames_only )
233 {
234 	if ( massfs.Open( mass_filename ) == 0 ) {
235 		Log( 1, "Error, could not open file (%s)\n", massfs.GetErrorStr() );
236 		return 0;
237 	}
238 
239 	int	i, c;
240 	c = massfs.MassfInfo.files_count;
241 
242 	massfs.ReadAllRecords();
243 
244 	if ( ! filenames_only ) {
245 		printf( "Files: %i\n", c );
246 		printf( "%9s %9s %s\n", "offset", "size", "name" );
247 	}
248 
249 	for( i=0; i<c; i++ ) {
250 		massfs.GetRec( i );
251 
252 		if ( filenames_only )
253 			printf( "%s\n", massfs.FileRec.name );
254 		else
255 			printf( "%9i %9i %s\n", massfs.FileRec.offset, massfs.FileRec.size, massfs.FileRec.name );
256 	}
257 
258 	massfs.Close();
259 
260 }
261 
262 //  const char* filename
ExtractMultipleFiles(const char * mass_filename,UnmassTools::array_of_filenames names)263 int UnmassTools::ExtractMultipleFiles(
264 		const char* mass_filename, UnmassTools::array_of_filenames names )
265 {
266 	// first try to open the file
267 	if ( massfs.Open( mass_filename ) == 0 ) {
268 		Log( 1, "Error, could not open file (%s)\n", massfs.GetErrorStr() );
269 		return 0;
270 	}
271 
272 	int	i, j, c, extracted_files = 0;
273 	bool	extract, prefix_match, suffix_match;
274 	c = massfs.MassfInfo.files_count;
275 	massfs.ReadAllRecords();
276 
277 	// second, parse the parameter list and try to extract the files
278 	int number_of_files = names.size();
279 	const char*	ccp;
280 
281 	// filename can be either "name" or "nam*" or "*me" or "na*me" or "*"
282 	//! structure is named [s_filespec] because MSVC6 complains if
283 	//! unnamed struct contains function
284 	struct s_filespec {
285 
286 		enum {
287 			name_len = 512,
288 		} ;
289 
290 		char	name[ name_len ];
291 		bool	valid;
292 		bool	all;	// extract all files ?
293 		char	prefix[ name_len ];
294 		char	suffix[ name_len ];
295 
296 		int		prefix_length, suffix_length;
297 
298 		void Reset( void ) {
299 			valid = false;
300 			all = false;
301 			name[ 0 ] = 0;
302 			prefix[ 0 ] = 0;
303 			suffix[ 0 ] = 0;
304 			prefix_length = suffix_length = 0;
305 		}
306 
307 		void Parse( const char* fname ) {
308 			int			stars = 0, c;
309 			const char*	ccp = fname;
310 
311 			Reset();
312 
313 			// is it a single star ? then it means 'all files'
314 			if (( fname[ 0 ] == '*' ) && ( fname[ 1 ] == 0 )) {
315 				all = true;
316 				valid = true;
317 				return;
318 			}
319 
320 			while( *ccp != 0 ) {
321 
322 				if ( *ccp == '*' ) {
323 
324 					// count the star characters. there can be only one in the filename
325 					stars++;
326 					if ( stars > 1 )
327 						break;
328 
329 					// treat all characters before star as prefix
330 					if ( ccp != fname ) {
331 						c = ( ccp - fname );
332 						if ( c >= name_len )
333 							c = name_len - 1;
334 						memcpy( prefix, fname, c );
335 						prefix[ c ] = 0;
336 						prefix_length = c;
337 					}
338 
339 				} else {
340 					// if there was star already, and suffix is empty then create it
341 					if (( stars > 0 ) && ( suffix[ 0 ] == 0 )) {
342 						strncpy( suffix, ccp, name_len-1 );
343 						suffix_length = strlen( suffix );
344 					}
345 
346 				}
347 
348 				ccp++;
349 			}
350 
351 			// too many stars ? invalid mask !
352 			if ( stars > 1 ) {
353 				Reset();
354 				valid = false;
355 
356 			} else {
357 
358 				strncpy( name, fname, name_len-1 );
359 				valid = true;
360 
361 			}
362 		} // Parse
363 
364 	} filespec;
365 
366 	for( i=0; i<number_of_files; i++ ) {
367 		ccp = names[ i ];
368 
369 		filespec.Parse( ccp );
370 
371 		if ( filespec.valid == false ) {
372 			Log( 1, "Error: filename specification '%s' is invalid.", ccp );
373 			continue;
374 		}
375 
376 		// loop through all files, and see if the filename matches the search pattern
377 		for( j=0; j<massfs.MassfInfo.files_count; j++ ) {
378 
379 			extract = false;
380 			prefix_match = false;
381 			suffix_match = false;
382 			massfs.GetRec( j );
383 
384 			if ( filespec.all )
385 				extract = true;
386 
387 			// whole name
388 			if (( extract == false ) && ( _strcasecmp( ccp, massfs.FileRec.name ) == 0 ))
389 				extract = true;
390 
391 			// check for prefix
392 			if (( extract == false ) && ( filespec.prefix_length > 0 ) &&
393 				( _strncasecmp( filespec.prefix, massfs.FileRec.name, filespec.prefix_length ) == 0 ))
394 				prefix_match = true;
395 
396 			// check for suffix
397 			if (( extract == false ) && ( filespec.suffix_length > 0 ) &&
398 				( _strcasecmp( filespec.suffix,
399 				massfs.FileRec.name + strlen( massfs.FileRec.name ) - filespec.suffix_length ) == 0 ))
400 				suffix_match = true;
401 
402 			// if there is both prefix and suffix defined, then extract only if both match
403 			if (( filespec.prefix_length > 0 ) && ( filespec.suffix_length > 0 )) {
404 				if (( prefix_match) && ( suffix_match ))
405 					extract = true;
406 			} else {
407 				if (( prefix_match ) || ( suffix_match ))
408 					extract = true;
409 			}
410 
411 			if ( extract ) {
412 				extracted_files++;
413 				Log( 0, "Extracting file '%s'\n", massfs.FileRec.name );
414 				massfs.Extract();
415 			}
416 		}
417 
418 		// if I extract all files its not needed to extract any more
419 		if ( filespec.all ) {
420 			break;
421 		}
422 
423 	}
424 
425 	Log( 0, "Extracted %i files.\n", extracted_files );
426 
427 	massfs.Close();
428 
429 	return 1;
430 }
431 
_strlwr(char * string)432 void UnmassTools::_strlwr( char* string )
433 {
434 	#ifdef _UNIX
435 		char*	cp = string;
436 
437 		while( *cp != 0 ) {
438 			*cp = tolower( *cp );
439 			cp++;
440 		}
441 		return;
442 	#else
443 		strlwr( string );
444 		return;
445 	#endif
446 }
447 
_strcasecmp(const char * s1,const char * s2)448 int UnmassTools::_strcasecmp( const char* s1, const char* s2 )
449 {
450 	#ifdef _UNIX
451 		return strcasecmp( s1, s2 );
452 	#else
453 		return strcmp( s1, s2 );
454 	#endif
455 }
456 
_strncasecmp(const char * s1,const char * s2,int n)457 int UnmassTools::_strncasecmp( const char* s1, const char* s2, int n )
458 {
459 	#ifdef _UNIX
460 		return strncasecmp( s1, s2, n );
461 	#else
462 		return stricmp( s1, s2 );
463 	#endif
464 }
465 
SetOutputDirectory(const char * s)466 void UnmassTools::SetOutputDirectory( const char* s )
467 {
468 	massfs.SetOutputDirectory( s );
469 }
470