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 /* compressor for smush san files */
23 
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <zlib.h>
28 #include <stdio.h>
29 
30 #include "compress_scumm_san.h"
31 #include "common/endian.h"
32 
encodeSanWaveWithOgg(const std::string & filename)33 void CompressScummSan::encodeSanWaveWithOgg(const std::string &filename) {
34 	std::string fbuf = filename + ".raw";
35 	Common::Filename fbuf2(filename.c_str());
36 
37 	fbuf2.setExtension(".ogg");
38 	encodeAudio(fbuf.c_str(), true, 22050, fbuf2.getFullPath().c_str(), AUDIO_VORBIS);
39 }
40 
encodeSanWaveWithLame(const std::string & filename)41 void CompressScummSan::encodeSanWaveWithLame(const std::string &filename) {
42 	std::string fbuf = filename + ".raw";
43 	Common::Filename fbuf2(filename.c_str());
44 
45 	fbuf2.setExtension(".mp3");
46 	encodeAudio(fbuf.c_str(), true, 22050, fbuf2.getFullPath().c_str(), AUDIO_MP3);
47 }
48 
writeToTempWaveFile(const std::string & fileName,byte * output_data,unsigned int size)49 void CompressScummSan::writeToTempWaveFile(const std::string &fileName, byte *output_data, unsigned int size) {
50 	if (!_waveTmpFile.isOpen()) {
51 		_waveTmpFile.open(fileName, "wb");
52 		if (!_waveTmpFile.isOpen()) {
53 			error("error writing temp wave file");
54 		}
55 		_waveDataSize = 0;
56 	}
57 	for (unsigned int j = 0; j < size - 1; j += 2) {
58 		byte tmp = output_data[j + 0];
59 		output_data[j + 0] = output_data[j + 1];
60 		output_data[j + 1] = tmp;
61 	}
62 
63 	_waveTmpFile.write(output_data, size);
64 	_waveDataSize += size;
65 }
66 
decompressComiIACT(const std::string & fileName,byte * output_data,byte * d_src,int bsize)67 void CompressScummSan::decompressComiIACT(const std::string &fileName, byte *output_data, byte *d_src, int bsize) {
68 	byte value;
69 
70 	while (bsize > 0) {
71 		if (_IACTpos >= 2) {
72 			int32 len = READ_BE_UINT16(_IACToutput) + 2;
73 			len -= _IACTpos;
74 			if (len > bsize) {
75 				memcpy(_IACToutput + _IACTpos, d_src, bsize);
76 				_IACTpos += bsize;
77 				bsize = 0;
78 			} else {
79 				memcpy(_IACToutput + _IACTpos, d_src, len);
80 				byte *dst = output_data;
81 				byte *d_src2 = _IACToutput;
82 				d_src2 += 2;
83 				int32 count = 1024;
84 				byte variable1 = *d_src2++;
85 				byte variable2 = variable1 / 16;
86 				variable1 &= 0x0f;
87 				do {
88 					value = *(d_src2++);
89 					if (value == 0x80) {
90 						*dst++ = *d_src2++;
91 						*dst++ = *d_src2++;
92 					} else {
93 						int16 val = (int8)value << variable2;
94 						*dst++ = val >> 8;
95 						*dst++ = (byte)(val);
96 					}
97 					value = *(d_src2++);
98 					if (value == 0x80) {
99 						*dst++ = *d_src2++;
100 						*dst++ = *d_src2++;
101 					} else {
102 						int16 val = (int8)value << variable1;
103 						*dst++ = val >> 8;
104 						*dst++ = (byte)(val);
105 					}
106 				} while (--count);
107 				writeToTempWaveFile(fileName, output_data, 0x1000);
108 				bsize -= len;
109 				d_src += len;
110 				_IACTpos = 0;
111 			}
112 		} else {
113 			if (bsize > 1 && _IACTpos == 0) {
114 				*(_IACToutput + 0) = *d_src++;
115 				_IACTpos = 1;
116 				bsize--;
117 			}
118 			*(_IACToutput + _IACTpos) = *d_src++;
119 			_IACTpos++;
120 			bsize--;
121 		}
122 	}
123 }
124 
handleComiIACT(Common::File & input,int size,const std::string & outputDir,const std::string & inputFilename)125 void CompressScummSan::handleComiIACT(Common::File &input, int size, const std::string &outputDir, const std::string &inputFilename) {
126 	input.seek(10, SEEK_CUR);
127 	int bsize = size - 18;
128 	byte output_data[0x1000];
129 	byte *src = (byte *)malloc(bsize);
130 	input.read_throwsOnError(src, bsize);
131 
132 	const std::string tmpPath = outputDir + inputFilename + ".raw";
133 	decompressComiIACT(tmpPath, output_data, src, bsize);
134 
135 	free(src);
136 }
137 
allocAudioTrack(int trackId,int frame)138 CompressScummSan::AudioTrackInfo *CompressScummSan::allocAudioTrack(int trackId, int frame) {
139 	for (int l = 0; l < COMPRESS_SCUMM_SAN_MAX_TRACKS; l++) {
140 		if ((_audioTracks[l].animFrame != frame) && (_audioTracks[l].trackId != trackId) && (!_audioTracks[l].used))
141 			return &_audioTracks[l];
142 	}
143 	return NULL;
144 }
145 
findAudioTrack(int trackId)146 CompressScummSan::AudioTrackInfo *CompressScummSan::findAudioTrack(int trackId) {
147 	for (int l = 0; l < COMPRESS_SCUMM_SAN_MAX_TRACKS; l++) {
148 		if (_audioTracks[l].trackId == trackId && _audioTracks[l].used && _audioTracks[l].file.isOpen())
149 			return &_audioTracks[l];
150 	}
151 	return NULL;
152 }
153 
flushTracks(int frame)154 void CompressScummSan::flushTracks(int frame) {
155 	for (int l = 0; l < COMPRESS_SCUMM_SAN_MAX_TRACKS; l++) {
156 		if (_audioTracks[l].used && _audioTracks[l].file.isOpen() && (frame - _audioTracks[l].lastFrame) > 1) {
157 			_audioTracks[l].file.close();
158 		}
159 	}
160 }
161 
prepareForMixing(const std::string & outputDir,const std::string & inputFilename)162 void CompressScummSan::prepareForMixing(const std::string &outputDir, const std::string &inputFilename) {
163 	char filename[200];
164 
165 	print("Decompressing tracks files...");
166 	for (int l = 0; l < COMPRESS_SCUMM_SAN_MAX_TRACKS; l++) {
167 		if (_audioTracks[l].used) {
168 			_audioTracks[l].file.close();
169 
170 			sprintf(filename, "%s%s_%04d_%03d.tmp", outputDir.c_str(), inputFilename.c_str(), _audioTracks[l].animFrame, _audioTracks[l].trackId);
171 			_audioTracks[l].file.open(filename, "rb");
172 			_audioTracks[l].file.seek(0, SEEK_END);
173 			int fileSize = _audioTracks[l].file.pos();
174 			_audioTracks[l].file.seek(0, SEEK_SET);
175 			byte *audioBuf = (byte *)malloc(fileSize);
176 			_audioTracks[l].file.read_throwsOnError(audioBuf, fileSize);
177 			_audioTracks[l].file.close();
178 
179 			int outputSize = fileSize;
180 			if (_audioTracks[l].bits == 8)
181 				outputSize *= 2;
182 			if (_audioTracks[l].bits == 12)
183 				outputSize = (outputSize / 3) * 4;
184 			if (!_audioTracks[l].stereo)
185 				outputSize *= 2;
186 			if (_audioTracks[l].freq == 11025)
187 				outputSize *= 2;
188 
189 			byte *outputBuf = (byte *)malloc(outputSize);
190 			if (_audioTracks[l].bits == 8) {
191 				byte *buf = outputBuf;
192 				byte *src = audioBuf;
193 				for (int i = 0; i < fileSize; i++) {
194 					uint16 val = (*src++ - 0x80) << 8;
195 					*buf++ = (byte)val;
196 					*buf++ = (byte)(val >> 8);
197 					if (_audioTracks[l].freq == 11025) {
198 						*buf++ = (byte)val;
199 						*buf++ = (byte)(val >> 8);
200 					}
201 					if (!_audioTracks[l].stereo) {
202 						*buf++ = (byte)val;
203 						*buf++ = (byte)(val >> 8);
204 						if (_audioTracks[l].freq == 11025) {
205 							*buf++ = (byte)val;
206 							*buf++ = (byte)(val >> 8);
207 						}
208 					}
209 				}
210 			}
211 			if (_audioTracks[l].bits == 12) {
212 				int loop_size = fileSize / 3;
213 				byte *decoded = outputBuf;
214 				byte *source = audioBuf;
215 				uint32 value;
216 
217 				while (loop_size--) {
218 					byte v1 =  *source++;
219 					byte v2 =  *source++;
220 					byte v3 =  *source++;
221 					value = ((((v2 & 0x0f) << 8) | v1) << 4) - 0x8000;
222 					*decoded++ = (byte)(value & 0xff);
223 					*decoded++ = (byte)((value >> 8) & 0xff);
224 					if (_audioTracks[l].freq == 11025) {
225 						*decoded++ = (byte)(value & 0xff);
226 						*decoded++ = (byte)((value >> 8) & 0xff);
227 					}
228 					if (!_audioTracks[l].stereo) {
229 						*decoded++ = (byte)(value & 0xff);
230 						*decoded++ = (byte)((value >> 8) & 0xff);
231 						if (_audioTracks[l].freq == 11025) {
232 							*decoded++ = (byte)(value & 0xff);
233 							*decoded++ = (byte)((value >> 8) & 0xff);
234 						}
235 					}
236 					value = ((((v2 & 0xf0) << 4) | v3) << 4) - 0x8000;
237 					*decoded++ = (byte)(value & 0xff);
238 					*decoded++ = (byte)((value >> 8) & 0xff);
239 					if (_audioTracks[l].freq == 11025) {
240 						*decoded++ = (byte)(value & 0xff);
241 						*decoded++ = (byte)((value >> 8) & 0xff);
242 					}
243 					if (!_audioTracks[l].stereo) {
244 						*decoded++ = (byte)(value & 0xff);
245 						*decoded++ = (byte)((value >> 8) & 0xff);
246 						if (_audioTracks[l].freq == 11025) {
247 							*decoded++ = (byte)(value & 0xff);
248 							*decoded++ = (byte)((value >> 8) & 0xff);
249 						}
250 					}
251 				}
252 			}
253 
254 			free(audioBuf);
255 			_audioTracks[l].file.open(filename, "wb");
256 			_audioTracks[l].file.write(outputBuf, outputSize);
257 			free(outputBuf);
258 		}
259 	}
260 }
261 
262 #define ST_SAMPLE_MAX 0x7fffL
263 #define ST_SAMPLE_MIN (-ST_SAMPLE_MAX - 1L)
264 
clampedAdd(int16 & a,int b)265 static inline void clampedAdd(int16& a, int b) {
266 	register int val;
267 	val = a + b;
268 
269 	if (val > ST_SAMPLE_MAX)
270 		val = ST_SAMPLE_MAX;
271 	else if (val < ST_SAMPLE_MIN)
272 		val = ST_SAMPLE_MIN;
273 
274 	a = val;
275 }
mixing(const std::string & outputDir,const std::string & inputFilename,int frames,int fps)276 void CompressScummSan::mixing(const std::string &outputDir, const std::string &inputFilename, int frames, int fps) {
277 	int l, r, z;
278 
279 	const std::string wavPath = outputDir + inputFilename + ".raw";
280 	Common::File &wavFile(_waveTmpFile);
281 
282 	wavFile.open(wavPath.c_str(), "wb+");
283 
284 	int frameAudioSize = 0;
285 	if (fps == 12) {
286 		frameAudioSize = 7352;
287 	} else if (fps == 10) {
288 		frameAudioSize = 8802;
289 	} else {
290 		error("Unsupported fps value %d", fps);
291 	}
292 
293 	print("Creating silent wav file...");
294 	for (l = 0; l < frameAudioSize * frames; l++) {
295 		wavFile.writeByte(0);
296 	}
297 
298 	print("Mixing tracks into wav file...");
299 	for (l = 0; l < COMPRESS_SCUMM_SAN_MAX_TRACKS; l++) {
300 		if (_audioTracks[l].used) {
301 			char filename[200];
302 			sprintf(filename, "%s%s_%04d_%03d.tmp", outputDir.c_str(), inputFilename.c_str(), _audioTracks[l].animFrame, _audioTracks[l].trackId);
303 			_audioTracks[l].file.open(filename, "rb");
304 			const uint32 fileSize = _audioTracks[l].file.size();
305 			byte *tmpBuf = (byte *)malloc(fileSize);
306 			_audioTracks[l].file.read_throwsOnError(tmpBuf, fileSize);
307 			_audioTracks[l].file.close();
308 			Common::removeFile(filename);
309 
310 			byte *wavBuf = (byte *)malloc(fileSize);
311 			memset(wavBuf, 0, fileSize);
312 			wavFile.seek(frameAudioSize * _audioTracks[l].animFrame, SEEK_SET);
313 			try {
314 				wavFile.read_throwsOnError(wavBuf, fileSize);
315 			} catch (...) {
316 				// ... pass through
317 				wavFile.clearErr();
318 			}
319 
320 			int offset = 0;
321 			for (z = 0; z < _audioTracks[l].countFrames; z++) {
322 				int length = _audioTracks[l].sizes[z];
323 				if (length == 0) {
324 					warning("zero length audio frame");
325 					break;
326 				}
327 				if (_audioTracks[l].sdatSize != 0 && (offset + length) > _audioTracks[l].sdatSize) {
328 					length = _audioTracks[l].sdatSize - offset;
329 				}
330 				int volume = _audioTracks[l].volumes[z];
331 				for (r = 0; r < length; r += 4) {
332 					int16 wavSampleL = (int16)READ_LE_UINT16(wavBuf + offset + r + 0);
333 					int16 wavSampleR = (int16)READ_LE_UINT16(wavBuf + offset + r + 2);
334 					int32 tmpSampleL = (int16)READ_LE_UINT16(tmpBuf + offset + r + 0);
335 					int32 tmpSampleR = (int16)READ_LE_UINT16(tmpBuf + offset + r + 2);
336 					tmpSampleL = (tmpSampleL * volume) / 255;
337 					tmpSampleR = (tmpSampleR * volume) / 255;
338 					clampedAdd(wavSampleL, tmpSampleL);
339 					clampedAdd(wavSampleR, tmpSampleR);
340 					WRITE_LE_UINT16(wavBuf + offset + r + 0, wavSampleL);
341 					WRITE_LE_UINT16(wavBuf + offset + r + 2, wavSampleR);
342 				}
343 				offset += length;
344 			}
345 			wavFile.seek(frameAudioSize * _audioTracks[l].animFrame, SEEK_SET);
346 			wavFile.write(wavBuf, fileSize);
347 
348 			free(wavBuf);
349 			free(tmpBuf);
350 		}
351 	}
352 
353 	_waveDataSize = frames * frameAudioSize;
354 }
355 
handleMapChunk(AudioTrackInfo * audioTrack,Common::File & input)356 void CompressScummSan::handleMapChunk(AudioTrackInfo *audioTrack, Common::File &input) {
357 	uint32 tag;
358 	int32 size;
359 	tag = input.readUint32BE();
360 	size = input.readUint32BE();
361 	assert(tag == 'iMUS');
362 	tag = input.readUint32BE();
363 	size = input.readUint32BE();
364 	assert(tag == 'MAP ');
365 	tag = input.readUint32BE();
366 	size = input.readUint32BE();
367 	assert(tag == 'FRMT');
368 	input.seek(8, SEEK_CUR);
369 	audioTrack->bits = input.readUint32BE();
370 	audioTrack->freq = input.readUint32BE();
371 	int chan = input.readUint32BE();
372 	if (chan == 2)
373 		audioTrack->stereo = true;
374 	tag = input.readUint32BE();
375 	size = input.readUint32BE();
376 	if (tag == 'TEXT') {
377 		input.seek(size, SEEK_CUR);
378 		tag = input.readUint32BE();
379 		size = input.readUint32BE();
380 		if (tag == 'TEXT') {
381 			input.seek(size, SEEK_CUR);
382 			tag = input.readUint32BE();
383 			size = input.readUint32BE();
384 		}
385 	}
386 	assert(tag == 'REGN');
387 	input.seek(8, SEEK_CUR);
388 	tag = input.readUint32BE();
389 	size = input.readUint32BE();
390 	if (tag == 'TEXT') {
391 		input.seek(size, SEEK_CUR);
392 		tag = input.readUint32BE();
393 		size = input.readUint32BE();
394 		if (tag == 'REGN') {
395 			input.seek(8, SEEK_CUR);
396 			tag = input.readUint32BE();
397 			size = input.readUint32BE();
398 		}
399 	}
400 	if (tag == 'STOP') {
401 		input.seek(4, SEEK_CUR);
402 		tag = input.readUint32BE();
403 		size = input.readUint32BE();
404 	}
405 	assert(tag == 'DATA');
406 }
407 
handleSaudChunk(AudioTrackInfo * audioTrack,Common::File & input)408 int32 CompressScummSan::handleSaudChunk(AudioTrackInfo *audioTrack, Common::File &input) {
409 	uint32 tag;
410 	int32 size;
411 	tag = input.readUint32BE();
412 	size = input.readUint32BE();
413 	assert(tag == 'SAUD');
414 	tag = input.readUint32BE();
415 	size = input.readUint32BE();
416 	assert(tag == 'STRK');
417 	input.seek(size, SEEK_CUR);
418 	tag = input.readUint32BE();
419 	size = input.readUint32BE();
420 	assert(tag == 'SDAT');
421 	return size;
422 }
423 
handleAudioTrack(int index,int trackId,int frame,int nbframes,Common::File & input,const std::string & outputDir,const std::string & inputFilename,int & size,int volume,int pan,bool iact)424 void CompressScummSan::handleAudioTrack(int index, int trackId, int frame, int nbframes, Common::File &input, const std::string &outputDir,
425 					  const std::string &inputFilename, int &size, int volume, int pan, bool iact) {
426 	AudioTrackInfo *audioTrack = NULL;
427 	if (index == 0) {
428 		audioTrack = allocAudioTrack(trackId, frame);
429 		assert(audioTrack);
430 		audioTrack->animFrame = frame;
431 		audioTrack->trackId = trackId;
432 		audioTrack->used = true;
433 		audioTrack->nbframes = nbframes;
434 		audioTrack->volumes = (int *)malloc(nbframes * sizeof(int));
435 		audioTrack->pans = (int *)malloc(nbframes * sizeof(int));
436 		audioTrack->sizes = (int *)malloc(nbframes * sizeof(int));
437 		memset(audioTrack->sizes, 0, nbframes * sizeof(int));
438 		if (iact) {
439 			int pos = input.pos();
440 			handleMapChunk(audioTrack, input);
441 			size -= (input.pos() - pos) + 18;
442 		} else {
443 			audioTrack->bits = 8;
444 			audioTrack->stereo = false;
445 			audioTrack->freq = 22050;
446 			int pos = input.pos();
447 			audioTrack->sdatSize = handleSaudChunk(audioTrack, input);
448 			audioTrack->sdatSize *= 4;
449 			size -= (input.pos() - pos) + 10;
450 			audioTrack->lastFrame = frame;
451 		}
452 		char tmpPath[1024];
453 		sprintf(tmpPath, "%s%s_%04d_%03d.tmp", outputDir.c_str(), inputFilename.c_str(), frame, trackId);
454 		audioTrack->file.open(tmpPath, "wb");
455 		if (!audioTrack->file.isOpen()) {
456 			error("error writing temp file");
457 		}
458 	} else {
459 		if (!iact)
460 			flushTracks(frame);
461 		audioTrack = findAudioTrack(trackId);
462 		assert(audioTrack);
463 		if (iact)
464 			size -= 18;
465 		else {
466 			size -= 10;
467 			audioTrack->lastFrame = frame;
468 		}
469 	}
470 	byte *buffer = (byte *)malloc(size);
471 	input.read_throwsOnError(buffer, size);
472 	audioTrack->file.write(buffer, size);
473 	free(buffer);
474 	audioTrack->volumes[index] = volume;
475 	audioTrack->pans[index] = pan;
476 	audioTrack->sizes[index] = size;
477 	if (audioTrack->bits == 8)
478 		audioTrack->sizes[index] *= 2;
479 	if (audioTrack->bits == 12)
480 		audioTrack->sizes[index] = (audioTrack->sizes[index] / 3) * 4;
481 	if (!audioTrack->stereo)
482 		audioTrack->sizes[index] *= 2;
483 	if (audioTrack->freq == 11025)
484 		audioTrack->sizes[index] *= 2;
485 	audioTrack->countFrames++;
486 
487 	// FIXME. This doesn't work with Russian FT
488 	if ((index + 1) >= nbframes) {
489 		audioTrack->file.close();
490 	}
491 }
492 
handleDigIACT(Common::File & input,int size,const std::string & outputDir,const std::string & inputFilename,int flags,int track_flags,int frame)493 void CompressScummSan::handleDigIACT(Common::File &input, int size, const std::string &outputDir, const std::string &inputFilename,int flags, int track_flags, int frame) {
494 	int track = input.readUint16LE();
495 	int index = input.readUint16LE();
496 	int nbframes = input.readUint16LE();
497 	/*int data_size = */ input.readUint32LE();
498 	int volume = 127;
499 	int trackId = track;
500 	int pan = 0;
501 
502 	if (track_flags == 1) {
503 		trackId = track + 100;
504 	} else if (track_flags == 2) {
505 		trackId = track + 200;
506 	} else if (track_flags == 3) {
507 		trackId = track + 300;
508 	} else if ((track_flags >= 100) && (track_flags <= 163)) {
509 		trackId = track + 400;
510 		volume = track_flags * 2 - 200;
511 	} else if ((track_flags >= 200) && (track_flags <= 263)) {
512 		trackId = track + 500;
513 		volume = track_flags * 2 - 400;
514 	} else if ((track_flags >= 300) && (track_flags <= 363)) {
515 		trackId = track + 600;
516 		volume = track_flags * 2 - 600;
517 	} else {
518 		error("handleDigIACT() Bad track_flags: %d", track_flags);
519 	}
520 
521 	handleAudioTrack(index, trackId, frame, nbframes, input, outputDir, inputFilename, size, volume, pan, true);
522 }
523 
handlePSAD(Common::File & input,int size,const std::string & outputDir,const std::string & inputFilename,int frame)524 void CompressScummSan::handlePSAD(Common::File &input, int size, const std::string &outputDir, const std::string &inputFilename, int frame) {
525 	int trackId = input.readUint16LE();
526 	int index = input.readUint16LE();
527 	int nbframes = input.readUint16LE();
528 	/*int flags = */ input.readUint16LE();
529 	int volume = input.readByte();
530 	int pan = input.readByte();
531 
532 	handleAudioTrack(index, trackId, frame, nbframes, input, outputDir, inputFilename, size, volume, pan, false);
533 }
534 
CompressScummSan(const std::string & name)535 CompressScummSan::CompressScummSan(const std::string &name) : CompressionTool(name, TOOLTYPE_COMPRESSION) {
536 	_IACTpos = 0;
537 
538 	_supportedFormats = AudioFormat(AUDIO_MP3 | AUDIO_VORBIS);
539 	_supportsProgressBar = true;
540 	_supportsMultipleRuns = true;
541 
542 	ToolInput input;
543 	input.format = "*.san";
544 	_inputPaths.push_back(input);
545 
546 	_shorthelp = "Used to compress .san files found in the later SCUMM games.";
547 	// TODO: Feature set seems more limited than what kCompressionAudioHelp contains
548 	_helptext = "\nUsage: " + getName() + " [mode] [mode-params] [-o outpufile = inputfile.san] <inputfile>\n";
549 }
550 
execute()551 void CompressScummSan::execute() {
552 	if (_format == AUDIO_FLAC)
553 		error("Only ogg vorbis and MP3 are supported for this tool.");
554 
555 	Common::Filename inpath(_inputPaths[0].path);
556 	Common::Filename outpath(_outputPath);
557 
558 	// We default to the current directory.
559 	// TODO: We shouldn't have to do this, an empty output path *should* work
560 	// fine. However, it currently doesn't, because we insert an extra slash
561 	// between the outpath and some filename, which would cause paths like
562 	//  "/foo.san" be formed -- and we wouldn't want to write into the root dir
563 	// by accident, right?
564 	if (outpath.empty()) {
565 		outpath.setFullPath("./");	// FIXME: Crude hack. Will this work on Windows?
566 	}
567 
568 	// Use the same filename as for the input file, and ensure the extension is right.
569 	outpath.setFullName(inpath.getName());
570 	outpath.setExtension(".san");
571 
572 	// Don't use the input file name for output by some weird accident.
573 	// (This check won't catch all cases of this, but it's better than nothing.)
574 	assert(inpath.getFullPath() != outpath.getFullPath());
575 
576 	Common::File input(inpath, "rb");
577 	Common::File output(outpath, "wb");
578 
579 	Common::Filename flupath(inpath);
580 	flupath.setExtension(".flu");
581 
582 	Common::File flu_in;
583 
584 	try {
585 		flu_in.open(flupath, "rb");
586 	} catch (...) {
587 		// pass
588 	}
589 
590 	Common::File flu_out;
591 	if (flu_in.isOpen()) {
592 		flupath = outpath;
593 		flupath.setExtension(".flu");
594 		flu_out.open(flupath, "wb");
595 	}
596 
597 	int32 l, size;
598 
599 	output.writeUint32BE(input.readUint32BE()); // ANIM
600 	int32 animChunkSize = input.readUint32BE(); // ANIM size
601 	output.writeUint32BE(animChunkSize);
602 
603 	output.writeUint32BE(input.readUint32BE()); // AHDR
604 	size = input.readUint32BE();
605 	output.writeUint32BE(size); // AHDR size
606 	output.writeUint16BE(input.readUint16BE()); // version
607 	int32 nbframes = input.readUint16LE(); // number frames
608 	output.writeUint16LE(nbframes);
609 	output.writeUint16BE(input.readUint16BE()); // unk
610 	for (l = 0; l < size - 6; l++) {
611 		output.writeByte(input.readByte()); // 0x300 palette + some bytes
612 	}
613 
614 	FrameInfo *frameInfo = (FrameInfo *)malloc(sizeof(FrameInfo) * nbframes);
615 
616 	for (l = 0; l < COMPRESS_SCUMM_SAN_MAX_TRACKS; l++) {
617 		_audioTracks[l].animFrame = -1;
618 		_audioTracks[l].trackId = 0;
619 		_audioTracks[l].bits = 0;
620 		_audioTracks[l].stereo = 0;
621 		_audioTracks[l].freq = 0;
622 		_audioTracks[l].used = 0;
623 		_audioTracks[l].waveDataSize = 0;
624 		_audioTracks[l].volumes = 0;
625 		_audioTracks[l].pans = 0;
626 		_audioTracks[l].sizes = 0;
627 		_audioTracks[l].nbframes = 0;
628 		_audioTracks[l].countFrames = 0;
629 		_audioTracks[l].lastFrame = 0;
630 		_audioTracks[l].sdatSize = 0;
631 	}
632 
633 	bool tracksCompress = false;
634 	int fps = 0;
635 	uint32 inputSize = input.size();
636 
637 	print("Frames: %d", nbframes);
638 
639 	for (l = 0; l < nbframes; l++) {
640 		// Compression takes place in this loops, which takes the most time by far
641 		updateProgress(l, nbframes);
642 
643 		print("frame: %d", l);
644 		bool first_fobj = true;
645 		uint32 tag = input.readUint32BE(); // chunk tag
646 		assert(tag == 'FRME');
647 		output.writeUint32BE(tag); // FRME
648 		int32 frameSize = input.readUint32BE(); // FRME size
649 		frameInfo[l].frameSize = frameSize;
650 		frameInfo[l].offsetOutput = output.pos();
651 		frameInfo[l].fobjDecompressedSize = 0;
652 		frameInfo[l].fobjCompressedSize = 0;
653 		frameInfo[l].lessIACTSize = 0;
654 		frameInfo[l].lessPSADSize = 0;
655 		output.writeUint32BE(frameSize);
656 		for (;;) {
657 			try {
658 				tag = input.readUint32BE(); // chunk tag
659 			} catch (...) {
660 				break;
661 			}
662 
663 			if (input.eos())
664 				break;
665 			if (tag == 'FRME') {
666 				input.seek(-4, SEEK_CUR);
667 				break;
668 			} else if ((tag == 'FOBJ') && (first_fobj)) {
669 				size = input.readUint32BE(); // FOBJ size
670 				if ((size & 1) != 0)
671 					size++;
672 				first_fobj = false;
673 				unsigned long outputSize = size + (size / 9) + 100;
674 				byte *zlibInputBuffer = (byte *)malloc(size);
675 				byte *zlibOutputBuffer = (byte *)malloc(outputSize);
676 				for (int k = 0; k < size; k++) {
677 					*(zlibInputBuffer + k) = input.readByte(); // FOBJ datas
678 				}
679 				int result = compress2(zlibOutputBuffer, &outputSize, zlibInputBuffer, size, 9);
680 				if (result != Z_OK) {
681 					error("compression error");
682 				}
683 				if ((outputSize & 1) != 0)
684 					outputSize++;
685 				frameInfo[l].fobjDecompressedSize = size;
686 				frameInfo[l].fobjCompressedSize = outputSize;
687 				output.writeUint32BE('ZFOB');
688 				output.writeUint32BE(outputSize + 4);
689 				output.writeUint32BE(size);
690 				for (unsigned int k = 0; k < outputSize; k++) {
691 					output.writeByte(*(zlibOutputBuffer + k)); // compressed FOBJ datas
692 				}
693 				free(zlibInputBuffer);
694 				free(zlibOutputBuffer);
695 				continue;
696 			} else if ((tag == 'IACT') && (!flu_in.isOpen())) {
697 				size = input.readUint32BE(); // chunk size
698 				int code = input.readUint16LE();
699 				int flags = input.readUint16LE();
700 				int unk = input.readUint16LE();
701 				int track_flags = input.readUint16LE();
702 				if ((code == 8) && (track_flags == 0) && (unk == 0) && (flags == 46)) {
703 					handleComiIACT(input, size, outpath.getPath(), inpath.getFullName());
704 				} else if ((code == 8) && (track_flags != 0) && (unk == 0) && (flags == 46)) {
705 					handleDigIACT(input, size, outpath.getPath(), inpath.getFullName(), flags, track_flags, l);
706 					tracksCompress = true;
707 					fps = 12;
708 				} else {
709 					input.seek(-12, SEEK_CUR);
710 					goto skip;
711 				}
712 
713 				if ((size & 1) != 0) {
714 					input.seek(1, SEEK_CUR);
715 					size++;
716 				}
717 				frameInfo[l].lessIACTSize += size + 8;
718 				continue;
719 			} else if ((tag == 'PSAD') && (!flu_in.isOpen())) {
720 				size = input.readUint32BE(); // chunk size
721 				handlePSAD(input, size, outpath.getPath(), inpath.getFullName(), l);
722 				if ((size & 1) != 0) {
723 					input.seek(1, SEEK_CUR);
724 					size++;
725 				}
726 				frameInfo[l].lessPSADSize += size + 8;
727 				tracksCompress = true;
728 				fps = 10;
729 			} else {
730 skip:
731 				size = input.readUint32BE(); // chunk size
732 
733 				// Some files have garbage at the end
734 				if ((uint32)(size + input.pos()) > inputSize) {
735 					print("Skipping rest of the file (%d bytes)", inputSize - input.pos());
736 					break;
737 				}
738 
739 				output.writeUint32BE(tag);
740 				output.writeUint32BE(size);
741 				if ((size & 1) != 0)
742 					size++;
743 				for (int k = 0; k < size; k++) {
744 					output.writeByte(input.readByte()); // chunk datas
745 				}
746 			}
747 		}
748 	}
749 
750 	if (tracksCompress) {
751 		prepareForMixing(outpath.getPath(), inpath.getFullName());
752 		assert(fps);
753 		mixing(outpath.getPath(), inpath.getFullName(), nbframes, fps);
754 	}
755 
756 	if (_waveTmpFile.isOpen()) {
757 		std::string tmpPath = outpath.getPath() + inpath.getFullName();
758 		setRawAudioType(true, true, 16); // LE, stereo, 16-bit
759 
760 		if (_format == AUDIO_VORBIS)
761 			encodeSanWaveWithOgg(tmpPath);
762 		else
763 			encodeSanWaveWithLame(tmpPath);
764 		_waveTmpFile.close();
765 		tmpPath += ".raw";
766 		Common::removeFile(tmpPath.c_str());
767 	}
768 
769 	input.close();
770 
771 	print("Fixing frames header...");
772 	int32 sumDiff = 0;
773 	for (l = 0; l < nbframes; l++) {
774 		int32 diff = 0;
775 		if (frameInfo[l].fobjCompressedSize != 0) {
776 			diff += frameInfo[l].fobjDecompressedSize - (frameInfo[l].fobjCompressedSize + 4);
777 		}
778 		if (frameInfo[l].lessIACTSize != 0) {
779 			diff += frameInfo[l].lessIACTSize;
780 		}
781 		if (frameInfo[l].lessPSADSize != 0) {
782 			diff += frameInfo[l].lessPSADSize;
783 		}
784 		output.seek(frameInfo[l].offsetOutput, SEEK_SET);
785 		sumDiff += diff;
786 		if (diff != 0)
787 			output.writeUint32BE(frameInfo[l].frameSize - diff);
788 	}
789 	print("done.");
790 
791 	print("Fixing anim header...");
792 	output.seek(4, SEEK_SET);
793 	output.writeUint32BE(animChunkSize - sumDiff);
794 	print("done.");
795 
796 	if (flu_in.isOpen()) {
797 		print("Fixing flu offsets...");
798 		int fsize = flu_in.size();
799 		for (int k = 0; k < fsize; k++) {
800 			flu_out.writeByte(flu_in.readByte());
801 		}
802 		flu_out.seek(0x324, SEEK_SET);
803 		for (l = 0; l < nbframes; l++) {
804 			flu_out.writeUint32LE(frameInfo[l].offsetOutput - 4);
805 		}
806 		print("done.");
807 	}
808 
809 	free(frameInfo);
810 
811 	print("compression done.");
812 }
813 
814 #ifdef STANDALONE_MAIN
main(int argc,char * argv[])815 int main(int argc, char *argv[]) {
816 	CompressScummSan scummsan(argv[0]);
817 	return scummsan.run(argc, argv);
818 }
819 #endif
820 
821