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