1 /* ScummVM Tools
2  *
3  * ScummVM Tools is the legal property of its developers, whose
4  * names are too numerous to list here. Please refer to the
5  * COPYRIGHT file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 /* Compress Bud Tucker Sound Data Files */
23 
24 #include <assert.h>
25 #include <string.h>
26 #include <stdio.h>
27 
28 #include "common/util.h"
29 #include "compress.h"
30 #include "compress_tucker.h"
31 
32 #define CURRENT_VER  1
33 #define HEADER_SIZE  4
34 #define HEADER_FLAG_AUDIO_INTRO (1 << 0)
35 
36 #define OUTPUT_MP3  "TUCKER.SO3"
37 #define OUTPUT_OGG  "TUCKER.SOG"
38 #define OUTPUT_FLA  "TUCKER.SOF"
39 
40 struct CompressedData {
41 	int offset;
42 	int size;
43 };
44 
45 static CompressedData temp_table[10000];
46 
CompressTucker(const std::string & name)47 CompressTucker::CompressTucker(const std::string &name) : CompressionTool(name, TOOLTYPE_COMPRESSION) {
48 	_supportsProgressBar = true;
49 
50 	ToolInput input;
51 	input.format = "/";
52 	input.file = false;
53 	_inputPaths.push_back(input);
54 
55 	_shorthelp = "Used to compress the Bud Tucker data files.";
56 	_helptext = "\nUsage: " + getName() + " [mode params] [-o outputdir] <inputdir>\n";
57 }
58 
append_compress_file(Common::File & output)59 int CompressTucker::append_compress_file(Common::File &output) {
60 	char buf[2048];
61 	int sz, compress_sz = 0;
62 
63 	Common::File input_temp(tempEncoded, "rb");
64 	while ((sz = input_temp.read_noThrow(buf, sizeof(buf))) > 0) {
65 		if ((sz = output.write(buf, sz)) > 0) {
66 			compress_sz += sz;
67 		}
68 	}
69 	return compress_sz;
70 }
71 
compress_file_wav(Common::File & input,Common::File & output)72 int CompressTucker::compress_file_wav(Common::File &input, Common::File &output) {
73 	char buf[8];
74 
75 	if (input.read_noThrow(buf, 8) == 8 && memcmp(buf, "RIFF", 4) == 0) {
76 		extractAndEncodeWAV(TEMP_WAV, input, _format);
77 		return append_compress_file(output);
78 	}
79 	return 0;
80 }
81 
compress_file_raw(const char * input,bool is16,Common::File & output)82 int CompressTucker::compress_file_raw(const char *input, bool is16, Common::File &output) {
83 	if (is16) {
84 		setRawAudioType(true, false, 16);
85 	} else {
86 		setRawAudioType(false, false, 8);
87 	}
88 	encodeAudio(input, true, 22050, tempEncoded, _format);
89 	return append_compress_file(output);
90 }
91 
92 #define SOUND_TYPES_COUNT 3
93 
94 #define MAX_SOUND_FILES    500
95 #define MAX_MUSIC_FILES     60
96 #define MAX_SPEECH_FILES 10000
97 
98 struct SoundDirectory {
99 	const char *name;
100 	const char *fmt;
101 	int count;
102 };
103 
104 static SoundDirectory sound_directory_table[SOUND_TYPES_COUNT] = {
105 	{ "FX",     "FX%d.WAV",    MAX_SOUND_FILES  },
106 	{ "MUSIC",  "MUS%d.WAV",   MAX_MUSIC_FILES  },
107 	{ "SPEECH", "SAM%04d.WAV", MAX_SPEECH_FILES }
108 };
109 
compress_sounds_directory(const Common::Filename * inpath,const Common::Filename * outpath,Common::File & output,const struct SoundDirectory * dir)110 uint32 CompressTucker::compress_sounds_directory(const Common::Filename *inpath, const Common::Filename *outpath, Common::File &output, const struct SoundDirectory *dir) {
111 	char filepath[1024];
112 	int i, pos, len;
113 	uint32 current_offset;
114 	Common::File input;
115 
116 	assert(dir->count <= ARRAYSIZE(temp_table));
117 
118 	// We can't use setFullName since dir->name can contain '/'
119 	len = snprintf(filepath, sizeof(filepath), "%s/%s/", inpath->getPath().c_str(), dir->name);
120 
121 	pos = output.pos();
122 
123 	/* write 0 offsets/sizes table */
124 	for (i = 0; i < dir->count; ++i) {
125 		output.writeUint32LE(0);
126 		output.writeUint32LE(0);
127 	}
128 
129 	/* compress .wav files in directory */
130 	current_offset = 0;
131 	for (i = 0; i < dir->count; ++i) {
132 		temp_table[i].offset = current_offset;
133 		snprintf(&filepath[len], sizeof(filepath) - len, dir->fmt, i);
134 		try {
135 			input.open(filepath, "rb");
136 			temp_table[i].size = compress_file_wav(input, output);
137 		} catch (...) {
138 			temp_table[i].size = 0;
139 		}
140 		current_offset += temp_table[i].size;
141 	}
142 
143 	/* fix offsets/sizes table */
144 	output.seek(pos, SEEK_SET);
145 	for (i = 0; i < dir->count; ++i) {
146 		output.writeUint32LE(temp_table[i].offset);
147 		output.writeUint32LE(temp_table[i].size);
148 	}
149 
150 	output.seek(0, SEEK_END);
151 	return current_offset + dir->count * 8;
152 }
153 
154 static const char *audio_files_list[] = {
155 	"DEMOMENU.RAW",
156 	"DEMOROLC.RAW",
157 	"FX101.WAV",
158 	"FX102.WAV",
159 	"FX103.WAV",
160 	"FX104.WAV",
161 	"FX105.WAV",
162 	"FX107.WAV",
163 	"FX108.WAV",
164 	"FX109.WAV",
165 	"FX110.WAV",
166 	"FX111.WAV",
167 	"FX112.WAV",
168 	"FX113.WAV",
169 	"FX114.WAV",
170 	"FX116.WAV",
171 	"FX117.WAV",
172 	"FX32.WAV",
173 	"FX33.WAV",
174 	"FX34.WAV",
175 	"FX35.WAV",
176 	"FX36.WAV",
177 	"FX37.WAV",
178 	"FX38.WAV",
179 	"FX39.WAV",
180 	"FX40.WAV",
181 	"FX42.WAV",
182 	"FX43.WAV",
183 	"FX44.WAV",
184 	"FX45.WAV",
185 	"FX47.WAV",
186 	"FX48.WAV",
187 	"FX49.WAV",
188 	"FX50.WAV",
189 	"FX52.WAV",
190 	"FX53.WAV",
191 	"FX55.WAV",
192 	"FX56.WAV",
193 	"FX57.WAV",
194 	"FX58.WAV",
195 	"FX59.WAV",
196 	"FX60.WAV",
197 	"FX61.WAV",
198 	"FX62.WAV",
199 	"FX63.WAV",
200 	"FX64.WAV",
201 	"FX66.WAV",
202 	"FX67.WAV",
203 	"FX68.WAV",
204 	"FX69.WAV",
205 	"FX70.WAV",
206 	"FX71.WAV",
207 	"FX72.WAV",
208 	"FX73.WAV",
209 	"FX74.WAV",
210 	"FX75.WAV",
211 	"FX76.WAV",
212 	"FX77.WAV",
213 	"FX78.WAV",
214 	"FX79.WAV",
215 	"FX80.WAV",
216 	"FX81.WAV",
217 	"FX83.WAV",
218 	"FX86.WAV",
219 	"FX91.WAV",
220 	"FX92.WAV",
221 	"FX93.WAV",
222 	"FX97.WAV",
223 	"FX98.WAV",
224 	"INT1.RAW",
225 	"INT14.RAW",
226 	"INT15.RAW",
227 	"INT16.RAW",
228 	"INT17.RAW",
229 	"INT18.RAW",
230 	"INT19.RAW",
231 	"INT2.RAW",
232 	"INT20.RAW",
233 	"INT21.RAW",
234 	"INT22.RAW",
235 	"INT23.RAW",
236 	"INT24.RAW",
237 	"INT25.RAW",
238 	"INT26.RAW",
239 	"INT27.RAW",
240 	"INT28.RAW",
241 	"INT29.RAW",
242 	"INT3.RAW",
243 	"INT30.RAW",
244 	"INT31.RAW",
245 	"INT32.RAW",
246 	"INT33.RAW",
247 	"INT34.RAW",
248 	"INT35.RAW",
249 	"INT36.RAW",
250 	"INT37.RAW",
251 	"INT38.RAW",
252 	"INT4.RAW",
253 	"INT41.RAW",
254 	"INT42.RAW",
255 	"INT5.RAW",
256 	"INT6.RAW",
257 	"INT7.RAW",
258 	"INTRODUA.WAV",
259 	"MERILOGO.RAW",
260 	"RDFX1.WAV",
261 	"RDFX12.WAV",
262 	"RDFX13.WAV",
263 	"RDFX14.WAV",
264 	"RDFX15.WAV",
265 	"RDFX16.WAV",
266 	"RDFX17.WAV",
267 	"RDFX18.WAV",
268 	"RDFX20.WAV",
269 	"RDFX21.WAV",
270 	"RDFX22.WAV",
271 	"RDFX24.WAV",
272 	"RDFX25.WAV",
273 	"RDFX26.WAV",
274 	"RDFX27.WAV",
275 	"RDFX28.WAV",
276 	"RDFX3.WAV",
277 	"RDFX30.WAV",
278 	"RDFX31.WAV",
279 	"RDFX33.WAV",
280 	"RDFX36.WAV",
281 	"RDFX37.WAV",
282 	"RDFX38.WAV",
283 	"RDFX8.WAV",
284 	"RDFX9.WAV"
285 };
286 
287 static const int audio_formats_table[] = {
288 	3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1,
289 	1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1,
290 	1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 2,
291 	1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1,
292 	1, 1, 1, 1, 1, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4,
293 	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
294 	4, 4, 4, 4, 4, 4, 4, 2, 3, 1, 1, 2, 1, 1, 1, 1,
295 	1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
296 	2, 1
297 };
298 
compress_audio_directory(const Common::Filename * inpath,const Common::Filename * outpath,Common::File & output)299 uint32 CompressTucker::compress_audio_directory(const Common::Filename *inpath, const Common::Filename *outpath, Common::File &output) {
300 	char filepath[1024];
301 	int i, pos, count;
302 	uint32 current_offset;
303 
304 	count = ARRAYSIZE(audio_files_list);
305 	pos = output.pos();
306 
307 	/* write 0 offsets/sizes table */
308 	for (i = 0; i < count; ++i) {
309 		output.writeUint32LE(0);
310 		output.writeUint32LE(0);
311 	}
312 
313 	current_offset = 0;
314 	for (i = 0; i < count; ++i) {
315 		temp_table[i].offset = current_offset;
316 		snprintf(filepath, sizeof(filepath), "%sAUDIO/%s", inpath->getPath().c_str(), audio_files_list[i]);
317 
318 		try {
319 			Common::File input(filepath, "rb");
320 
321 			switch (audio_formats_table[i]) {
322 			case 1:
323 			case 2:
324 				temp_table[i].size = compress_file_wav(input, output);
325 				break;
326 			case 3:
327 				temp_table[i].size = compress_file_raw(filepath, 0, output);
328 				break;
329 			case 4:
330 				temp_table[i].size = compress_file_raw(filepath, 1, output);
331 				break;
332 			}
333 		} catch (...) {
334 			warning("Can't open file '%s'", filepath);
335 			temp_table[i].size = 0;
336 		}
337 
338 		current_offset += temp_table[i].size;
339 	}
340 
341 	/* fix offsets/sizes table */
342 	output.seek(pos, SEEK_SET);
343 	for (i = 0; i < count; ++i) {
344 		output.writeUint32LE(temp_table[i].offset);
345 		output.writeUint32LE(temp_table[i].size);
346 	}
347 
348 	output.seek(0, SEEK_END);
349 	return current_offset + count * 8;
350 }
351 
compress_sound_files(const Common::Filename * inpath,const Common::Filename * outpath)352 void CompressTucker::compress_sound_files(const Common::Filename *inpath, const Common::Filename *outpath) {
353 	int i;
354 	uint32 current_offset;
355 	uint32 sound_directory_size[SOUND_TYPES_COUNT];
356 	uint32 audio_directory_size;
357 	static const uint16 flags = HEADER_FLAG_AUDIO_INTRO;
358 
359 	Common::File output(*outpath, "wb");
360 
361 	output.writeUint16LE(CURRENT_VER);
362 	output.writeUint16LE(flags);
363 
364 	/* write 0 offsets/count */
365 	for (i = 0; i < SOUND_TYPES_COUNT; ++i) {
366 		output.writeUint32LE(0);
367 		output.writeUint32LE(0);
368 	}
369 	if (flags & HEADER_FLAG_AUDIO_INTRO) {
370 		output.writeUint32LE(0);
371 		output.writeUint32LE(0);
372 	}
373 
374 	/* compress the .wav files in each directory */
375 	for (i = 0; i < SOUND_TYPES_COUNT; ++i) {
376 		updateProgress(i, SOUND_TYPES_COUNT + 1);
377 
378 		print("Processing directory '%s'...", sound_directory_table[i].name);
379 		sound_directory_size[i] = compress_sounds_directory(inpath, outpath, output, &sound_directory_table[i]);
380 		print("Done (%d bytes)", sound_directory_size[i]);
381 	}
382 	if (flags & HEADER_FLAG_AUDIO_INTRO) {
383 		updateProgress(1, 1);
384 
385 		print("Processing directory 'audio'...");
386 		audio_directory_size = compress_audio_directory(inpath, outpath, output);
387 		print("Done (%d bytes)", audio_directory_size);
388 	}
389 
390 	/* fix sound types offsets/counts */
391 	output.seek(HEADER_SIZE, SEEK_SET);
392 	current_offset = 0;
393 	for (i = 0; i < SOUND_TYPES_COUNT; ++i) {
394 		output.writeUint32LE(current_offset);
395 		output.writeUint32LE(sound_directory_table[i].count);
396 		current_offset += sound_directory_size[i];
397 	}
398 	if (flags & HEADER_FLAG_AUDIO_INTRO) {
399 		output.writeUint32LE(current_offset);
400 		output.writeUint32LE(ARRAYSIZE(audio_files_list));
401 		current_offset += audio_directory_size;
402 	}
403 
404 	output.close();
405 
406 	/* cleanup */
407 	Common::removeFile(TEMP_WAV);
408 	Common::removeFile(TEMP_RAW);
409 	Common::removeFile(tempEncoded);
410 
411 	print("Done.");
412 }
413 
414 static const char *inputDirs[] = { "AUDIO", "FX", "MUSIC", "SPEECH", 0 };
415 
execute()416 void CompressTucker::execute() {
417 	Common::Filename inpath(_inputPaths[0].path);
418 	Common::Filename &outpath = _outputPath;
419 
420 	// Ensure necessary directories are present
421 	for (int i = 0; inputDirs[i]; ++i) {
422 		char path[1024];
423 		snprintf(path, sizeof(path), "%s%s", inpath.getPath().c_str(), inputDirs[i]);
424 		if (!Common::isDirectory(path)) {
425 			error("Missing input directory '%s'", path);
426 		}
427 	}
428 
429 	// Default out is same as in directory, file names differ by extension
430 	if (outpath.empty()) {
431 		outpath = inpath;
432 	}
433 
434 	// Temporary output file
435 	switch(_format) {
436 	case AUDIO_MP3:
437 		tempEncoded = TEMP_MP3;
438 		outpath.setFullName(OUTPUT_MP3);
439 		break;
440 	case AUDIO_VORBIS:
441 		tempEncoded = TEMP_OGG;
442 		outpath.setFullName(OUTPUT_OGG);
443 		break;
444 	case AUDIO_FLAC:
445 		tempEncoded = TEMP_FLAC;
446 		outpath.setFullName(OUTPUT_FLA);
447 		break;
448 	default:
449 		throw ToolException("Unknown audio format");
450 		break;
451 	}
452 
453 	compress_sound_files(&inpath, &outpath);
454 }
455 
456 #ifdef STANDALONE_MAIN
main(int argc,char * argv[])457 int main(int argc, char *argv[]) {
458 	return export_main(compress_tucker)(argc, argv);
459 }
460 #endif
461 
462