1#include <algorithm>
2
3#include "All.h"
4#include "APEInfo.h"
5#include "APECompress.h"
6#include "APEDecompress.h"
7#include "WAVInputSource.h"
8#include IO_HEADER_FILE
9#include "MACProgressHelper.h"
10#include "GlobalFunctions.h"
11#include "CharacterHelper.h"
12#include "MD5.h"
13
14#define UNMAC_DECODER_OUTPUT_NONE       0
15#define UNMAC_DECODER_OUTPUT_WAV        1
16#define UNMAC_DECODER_OUTPUT_APE        2
17
18#define BLOCKS_PER_DECODE               9216
19
20int DecompressCore(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nOutputMode, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag);
21
22/*****************************************************************************************
23ANSI wrappers
24*****************************************************************************************/
25int __stdcall CompressFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
26{
27    CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
28    CSmartPtr<str_utf16> spOutputFile(GetUTF16FromANSI(pOutputFilename), TRUE);
29    return CompressFileW(spInputFile, spOutputFile, nCompressionLevel, pPercentageDone, ProgressCallback, pKillFlag);
30}
31
32int __stdcall DecompressFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
33{
34    CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
35    CSmartPtr<str_utf16> spOutputFile(GetUTF16FromANSI(pOutputFilename), TRUE);
36    return DecompressFileW(spInputFile, pOutputFilename ? static_cast<str_utf16*>(spOutputFile) : NULL, pPercentageDone, ProgressCallback, pKillFlag);
37}
38
39int __stdcall ConvertFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
40{
41    CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
42    CSmartPtr<str_utf16> spOutputFile(GetUTF16FromANSI(pOutputFilename), TRUE);
43    return ConvertFileW(spInputFile, spOutputFile, nCompressionLevel, pPercentageDone, ProgressCallback, pKillFlag);
44}
45
46int __stdcall VerifyFile(const str_ansi * pInputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag, BOOL bQuickVerifyIfPossible)
47{
48    CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
49    return VerifyFileW(spInputFile, pPercentageDone, ProgressCallback, pKillFlag, FALSE);
50}
51
52/*****************************************************************************************
53Compress file
54*****************************************************************************************/
55int __stdcall CompressFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
56{
57    // declare the variables
58    int nFunctionRetVal = ERROR_SUCCESS;
59    WAVEFORMATEX WaveFormatEx;
60    CSmartPtr<CMACProgressHelper> spMACProgressHelper;
61    CSmartPtr<unsigned char> spBuffer;
62    CSmartPtr<IAPECompress> spAPECompress;
63
64    try
65    {
66        // create the input source
67        int nRetVal = ERROR_UNDEFINED;
68        int nAudioBlocks = 0; int nHeaderBytes = 0; int nTerminatingBytes = 0;
69        CSmartPtr<CInputSource> spInputSource(CreateInputSource(pInputFilename, &WaveFormatEx, &nAudioBlocks,
70            &nHeaderBytes, &nTerminatingBytes, &nRetVal));
71
72        if ((spInputSource == NULL) || (nRetVal != ERROR_SUCCESS))
73            throw nRetVal;
74
75        // create the compressor
76        spAPECompress.Assign(CreateIAPECompress());
77        if (spAPECompress == NULL) throw ERROR_UNDEFINED;
78
79        // figure the audio bytes
80        int nAudioBytes = nAudioBlocks * WaveFormatEx.nBlockAlign;
81
82        // start the encoder
83        if (nHeaderBytes > 0) spBuffer.Assign(new unsigned char [nHeaderBytes], TRUE);
84        THROW_ON_ERROR(spInputSource->GetHeaderData(spBuffer.GetPtr()))
85        THROW_ON_ERROR(spAPECompress->Start(pOutputFilename, &WaveFormatEx, nAudioBytes,
86            nCompressionLevel, spBuffer.GetPtr(), nHeaderBytes));
87
88        spBuffer.Delete();
89
90        // set-up the progress
91        spMACProgressHelper.Assign(new CMACProgressHelper(nAudioBytes, pPercentageDone, ProgressCallback, pKillFlag));
92
93        // master loop
94        int nBytesLeft = nAudioBytes;
95
96        while (nBytesLeft > 0)
97        {
98            int nBytesAdded = 0;
99            THROW_ON_ERROR(spAPECompress->AddDataFromInputSource(spInputSource.GetPtr(), nBytesLeft, &nBytesAdded))
100
101            nBytesLeft -= nBytesAdded;
102
103            // update the progress
104            spMACProgressHelper->UpdateProgress(nAudioBytes - nBytesLeft);
105
106            // process the kill flag
107            if (spMACProgressHelper->ProcessKillFlag(TRUE) != ERROR_SUCCESS)
108                throw(ERROR_USER_STOPPED_PROCESSING);
109        }
110
111        // finalize the file
112        if (nTerminatingBytes > 0) spBuffer.Assign(new unsigned char [nTerminatingBytes], TRUE);
113        THROW_ON_ERROR(spInputSource->GetTerminatingData(spBuffer.GetPtr()));
114        THROW_ON_ERROR(spAPECompress->Finish(spBuffer.GetPtr(), nTerminatingBytes, nTerminatingBytes))
115
116        // update the progress to 100%
117        spMACProgressHelper->UpdateProgressComplete();
118    }
119    catch(int nErrorCode)
120    {
121        nFunctionRetVal = (nErrorCode == 0) ? ERROR_UNDEFINED : nErrorCode;
122    }
123    catch(...)
124    {
125        nFunctionRetVal = ERROR_UNDEFINED;
126    }
127
128    // kill the compressor if we failed
129    if ((nFunctionRetVal != 0) && (spAPECompress != NULL))
130        spAPECompress->Kill();
131
132    // return
133    return nFunctionRetVal;
134}
135
136
137/*****************************************************************************************
138Verify file
139*****************************************************************************************/
140int __stdcall VerifyFileW(const str_utf16 * pInputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag, BOOL bQuickVerifyIfPossible)
141{
142    // error check the function parameters
143    if (pInputFilename == NULL)
144    {
145        return ERROR_INVALID_FUNCTION_PARAMETER;
146    }
147
148
149    // return value
150    int nRetVal = ERROR_UNDEFINED;
151
152    // see if we can quick verify
153    if (bQuickVerifyIfPossible)
154    {
155        CSmartPtr<IAPEDecompress> spAPEDecompress;
156        try
157        {
158            int nFunctionRetVal = ERROR_SUCCESS;
159
160            spAPEDecompress.Assign(CreateIAPEDecompress(pInputFilename, &nFunctionRetVal));
161            if (spAPEDecompress == NULL || nFunctionRetVal != ERROR_SUCCESS) throw(nFunctionRetVal);
162
163            APE_FILE_INFO * pInfo = (APE_FILE_INFO *) spAPEDecompress->GetInfo(APE_INTERNAL_INFO);
164            if ((pInfo->nVersion < 3980) || (pInfo->spAPEDescriptor == NULL))
165                throw(ERROR_UPSUPPORTED_FILE_VERSION);
166        }
167        catch(...)
168        {
169            bQuickVerifyIfPossible = FALSE;
170        }
171    }
172
173    // if we can and should quick verify, then do it
174    if (bQuickVerifyIfPossible)
175    {
176        // variable declares
177        int nFunctionRetVal = ERROR_SUCCESS;
178        unsigned int nBytesRead = 0;
179        CSmartPtr<IAPEDecompress> spAPEDecompress;
180
181        // run the quick verify
182        try
183        {
184            spAPEDecompress.Assign(CreateIAPEDecompress(pInputFilename, &nFunctionRetVal));
185            if (spAPEDecompress == NULL || nFunctionRetVal != ERROR_SUCCESS) throw(nFunctionRetVal);
186
187            CMD5Helper MD5Helper;
188
189            CIO * pIO = GET_IO(spAPEDecompress);
190            APE_FILE_INFO * pInfo = (APE_FILE_INFO *) spAPEDecompress->GetInfo(APE_INTERNAL_INFO);
191
192            if ((pInfo->nVersion < 3980) || (pInfo->spAPEDescriptor == NULL))
193                throw(ERROR_UPSUPPORTED_FILE_VERSION);
194
195            int nHead = pInfo->nJunkHeaderBytes + pInfo->spAPEDescriptor->nDescriptorBytes;
196            int nStart = nHead + pInfo->spAPEDescriptor->nHeaderBytes + pInfo->spAPEDescriptor->nSeekTableBytes;
197
198            pIO->Seek(nHead, FILE_BEGIN);
199            int nHeadBytes = nStart - nHead;
200            CSmartPtr<unsigned char> spHeadBuffer(new unsigned char [nHeadBytes], TRUE);
201            if ((pIO->Read(spHeadBuffer, nHeadBytes, &nBytesRead) != ERROR_SUCCESS) || (nHeadBytes != int(nBytesRead)))
202                throw(ERROR_IO_READ);
203
204            int nBytesLeft = pInfo->spAPEDescriptor->nHeaderDataBytes + pInfo->spAPEDescriptor->nAPEFrameDataBytes + pInfo->spAPEDescriptor->nTerminatingDataBytes;
205            CSmartPtr<unsigned char> spBuffer(new unsigned char [16384], TRUE);
206            nBytesRead = 1;
207            while ((nBytesLeft > 0) && (nBytesRead > 0))
208            {
209                int nBytesToRead = min(16384, nBytesLeft);
210                if (pIO->Read(spBuffer, nBytesToRead, &nBytesRead) != ERROR_SUCCESS)
211                    throw(ERROR_IO_READ);
212
213                MD5Helper.AddData(spBuffer, nBytesRead);
214                nBytesLeft -= nBytesRead;
215            }
216
217            if (nBytesLeft != 0)
218                throw(ERROR_IO_READ);
219
220            MD5Helper.AddData(spHeadBuffer, nHeadBytes);
221
222            unsigned char cResult[16];
223            MD5Helper.GetResult(cResult);
224
225            if (memcmp(cResult, pInfo->spAPEDescriptor->cFileMD5, 16) != 0)
226                nFunctionRetVal = ERROR_INVALID_CHECKSUM;
227
228        }
229        catch(int nErrorCode)
230        {
231            nFunctionRetVal = (nErrorCode == 0) ? ERROR_UNDEFINED : nErrorCode;
232        }
233        catch(...)
234        {
235            nFunctionRetVal = ERROR_UNDEFINED;
236        }
237
238        // return value
239        nRetVal = nFunctionRetVal;
240    }
241    else
242    {
243        nRetVal = DecompressCore(pInputFilename, NULL, UNMAC_DECODER_OUTPUT_NONE, -1, pPercentageDone, ProgressCallback, pKillFlag);
244    }
245
246
247    return nRetVal;
248}
249
250/*****************************************************************************************
251Decompress file
252*****************************************************************************************/
253int __stdcall DecompressFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
254{
255    if (pOutputFilename == NULL)
256        return VerifyFileW(pInputFilename, pPercentageDone, ProgressCallback, pKillFlag);
257    else
258        return DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_WAV, -1, pPercentageDone, ProgressCallback, pKillFlag);
259}
260
261/*****************************************************************************************
262Convert file
263*****************************************************************************************/
264int __stdcall ConvertFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
265{
266    return DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_APE, nCompressionLevel, pPercentageDone, ProgressCallback, pKillFlag);
267}
268
269/*****************************************************************************************
270Decompress a file using the specified output method
271*****************************************************************************************/
272int DecompressCore(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nOutputMode, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
273{
274    // error check the function parameters
275    if (pInputFilename == NULL)
276    {
277        return ERROR_INVALID_FUNCTION_PARAMETER;
278    }
279
280    // variable declares
281    int nFunctionRetVal = ERROR_SUCCESS;
282    CSmartPtr<IO_CLASS_NAME> spioOutput;
283    CSmartPtr<IAPECompress> spAPECompress;
284    CSmartPtr<IAPEDecompress> spAPEDecompress;
285    CSmartPtr<unsigned char> spTempBuffer;
286    CSmartPtr<CMACProgressHelper> spMACProgressHelper;
287    WAVEFORMATEX wfeInput;
288
289    try
290    {
291        // create the decoder
292        spAPEDecompress.Assign(CreateIAPEDecompress(pInputFilename, &nFunctionRetVal));
293        if (spAPEDecompress == NULL || nFunctionRetVal != ERROR_SUCCESS) throw(nFunctionRetVal);
294
295        // get the input format
296        THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAVEFORMATEX, (int) &wfeInput))
297
298        // allocate space for the header
299        spTempBuffer.Assign(new unsigned char [spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)], TRUE);
300        if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
301
302        // get the header
303        THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_DATA, (int) spTempBuffer.GetPtr(), spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)));
304
305        // initialize the output
306        if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
307        {
308            // create the file
309            spioOutput.Assign(new IO_CLASS_NAME); THROW_ON_ERROR(spioOutput->Create(pOutputFilename))
310
311            // output the header
312            THROW_ON_ERROR(WriteSafe(spioOutput, spTempBuffer, spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)));
313        }
314        else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
315        {
316            // quit if there is nothing to do
317            if (spAPEDecompress->GetInfo(APE_INFO_FILE_VERSION) == MAC_VERSION_NUMBER && spAPEDecompress->GetInfo(APE_INFO_COMPRESSION_LEVEL) == nCompressionLevel)
318                throw(ERROR_SKIPPED);
319
320            // create and start the compressor
321            spAPECompress.Assign(CreateIAPECompress());
322            THROW_ON_ERROR(spAPECompress->Start(pOutputFilename, &wfeInput, spAPEDecompress->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS) * spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN),
323                nCompressionLevel, spTempBuffer, spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)))
324        }
325
326        // allocate space for decompression
327        spTempBuffer.Assign(new unsigned char [spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN) * BLOCKS_PER_DECODE], TRUE);
328        if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
329
330        int nBlocksLeft = spAPEDecompress->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS);
331
332        // create the progress helper
333        spMACProgressHelper.Assign(new CMACProgressHelper(nBlocksLeft / BLOCKS_PER_DECODE, pPercentageDone, ProgressCallback, pKillFlag));
334
335        // main decoding loop
336        while (nBlocksLeft > 0)
337        {
338            // decode data
339            int nBlocksDecoded = -1;
340            int nRetVal = spAPEDecompress->GetData((char *) spTempBuffer.GetPtr(), BLOCKS_PER_DECODE, &nBlocksDecoded);
341            if (nRetVal != ERROR_SUCCESS)
342                throw(ERROR_INVALID_CHECKSUM);
343
344            // handle the output
345            if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
346            {
347                unsigned int nBytesToWrite = (nBlocksDecoded * spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN));
348                unsigned int nBytesWritten = 0;
349                int nRetVal = spioOutput->Write(spTempBuffer, nBytesToWrite, &nBytesWritten);
350                if ((nRetVal != 0) || (nBytesToWrite != nBytesWritten))
351                    throw(ERROR_IO_WRITE);
352            }
353            else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
354            {
355                THROW_ON_ERROR(spAPECompress->AddData(spTempBuffer, nBlocksDecoded * spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN)))
356            }
357
358            // update amount remaining
359            nBlocksLeft -= nBlocksDecoded;
360
361            // update progress and kill flag
362            spMACProgressHelper->UpdateProgress();
363            if (spMACProgressHelper->ProcessKillFlag(TRUE) != 0)
364                throw(ERROR_USER_STOPPED_PROCESSING);
365        }
366
367        // terminate the output
368        if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
369        {
370            // write any terminating WAV data
371            if (spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES) > 0)
372            {
373                spTempBuffer.Assign(new unsigned char[spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)], TRUE);
374                if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
375                THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_DATA, (int) spTempBuffer.GetPtr(), spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)))
376
377                unsigned int nBytesToWrite = spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES);
378                unsigned int nBytesWritten = 0;
379                int nRetVal = spioOutput->Write(spTempBuffer, nBytesToWrite, &nBytesWritten);
380                if ((nRetVal != 0) || (nBytesToWrite != nBytesWritten))
381                    throw(ERROR_IO_WRITE);
382            }
383        }
384        else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
385        {
386            // write the WAV data and any tag
387            int nTagBytes = GET_TAG(spAPEDecompress)->GetTagBytes();
388            BOOL bHasTag = (nTagBytes > 0);
389            int nTerminatingBytes = nTagBytes;
390            nTerminatingBytes += spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES);
391
392            if (nTerminatingBytes > 0)
393            {
394                spTempBuffer.Assign(new unsigned char[nTerminatingBytes], TRUE);
395                if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
396
397                THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_DATA, (int) spTempBuffer.GetPtr(), nTerminatingBytes))
398
399                if (bHasTag)
400                {
401                    unsigned int nBytesRead = 0;
402                    THROW_ON_ERROR(GET_IO(spAPEDecompress)->Seek(-(nTagBytes), FILE_END))
403                    THROW_ON_ERROR(GET_IO(spAPEDecompress)->Read(&spTempBuffer[spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)], nTagBytes, &nBytesRead))
404                }
405
406                THROW_ON_ERROR(spAPECompress->Finish(spTempBuffer, nTerminatingBytes, spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)));
407            }
408            else
409            {
410                THROW_ON_ERROR(spAPECompress->Finish(NULL, 0, 0));
411            }
412        }
413
414        // fire the "complete" progress notification
415        spMACProgressHelper->UpdateProgressComplete();
416    }
417    catch(int nErrorCode)
418    {
419        nFunctionRetVal = (nErrorCode == 0) ? ERROR_UNDEFINED : nErrorCode;
420    }
421    catch(...)
422    {
423        nFunctionRetVal = ERROR_UNDEFINED;
424    }
425
426    // return
427    return nFunctionRetVal;
428}
429