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